diff --git a/coregraphicsr/Cargo.lock b/coregraphicsr/Cargo.lock new file mode 100644 index 0000000..a3c0f44 --- /dev/null +++ b/coregraphicsr/Cargo.lock @@ -0,0 +1,30 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "cc" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" + +[[package]] +name = "coregraphicsr" +version = "0.1.0" +dependencies = [ + "objr", +] + +[[package]] +name = "objr" +version = "0.1.0" +source = "git+https://github.com/drewcrawford/objr?branch=winbuild#fb540c20ee8341bcc9d1ae6bfed0a13b1702a674" +dependencies = [ + "cc", + "procmacro", +] + +[[package]] +name = "procmacro" +version = "0.1.0" +source = "git+https://github.com/drewcrawford/objr?branch=winbuild#fb540c20ee8341bcc9d1ae6bfed0a13b1702a674" diff --git a/coregraphicsr/Cargo.toml b/coregraphicsr/Cargo.toml index 07d5d0b..a9ecf90 100644 --- a/coregraphicsr/Cargo.toml +++ b/coregraphicsr/Cargo.toml @@ -6,4 +6,4 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -objr = {git = "https://github.com/drewcrawford/objr", branch = "winbuild"} \ No newline at end of file +objr = {path = "../objr-upstream", package="objr"} diff --git a/coregraphicsr/src/cgcolor.rs b/coregraphicsr/src/cgcolor.rs deleted file mode 100644 index ca34088..0000000 --- a/coregraphicsr/src/cgcolor.rs +++ /dev/null @@ -1,17 +0,0 @@ -use objr::bindings::*; -use crate::CGFloat; -objc_instance! { - pub struct CGColorRef; -} -extern "C" { - fn CGColorCreateGenericGray(grey: CGFloat, alpha: CGFloat) -> *const CGColorRef; -} - -impl CGColorRef { - pub fn grey(grey: CGFloat, alpha: CGFloat) -> StrongCell { - unsafe { - Self::assume_nonnil(CGColorCreateGenericGray(grey, alpha)).assume_retained() - } - } -} - diff --git a/coregraphicsr/src/lib.rs b/coregraphicsr/src/lib.rs index ce9c5f1..d24a0b1 100644 --- a/coregraphicsr/src/lib.rs +++ b/coregraphicsr/src/lib.rs @@ -1,6 +1 @@ -mod cgdirectdisplay; - -use objr::bindings::PerformsSelector; - -// pub use cgcolor::CGColorRef; -pub use cgdirectdisplay::CGDirectDisplayID; \ No newline at end of file +use objr::bindings::PerformsSelector; \ No newline at end of file diff --git a/corevideor/Cargo.lock b/corevideor/Cargo.lock new file mode 100644 index 0000000..c9fa908 --- /dev/null +++ b/corevideor/Cargo.lock @@ -0,0 +1,33 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "coregraphicsr" +version = "0.1.0" +dependencies = [ + "objr 0.2.0", +] + +[[package]] +name = "corevideor" +version = "0.1.0" +dependencies = [ + "coregraphicsr", + "objr 0.1.0", +] + +[[package]] +name = "objr" +version = "0.1.0" + +[[package]] +name = "objr" +version = "0.2.0" +dependencies = [ + "procmacro", +] + +[[package]] +name = "procmacro" +version = "0.1.0" diff --git a/corevideor/heaptrack.rustdoc.126411.gz b/corevideor/heaptrack.rustdoc.126411.gz new file mode 100644 index 0000000..3128271 Binary files /dev/null and b/corevideor/heaptrack.rustdoc.126411.gz differ diff --git a/corevideor/src/lib.rs b/corevideor/src/lib.rs index c196265..ece578e 100644 --- a/corevideor/src/lib.rs +++ b/corevideor/src/lib.rs @@ -1 +1 @@ -use coregraphicsr::CGDirectDisplayID; +extern crate coregraphicsr; diff --git a/objr-upstream/.github/workflows/ci.yaml b/objr-upstream/.github/workflows/ci.yaml new file mode 100644 index 0000000..294d6f6 --- /dev/null +++ b/objr-upstream/.github/workflows/ci.yaml @@ -0,0 +1,8 @@ +on: [push] +jobs: + ci: + runs-on: macos-11 + steps: + - uses: actions/checkout@v2 + - run: cargo test + - run: cargo doc \ No newline at end of file diff --git a/objr-upstream/.gitignore b/objr-upstream/.gitignore new file mode 100644 index 0000000..bab8513 --- /dev/null +++ b/objr-upstream/.gitignore @@ -0,0 +1,5 @@ +Cargo.lock +procmacro/target +target +.DS_Store +.idea diff --git a/objr-upstream/Cargo.toml b/objr-upstream/Cargo.toml new file mode 100644 index 0000000..b09650a --- /dev/null +++ b/objr-upstream/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "objr" +version = "0.2.0" +authors = ["Drew Crawford "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +procmacro = { path = "procmacro" } + +[dev-dependencies] +criterion = "0.3" + +[[bench]] +name = "basic_bench" +harness = false + +#[build-dependencies] +#cc = "1" diff --git a/objr-upstream/LICENSE.md b/objr-upstream/LICENSE.md new file mode 100644 index 0000000..db21177 --- /dev/null +++ b/objr-upstream/LICENSE.md @@ -0,0 +1,83 @@ +This software is licensed under the Prosperity Public License which permits noncommercial use only. This software may also be offered under other licenses. Commercial licensing and support is available from [the author](mailto:drew@sealedabstract.com). + +As a special exception, if you've received under $100k in funding last year, **your use doesn't count as commercial use**, even if you make money. As long as you qualify, you can use this software for any purpose without another license and free of charge. + +To decide if you qualify: + + +- If you're using this software for a client, use your client’s funding. +- Otherwise, if you’re an individual, sole proprietor, or disregarded entity, only gross revenue and funding related to this software counts. + - For example, if you use this software on a side project, but not for your main business, use the revenue and funding of your side project. You aren’t disqualified by the main business. +- Otherwise, if you’re another kind of company, use the gross revenue and funding of the company. + +# The Prosperity Public License 3.0.0 + + +Contributor: Drew Crawford + +Source Code: https://github.com/drewcrawford/objr + +## Purpose + + +This license allows you to use and share this software for noncommercial purposes for free and to try this software for commercial purposes for thirty days. + +## Agreement + + +In order to receive this license, you have to agree to its rules. Those rules are both obligations under that agreement and conditions to your license. Don’t do anything with this software that triggers a rule you can’t or won’t follow. + +## Notices + + +Make sure everyone who gets a copy of any part of this software from you, with or without changes, also gets the text of this license and the contributor and source code lines above. + +## Commercial Trial + + +Limit your use of this software for commercial purposes to a thirty-day trial period. If you use this software for work, your company gets one trial period for all personnel, not one trial per person. + +## Contributions Back + + +Developing feedback, changes, or additions that you contribute back to the contributor on the terms of a standardized public software license such as [the Blue Oak Model License 1.0.0](https://blueoakcouncil.org/license/1.0.0), [the Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html), [the MIT license](https://spdx.org/licenses/MIT.html), or [the two-clause BSD license](https://spdx.org/licenses/BSD-2-Clause.html) doesn’t count as use for a commercial purpose. + +## Personal Uses + + +Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, doesn’t count as use for a commercial purpose. + +## Noncommercial Organizations + + +Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution doesn’t count as use for a commercial purpose regardless of the source of funding or obligations resulting from the funding. + +## Defense + + +Don’t make any legal claim against anyone accusing this software, with or without changes, alone or with other technology, of infringing any patent. + +## Copyright + + +The contributor licenses you to do everything with this software that would otherwise infringe their copyright in it. + +## Patent + + +The contributor licenses you to do everything with this software that would otherwise infringe any patents they can license or become able to license. + +## Reliability + + +The contributor can’t revoke this license. + +## Excuse + + +You’re excused for unknowingly breaking [Notices](https://prosperitylicense.com/versions/3.0.0#notices) if you take all practical steps to comply within thirty days of learning you broke the rule. + +## No Liability + + +***As far as the law allows, this software comes as is, without any warranty or condition, and the contributor won’t be liable to anyone for any damages related to this software or this license, under any kind of legal claim.*** diff --git a/objr-upstream/README.md b/objr-upstream/README.md new file mode 100644 index 0000000..bf1d100 --- /dev/null +++ b/objr-upstream/README.md @@ -0,0 +1,133 @@ +# Drew's very fast objc Rust bindings +This library provides low-level Rust bindings for ObjC, which is in practice the ABI for macOS. You might compare +this crate with [objc](https://crates.io/crates/objc), [fruity](https://docs.rs/fruity/0.2.0/fruity/), [objcrs](https://crates.io/crates/objrs) +and many others. + +Distinctive features of this library include: +* Zero-cost abstractions, including the elusive "compile-time selectors" as well as many other new and exciting static technologies +* Advanced performance and codesize optimizations suitable for real-time, game, and graphical applications +* Smart pointers that integrate ObjC memory management into safe Rust abstractions +* Write ObjC subclasses directly in Rust +* Emits code similar to real ObjC compilers, for speed and future-compatibility +* Macro system for productively hand-coding bindings for new ObjC APIs +* Low level, ObjC-like APIs that you can build on to expose a Rusty interface – or not, for extra control and performance +* Minimal API coverage, leaving the question of which APIs to use and how to expose them to Rust for other crates +* Focus on modern, Apple-platform ObjC +* Free for noncommercial and "small commercial" use. + +# Detailed general examples + +## Using an external class + +```rust +//We intend to write bindings for ObjC APIs +use objr::bindings::*; +objc_class! { + //Rust wrapper type + pub struct NSDate { + //ObjC class name + @class(NSDate) + } +} +autoreleasepool(|pool| { + //In this library, autoreleasepools are often arguments to ObjC-calling APIs, providing static guarantees you created one. + //Forgetting this is a common ObjC bug. + let date = NSDate::class().alloc_init(&pool); + println!("{}",date); // 2021-06-21 19:03:15 +0000 +}) +``` + +Compare this with `objc_instance!` for non-class instances. + +## Binding an ObjC API + +```rust +use objr::bindings::*; +objc_class! { + //Rust wrapper type + pub struct NSDate { + @class(NSDate) + } +} +//Declares a group of static selectors. +objc_selector_group! { + pub trait NSDateSelectors { + @selector("dateByAddingTimeInterval:") + } + //Adds support for these selectors to our `Sel` APIs. + impl NSDateSelectors for Sel {} +} + +//Declare APIs directly on our `NSDate` wrapper type +impl NSDate { + fn dateByAddingTimeInterval(&self, pool: &ActiveAutoreleasePool, interval: f64) + //Although the underlying ObjC API returns a +0 unowned reference, + //We create a binding that returns +1 retained instead. We might do this + //because it's the preferred pattern of our application. + //For more details, see the documentation of [objc_instance!] + -> StrongCell { + //Use of ObjC is unsafe. There is no runtime or dynamic checking of your work here, + //so you must provide a safe abstraction to callers (or mark the enclosing function unsafe). + unsafe { + /*Convert from an autoreleased return value to a strong one. + This uses tricks used by real ObjC compilers and is far faster than calling `retain` yourself. + */ + let raw = Self::perform_autorelease_to_retain( + //the objc method we are calling does not mutate the receiver + self.assume_nonmut_perform(), + ///Use the compile-time selector we declared above + Sel::dateByAddingTimeInterval_(), + ///Static checking that we have an autoreleasepool available + pool, + ///Arguments. Note the trailing `,`. Arguments are tuple types. + (interval,)); + //assume the result is nonnil + Self::assume_nonnil(raw) + //assume the object is +1 convention (it is, because we called perform_autorelease_to_retain above) + .assume_retained() + } + } +} +autoreleasepool(|pool| { + //In this library, autoreleasepools are often arguments to ObjC-calling APIs, providing compile-time guarantees you created one. + //Forgetting this is a common ObjC bug. + let date = NSDate::class().alloc_init(&pool); + let new_date = date.dateByAddingTimeInterval(&pool, 23.5); +}) +``` + +For more examples, see the documentation for `objc_instance!`. + +# Feature index + +* Statically declare selectors and classes, string literals, enums, etc. so they don't have to be looked up at runtime +* Leverage the Rust typesystem to elide `retain`/`release`/`autorelease` calls in many cases. +* Participate in runtime autorelease eliding which reduces memory overhead when calling system code + This means that for programs that are mostly Rust, codegeneration may be significantly better even than real ObjC programs. +* Pointer packing for `Option<&NSObject>` +* Smart pointer system, with support for `StrongCell` and `AutoreleasedCell` +* Subclassing directly from Rust +* (limited) support for mutability and exclusive references in imported types + +Not yet implemented, but planned or possible: + +* iOS support +* Exceptions (Debug-quality API available now, see ``bindings::try_unwrap_void`) + +# Design limitations + +This library intends to follow normal guidelines for safe Rust. However, calling into ObjC means there's +a giant ball of `unsafe` somewhere. + +This library makes the assumption that the underlying ball of ObjC is implemented correctly. In particular, +it avoids runtime checks that ObjC is implemented correctly, so to the extent that it isn't, you may encounter UB. + +For more information, see the safety section of `objc_instance`! + +# objr expanded universe + +objr is part of an expanded universe of related crates that take a similar approach +to binding Apple technologies to Rust. + +* [blocksr](https://github.com/drewcrawford/blocksr) provides Clang/ObjC blocks +* [dispatchr](http://github.com/drewcrawford/dispatchr) binds libdispatch/GCD \ No newline at end of file diff --git a/objr-upstream/benches/basic_bench.rs b/objr-upstream/benches/basic_bench.rs new file mode 100644 index 0000000..e417d5f --- /dev/null +++ b/objr-upstream/benches/basic_bench.rs @@ -0,0 +1,17 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +extern crate objr; +use objr::foundation::*; + +fn alloc_init_description(c: &mut Criterion) { + autoreleasepool(|pool| { + c.bench_function("NSObject_alloc_init_description", |b| b.iter(|| { + let class = NSObject::class(); + let instance = class.alloc_init(pool); + let description = instance.description(pool).to_str(pool).len(); + black_box(description) + })); + }); +} + +criterion_group!(benches, alloc_init_description); +criterion_main!(benches); \ No newline at end of file diff --git a/objr-upstream/procmacro/Cargo.toml b/objr-upstream/procmacro/Cargo.toml new file mode 100644 index 0000000..b41bc64 --- /dev/null +++ b/objr-upstream/procmacro/Cargo.toml @@ -0,0 +1,14 @@ + + +[package] +name = "procmacro" +version = "0.1.0" +authors = ["Drew Crawford "] +edition = "2018" + +[lib] +proc-macro = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/objr-upstream/procmacro/src/classes.rs b/objr-upstream/procmacro/src/classes.rs new file mode 100644 index 0000000..b238566 --- /dev/null +++ b/objr-upstream/procmacro/src/classes.rs @@ -0,0 +1,28 @@ +//! Contains the implementation for ::objr::bindings::ObjcClass. + + + + + +pub fn implement_class(rust_name: &str,class_name: &str) -> String { + format!(r#" + impl ::objr::bindings::ObjcClass for {RUST_NAME} {{ + fn class() -> &'static ::objr::bindings::Class<{RUST_NAME}> {{ + #[inline(never)] unsafe fn merge_compilation_units() -> &'static ::objr::bindings::Class<{RUST_NAME}> {{ + extern {{ + //this link name needs to be exactly this so the linker understands we're doing an objc class + #[link_name = "\x01_OBJC_CLASS_$_{CLASS_NAME}"] + static CLASS : *mut core::ffi::c_void; + }} + + #[link_section="__DATA,__objc_classrefs,regular,no_dead_strip"] + //in practice, seems like this can be L_Anything + //but it needs to not conflict with multiple declarations + static CLASS_REF: &'static ::objr::bindings::Class<{RUST_NAME}> = unsafe{{ std::mem::transmute(&CLASS) }}; + ::core::ptr::read_volatile(&CLASS_REF) + }} + unsafe{{ merge_compilation_units() }} + }} + }} + "#, RUST_NAME=rust_name,CLASS_NAME=class_name) +} \ No newline at end of file diff --git a/objr-upstream/procmacro/src/declarations.rs b/objr-upstream/procmacro/src/declarations.rs new file mode 100644 index 0000000..4943d20 --- /dev/null +++ b/objr-upstream/procmacro/src/declarations.rs @@ -0,0 +1,471 @@ +//! Implements declaration parsing for objc headers +//! +//! Primarily used in subclassing. + +#[derive(Debug)] +struct Type(String); +#[derive(Debug)] +struct SelectorPart(String); +#[derive(Debug)] +struct ArgumentName(String); + +///Taken from https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100 +#[derive(Debug)] +//ignore the fact that some of these cases are unused +#[allow(dead_code)] +enum ParsedType { + Char, + Int, + Short, + Long, + LongLong, + UChar, + UInt, + UShort, + ULong, + ULongLong, + Float, + Double, + Bool, + Void, + CharStar, + Object, + Sel, + //These types are included but may not be correctly parsed + Array, + Structure, + Union, + Bitfield, + Pointer(Box), + Class, + Unknown, + //"Special" types, not part of the standard, but implemented for convenience + CGRect +} + +impl ParsedType { + fn type_encoding(&self) -> String { + //https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100 + match self { + ParsedType::Char => "c".to_owned(), + ParsedType::Int => "i".to_owned(), + ParsedType::Short => "s".to_owned(), + ParsedType::Long => "l".to_owned(), + ParsedType::LongLong => "q".to_owned(), + ParsedType::UChar => "C".to_owned(), + ParsedType::UInt => "I".to_owned(), + ParsedType::UShort => "S".to_owned(), + ParsedType::ULong => "L".to_owned(), + ParsedType::ULongLong => "Q".to_owned(), + ParsedType::Float => "f".to_owned(), + ParsedType::Double => "d".to_owned(), + ParsedType::Bool => "B".to_owned(), + ParsedType::Void => "v".to_owned(), + ParsedType::CharStar => "*".to_owned(), + ParsedType::Object => "@".to_owned(), + ParsedType::Sel => ":".to_owned(), + ParsedType::Array => "[v]".to_owned(), //treated as array to void + ParsedType::Structure => "{n=v}".to_owned(), //treated as struct of void + ParsedType::Union => "(n=v)".to_owned(), //union of void + ParsedType::Bitfield => "b0".to_owned(), //0 bits + ParsedType::Pointer(t) => { + let mut s = "^".to_owned(); + s.push_str(&t.type_encoding()); + s + } + ParsedType::Class => "@".to_owned(), + ParsedType::Unknown => "?".to_owned(), + ParsedType::CGRect => "{CGRect={CGPoint=dd}{CGSize=dd}}".to_owned(), + + } + } + + //This is declared as `Result` so we can make it a const fn. However, + //the err is basically a panic. + const fn magic_size(&self) -> Result { + + /*On x64 anyway this appears to be the size of the type in bytes, rounded up to the nearest word. + + e.g. char is 1 byte, but we round up to 4 + int is 4 bytes and also rounded up to 4 etc. + + I assume this is some alignment or memory thing either part of C or objc, not sure which. + + Not handling the incomplete types since it seems like more work than it's worth. + */ + match self { + ParsedType::Char => Ok(4), + ParsedType::Int => Ok(4), + ParsedType::Short => Ok(4), + ParsedType::Long => Ok(8), + ParsedType::LongLong => Ok(8), + ParsedType::UChar => Ok(4), + ParsedType::UInt => Ok(4), + ParsedType::UShort => Ok(4), + ParsedType::ULong => Ok(8), + ParsedType::ULongLong => Ok(8), + ParsedType::Float => Ok(4), + ParsedType::Double => Ok(8), + ParsedType::Bool => Ok(4), + ParsedType::Void => Err(()), + ParsedType::CharStar => Ok(8), + ParsedType::Object => Ok(8), + ParsedType::Sel => Ok(8), + ParsedType::Array => Err(()), + ParsedType::Structure => Err(()), + ParsedType::Union => Err(()), + ParsedType::Bitfield => Err(()), + ParsedType::Pointer(_) => Ok(8), + ParsedType::Class => Ok(8), + ParsedType::Unknown => Err(()), + ParsedType::CGRect => Ok(32), + } + } + fn parse(str: &str) -> Self { + match str { + "CGRect" => ParsedType::CGRect, + "NSRect" => ParsedType::CGRect, + "id" => ParsedType::Object, + "char" => ParsedType::Char, + "int" => ParsedType::Int, + "short" => ParsedType::Short, + "long" => ParsedType::Long, + "long long" => ParsedType::LongLong, + "unsigned char" => ParsedType::UChar, + "unsigned int" => ParsedType::UInt, + "unsigned short" => ParsedType::UShort, + "unsigned long"=>ParsedType::ULong, + "unsigned long long"=>ParsedType::ULongLong, + "float"=>ParsedType::Float, + "double"=>ParsedType::Double, + "bool"=>ParsedType::Bool, + "BOOL"=>ParsedType::Bool, + "void"=>ParsedType::Void, + "char*"=>ParsedType::CharStar, + "char *"=>ParsedType::CharStar, + str if str.ends_with("*") => { + //parse the part before the pointer + let prior_to_ptr = str.split_at(str.len() - 2); + let final_result = match ParsedType::parse(prior_to_ptr.0) { + ParsedType::Unknown => ParsedType::Object, + //valid types are pointers to the type + other => ParsedType::Pointer(Box::new(other)) + }; + final_result + }, + "SEL"=>ParsedType::Sel, + _ => ParsedType::Unknown + } + } +} + +///This parses expressions such as `[-/+](ReturnType) selectorPart:(ArgumentType) ArgumentName` +#[derive(Debug)] +enum DeclarationParserState { + Initial, + ///e.g., `-(void)` is `void` + ReturnType(Type), + SelectorPart(SelectorPart), + ArgumentType(Type), + ArgumentName(ArgumentName) //loops back to SelectorPart +} + +struct PartialDeclaration { + selector_part: SelectorPart, + argument_type: Type, +} + +enum PartType { + LoneSelector(SelectorPart), //something like `-(void) a`; + Argument(PartialDeclaration) //something like `-(void) a:(int) foo` +} + + +struct ParsedDeclaration { + //todo: methodkind + return_type: Type, + //All methods are required to have at least 1 part. + //To model this in the typesystem, we store the first part inline + first_part: PartType, + //subsequent parts go in here + next_parts: Vec + +} + +impl ParsedDeclaration { + fn selector(&self) -> String { + let mut s = String::new(); + match &self.first_part { + PartType::LoneSelector(sel) => { + s.push_str(&sel.0); + } + PartType::Argument(a) => { + s.push_str(&a.selector_part.0); + s.push(':'); + } + } + for part in &self.next_parts { + s.push_str(&part.selector_part.0); + s.push(':') + } + s + } + + fn type_str(&self) -> String { + let mut user_args = Vec::new(); + match &self.first_part { + PartType::LoneSelector(_) => {} + PartType::Argument(arg) => { + user_args.push(ParsedType::parse(&arg.argument_type.0)); + } + } + for arg in &self.next_parts { + user_args.push(ParsedType::parse(&arg.argument_type.0)); + } + let return_type = ParsedType::parse(&self.return_type.0); + //output starts with return type + let mut output = return_type.type_encoding(); + //Next phrase is the entire size + //calculate the arg size + let user_arg_size = user_args.iter().fold(0, |a, b| a + b.magic_size().expect("magic_size")); + let entire_size = user_arg_size + + ParsedType::Object.magic_size().expect("magic_size") //implicit self arg + + ParsedType::Sel.magic_size().expect("magic_size"); //implicit SEL arg + //return type seems not to be included in this value. + + //this consists of + //0. entire_size + //1. @0 => seems to indicate the self arg goes into some 0 slot + //2. :{} => sel goes into slot 8 + output.push_str(&format!("{}@0:{}",entire_size,ParsedType::Object.magic_size().expect("magic_size"))); + + let mut slot = ParsedType::Object.magic_size().expect("magic_size") + ParsedType::Sel.magic_size().expect("magic_size"); + for arg in user_args { + output.push_str(&arg.type_encoding()); + output.push_str(&format!("{}",slot)); + //advance slot for next time? + slot += arg.magic_size().expect("magic_size"); + } + output + } +} + +const DEBUG_PARSER: bool = false; + +impl ParsedDeclaration { + + fn from_str(str: &str) -> Result { + let mut state = DeclarationParserState::Initial; + let mut string_iter = str.chars(); + let mut return_type = None; + + let mut current_partial_argument_type = None; + let mut current_partial_selector_part = None; + + let mut parsed_partials = Vec::new(); + + while let Some(char) = string_iter.next(){ + + //I thought about parsing in wider blocks than by characters but I think + //it would complicate the tokenization (whitespace removal) somewhat. + match state { //state is moved here. After this point we need to reassign it. + DeclarationParserState::Initial => { + if char == ' ' { + state = DeclarationParserState::Initial; //continue + } + else if char == '-' { + state = DeclarationParserState::ReturnType(Type(String::with_capacity(10))); + } + else { + return Err(format!("expected `-``near {:?}",char)); + } + } + DeclarationParserState::ReturnType(partial_type) => { + if char == ' ' && partial_type.0.len() == 0 { + //ignore leading space + state = DeclarationParserState::ReturnType(partial_type); + } + else if char == ' ' { + return Err("Expected return type near ' '".to_owned()); + } + else if char == '(' { + //ignore + state = DeclarationParserState::ReturnType(partial_type); + } + else if char == ')' { + //section complete + if DEBUG_PARSER { + println!("Parsed return type {:?}",partial_type); + } + return_type = Some(partial_type); + state = DeclarationParserState::SelectorPart(SelectorPart(String::with_capacity(20))); + } + else if char == '(' || char == ' ' { + //ignore + state = DeclarationParserState::ReturnType(partial_type); + } + else { + //extend type + let mut extended_type = partial_type.0; + extended_type.push(char); + state = DeclarationParserState::ReturnType(Type(extended_type)); + } + } + DeclarationParserState::SelectorPart(partial_selector) => { + if char == ' ' && partial_selector.0.len() == 0 { + //ignore leading space + state = DeclarationParserState::SelectorPart(partial_selector); + } + else if char == ' ' { + return Err(format!("Expected `selector:` near {:?}", partial_selector)) + } + else if char == ':' { + //section complete + if DEBUG_PARSER { + println!("Parsed {:?}",partial_selector); + } + current_partial_selector_part = Some(partial_selector); + + state = DeclarationParserState::ArgumentType(Type(String::with_capacity(10))); + } + else { + //extend type + let mut partial_string = partial_selector.0; + partial_string.push(char); + state = DeclarationParserState::SelectorPart(SelectorPart(partial_string)); + } + } + DeclarationParserState::ArgumentType(partial_type) => { + if char == ' ' && partial_type.0.len() == 0 { + //ignore leading whitespace + state = DeclarationParserState::ArgumentType(partial_type) + } + else if char == ' ' { + return Err(format!("Expected argument type near whitespace after {:?}",partial_type)); + } + else if char == '(' { //ignore this token + state = DeclarationParserState::ArgumentType(partial_type) + } + else if char == ')' { + //section complete + if DEBUG_PARSER { + println!("Parsed argument type {:?}",partial_type); + } + current_partial_argument_type = Some(partial_type); + state = DeclarationParserState::ArgumentName(ArgumentName(String::with_capacity(10))); + } + else { //extend type + let mut new= partial_type.0; + new.push(char); + state = DeclarationParserState::ArgumentType(Type(new)); + } + } + DeclarationParserState::ArgumentName(partial_name) => { + if char == ' ' && partial_name.0.len() == 0 { + //ignore leading whitespace + state = DeclarationParserState::ArgumentName(partial_name) + } + else if char == ' ' { //end of argument name + if DEBUG_PARSER { + println!("Parsed {:?}",partial_name); + } + let new_part = PartialDeclaration { + argument_type: current_partial_argument_type.take().unwrap(), + selector_part: current_partial_selector_part.take().unwrap() + }; + parsed_partials.push(new_part); + state = DeclarationParserState::SelectorPart(SelectorPart(String::with_capacity(20))); + } + else { + let mut new = partial_name.0; + new.push(char); + state = DeclarationParserState::ArgumentName(ArgumentName(new)); + } + } + } + } //end of chars + + //at this point, the question is, did we stop at an OK location? + let expected: Option<&'static str> = match state { + DeclarationParserState::Initial => Some("-"), + DeclarationParserState::ReturnType(_) => Some(")"), + DeclarationParserState::SelectorPart(_) => None, //ok to stop here + DeclarationParserState::ArgumentType(_) => Some(")"), + DeclarationParserState::ArgumentName(_) => None, //ok to stop here + }; + if let Some(expected) = expected { + return Err(format!("Expected `{}` after {}",expected,str)); + } + + //Finish all our final states + //If we were parsing an argument, finish the partial + if let Some(t) = current_partial_argument_type.take() { + parsed_partials.push(PartialDeclaration { + argument_type: t, + selector_part: current_partial_selector_part.take().expect("current_partial_selector_part") + }); + } + + let first_part: PartType; + match state { + DeclarationParserState::SelectorPart(part) if parsed_partials.len() == 0 => { + //In this case, we may have parsed a bit of a selector, but did not see a `:` + //ex `-(void) foo;` + //here we want this to be a lone selector + first_part = PartType::LoneSelector(part); + } + _ => { + //otherwise the first part is the removed first element + first_part = PartType::Argument(parsed_partials.remove(0)); + } + } + + + Ok(ParsedDeclaration { + return_type: return_type.expect("return_type"), + first_part, + next_parts: parsed_partials + }) + } +} + +///Uses the above typesystem to parse a declaration into a selector +pub fn parse_to_selector(declaration: &str) -> Result { + let decl = ParsedDeclaration::from_str(declaration); + decl.map(|f| f.selector()) +} + + +pub fn parse_to_type_encoding(declaration: &str) -> Result { + let decl = ParsedDeclaration::from_str(declaration); + decl.map(|f| f.type_str()) +} + + + +#[test] +fn parse_declaration_1() { + let parse_1 = ParsedDeclaration::from_str("-(void) bar"); + assert!(parse_1.is_ok()); + let t = parse_1.unwrap(); + assert_eq!(t.selector(), "bar"); + + assert_eq!(t.type_str(), "v16@0:8"); +} + +#[test] +fn parse_declaration_2() { + let parse_2 = ParsedDeclaration::from_str("-(void) a:(int) arg b: (float) arg2"); + assert!(parse_2.is_ok(),"{:?}",parse_2.err().unwrap()); + let p = parse_2.unwrap(); + assert_eq!(p.selector(), "a:b:"); + assert_eq!(p.type_str(), "v24@0:8i16f20"); +} + +#[test] fn parse_declaration_3() { + let parse = ParsedDeclaration::from_str("-(id) initWithFrame:(CGRect) frame"); + assert!(parse.is_ok()); + let p = parse.unwrap(); + assert_eq!(p.selector(), "initWithFrame:"); + assert_eq!(p.type_str(), "@48@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16"); +} \ No newline at end of file diff --git a/objr-upstream/procmacro/src/export_name.rs b/objr-upstream/procmacro/src/export_name.rs new file mode 100644 index 0000000..a2fc5f0 --- /dev/null +++ b/objr-upstream/procmacro/src/export_name.rs @@ -0,0 +1,35 @@ +//! Helper functions to emit various link instructions + +/// __static_asciz!("LINK_SECTION",IDENT,"ascii") +/// Should expand to something like +///``` +/// #[link_section="__TEXT,test_section"] +/// static IDENT: [u8; 6] = *b"ascii\0"; +/// ``` +pub fn export_ascii(link_section:&str, ident: &str, ascii: &str) -> String { + format!( + r#" + #[link_section="{LINK_SECTION}"] + static {IDENT}: [u8; {ASCII_LEN}] = *b"{ASCII}\0"; + "# + ,LINK_SECTION=link_section,IDENT=ident,ASCII=ascii,ASCII_LEN=ascii.len() + 1) +} + +pub fn export_name_attrs(link_section: &str, export_name_1: &str, export_name_2: &str) -> String { + format!( + r#" + #[link_section="{LINK_SECTION}"] + #[export_name="{EXPORT_NAME_1}{EXPORT_NAME_2}"] + "# + ,LINK_SECTION=link_section,EXPORT_NAME_1=export_name_1, EXPORT_NAME_2=export_name_2 + ) +} +pub fn export_name_attrs3(link_section: &str, export_name_1: &str, export_name_2: &str,export_name_3: &str) -> String { + format!( + r#" + #[link_section="{LINK_SECTION}"] + #[export_name="{EXPORT_NAME_1}{EXPORT_NAME_2}{EXPORT_NAME_3}"] + "# + ,LINK_SECTION=link_section,EXPORT_NAME_1=export_name_1, EXPORT_NAME_2=export_name_2,EXPORT_NAME_3=export_name_3 + ) +} \ No newline at end of file diff --git a/objr-upstream/procmacro/src/flatten.rs b/objr-upstream/procmacro/src/flatten.rs new file mode 100644 index 0000000..8eae7d3 --- /dev/null +++ b/objr-upstream/procmacro/src/flatten.rs @@ -0,0 +1,64 @@ +//! This module implements 'flat parsing', e.g. doing a depth-first +//! traversal of groups. +//! +//! This is often useful because, for reasons I don't fully understand, +//! Rust likes to put things in random groups when I don't think they should +//! be, I just want to parse some literal or ident for example. +use proc_macro::{TokenTree,Ident,Punct,Literal}; +use proc_macro::token_stream::IntoIter; +///Like TokenTree, but without [TokenTree::Group] +#[derive(Debug)] +pub enum FlatTree { + Ident(Ident), + Punct(Punct), + Literal(Literal) +} +///Iterator type for `FlatTree`. +pub struct FlatIterator { + inner: Vec +} +impl FlatIterator { + ///Creates a new iterator from a `TokenStream` iterator. + pub fn new(into_iter: IntoIter) -> FlatIterator { + FlatIterator{ inner: vec!(into_iter) } + } +} + +impl Iterator for FlatIterator { + type Item = FlatTree; + fn next(&mut self) -> Option<::Item> { + loop { + //pop uses last in Rust + let next_iterator = self.inner.last_mut()?; + match next_iterator.next() { + None => { + self.inner.pop(); + continue; + } + Some(tree) => { + match tree { + TokenTree::Group(g) => { + let new_iterator = g.stream().into_iter(); + self.inner.push(new_iterator); + continue; + } + TokenTree::Ident(i) => { + return Some(FlatTree::Ident(i)) + } + TokenTree::Punct(p) => { + return Some(FlatTree::Punct(p)) + } + TokenTree::Literal(l) => { + return Some(FlatTree::Literal(l)) + } + } + } + } + } + + } +} + +// fn scratch() { +// let f = TokenTree:: +// } \ No newline at end of file diff --git a/objr-upstream/procmacro/src/instances.rs b/objr-upstream/procmacro/src/instances.rs new file mode 100644 index 0000000..bf98533 --- /dev/null +++ b/objr-upstream/procmacro/src/instances.rs @@ -0,0 +1,15 @@ +///Returns an implementation of ObjcInstance for type +pub fn instance_impl(_type: &str) -> String{ + format!(r#" + impl ::objr::bindings::ObjcInstance for {TYPE} {{ + }} + impl std::fmt::Display for {TYPE} {{ + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {{ + use ::objr::foundation::NSObjectTrait; + //this ought to be safe, since the object was allocated somehow and we had an autoreleasepool for that. + let fake_pool = unsafe {{ ::objr::bindings::ActiveAutoreleasePool::assume_autoreleasepool() }}; + write!(f, "{{}}",self.description(&fake_pool).to_str(&fake_pool)) + }} + }} + "#,TYPE=_type) +} \ No newline at end of file diff --git a/objr-upstream/procmacro/src/lib.rs b/objr-upstream/procmacro/src/lib.rs new file mode 100644 index 0000000..e5380ad --- /dev/null +++ b/objr-upstream/procmacro/src/lib.rs @@ -0,0 +1,765 @@ +//! objr procmacro library +//! Implements a variety of helper macros for the main crate. +//! +mod misc; +mod selectors; +mod classes; +mod instances; +mod flatten; +mod strings; +mod export_name; +mod declarations; + +use proc_macro::{TokenStream, TokenTree}; +use misc::{error, parse_literal_string,parse_ident}; +use crate::misc::ParsedLiteral; + +///``` +/// # extern crate self as objr; +/// # fn main () { } +/// # use procmacro::_objc_selector_decl; +/// # mod bindings { pub struct Sel; } +/// trait Example { +/// _objc_selector_decl!{"selector"} +/// } +/// +/// ``` +/// +/// becomes +/// ``` +/// # struct Sel; +/// trait Example { +/// unsafe fn selector() -> Sel; +/// } +/// ``` +#[proc_macro] +#[doc(hidden)] +pub fn _objc_selector_decl(stream: TokenStream) -> TokenStream { + let mut iter = stream.into_iter(); + use crate::selectors::{sel_to_rust_name, make_fn_partial}; + let selector = match parse_literal_string(&mut iter) { + Ok(s) => s, + Err(e) => return error(&format!("Expected selector, but {}",e)) + }; + let rust_name = sel_to_rust_name(&selector.unwrap_literal()); + let fn_decl = make_fn_partial(&rust_name) + ";"; + match iter.next() { + None => (), + Some(other) => { + return error(&format!("Unexpected token {}",other)); + } + } + fn_decl.parse().unwrap() +} + +/// +/// ``` +/// use procmacro::_objc_selector_impl; +/// # extern crate self as objr; +/// # fn main() { } +/// # mod bindings { +/// # pub struct Sel{ pub ptr: *const std::ffi::c_void }/// +/// # impl Sel { pub unsafe fn from_ptr(ptr: *const std::ffi::c_void) -> Sel {todo!()}} +/// # } +/// # +/// trait ExampleT{ unsafe fn selector() -> ::objr::bindings::Sel; } +/// struct ExampleS; +/// impl ExampleT for ExampleS { +/// _objc_selector_impl!{"selector"} +/// } +/// +/// ``` +/// becomes +/// ``` +/// # extern crate self as objr; +/// # fn main() { } +/// # mod bindings { +/// # pub struct Sel; +/// # } +/// trait ExampleT{ unsafe fn selector() -> ::objr::bindings::Sel; } +/// struct ExampleS; +/// impl ExampleT for ExampleS { +/// unsafe fn selector() -> ::objr::bindings::Sel { /* static magic! */ todo!() } +/// } +/// ``` +#[doc(hidden)] +#[proc_macro] +pub fn _objc_selector_impl(stream: TokenStream) -> TokenStream { + use selectors::{sel_to_rust_name,make_fn_partial,sel_expression}; + let mut iter = stream.into_iter(); + let selector = match parse_literal_string(&mut iter) { + Ok(s) => s, + Err(e) => return error(&format!("Expected selector literal, but {}",e)) + }.unwrap_literal(); + let rust_name = sel_to_rust_name(&selector); + let mut decl = make_fn_partial(&rust_name); + decl += &sel_expression(&selector); + + //check for extra tokens + match iter.next() { + None => (), + Some(other) => { + return error(&format!("Unexpected token {}",other)); + } + } + decl.parse().unwrap() +} + +///Derive macro for ObjcInstance. +/// Requires the struct to be of tuple-type and have c_void +#[proc_macro_derive(ObjcInstance)] +pub fn derive_objc_instance(stream: TokenStream) -> TokenStream { + //we're looking for something like `struct Foo` + let mut parse_ident = false; + let mut parsed_name = None; + let mut item_help = None; + + //Do a flat parse, groups are dumb + use flatten::{FlatIterator,FlatTree}; + for item in FlatIterator::new(stream.into_iter()) { + match &item { + FlatTree::Ident(i) if !parse_ident && i.to_string() == "struct" => { + parse_ident = true; //about to see the type name + } + FlatTree::Ident(i) if parse_ident => { + parsed_name = Some(i.to_string()); + break; + } + _ => () + } + item_help = Some(item); + } + if parsed_name.is_none() { + return error(&format!("Looking for `struct Identifier` near {:?}",item_help)) + } + instances::instance_impl(&parsed_name.unwrap()).parse().unwrap() +} + +///Provides an implementation of ObjcClass, based on an `objc_any_class!()` trait being in scope. +/// ``` +/// # fn main() {} //https://stackoverflow.com/questions/67443775/combining-doctests-and-extern-crate/67452255#67452255 +/// # extern crate self as objr; //pretend we're objr crate +/// # pub mod bindings { //shim objr objects +/// # use std::marker::PhantomData; +/// # pub struct Class(core::ffi::c_void, PhantomData); +/// # pub trait ObjcClass { fn class() -> &'static Class; } +/// # } +/// use procmacro::__objc_implement_class; +/// struct RustIdentifier(core::ffi::c_void); +/// trait InScopeAutoTrait { +/// fn new() -> &'static objr::bindings::Class; +/// } +/// impl InScopeAutoTrait for objr::bindings::Class { +/// fn new() -> &'static objr::bindings::Class { todo!() } +/// } +/// __objc_implement_class!{RustIdentifier} +/// ``` +#[doc(hidden)] +#[proc_macro] +pub fn __objc_implement_class(stream: TokenStream) -> TokenStream { + let mut iter = stream.into_iter(); + let rust_identifier = match parse_ident(&mut iter) { + Ok(i)=> i, + Err(err) => { return error(&format!("Expected rust identifier {:?}",err))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + let objc_identifier = match parse_ident(&mut iter) { + Ok(ident) => ident, + Err(e) => { return error(&format!("Expected objc identifier, got {}",e))} + }; + match iter.next() { + None => (), + Some(e) => { return error(&format!("Expected end of macro invocation, got {:?}",e))} + }; + + let result = classes::implement_class(&rust_identifier, &objc_identifier); + //error(&result) + result.parse().unwrap() +} + +/// Creates a compile-time NSString expression for a given literal. +/// +/// Escape sequences are not currently supported and may not compile; please file a bug. +/// The expression will be of type `&'static NSString`. +/// ``` +/// # extern crate self as objr; +/// # mod foundation { +/// # pub struct NSString; +/// # +/// # } +/// # mod bindings { +/// # use core::ffi::c_void; +/// # } +/// use procmacro::objc_nsstring; +/// # fn main() { +/// let nsstring: &'static foundation::NSString = objc_nsstring!("My test string"); +/// # } +/// +/// +/// ``` +#[proc_macro] +pub fn objc_nsstring(stream: TokenStream) -> TokenStream { + let mut iter = stream.into_iter(); + let literal = match parse_literal_string(&mut iter) { + Ok(literal) => literal, + Err(str) => {return error(&format!("Expected a literal {}",str)) } + }.unwrap_literal(); + let extra = iter.next(); + if extra.is_some() { + return error(&format!("Expected end of macro near {:?}",extra.unwrap())); + } + strings::static_string(&literal).parse().unwrap() +} + +/// Declares a static bytestring with 0 appended, with the given link_section. +/// +/// It's quite difficult to concat attributes in Rust due to limitations on emitting non-items. I can't even get munchers to inject an attribute on a macro (that expands to an item). This is a one-shot macro that does everything for you. +/// ``` +/// use procmacro::__static_asciiz; +/// __static_asciiz!("__DATA,valid_section",IDENT,"ascii"); +/// ``` +/// Should expand to something like +/// ``` +/// #[link_section="__DATA,valid_section"] +/// static IDENT: [u8; 6] = *b"ascii\0"; +/// ``` +/// # Notes: +/// * the "ascii" argument may be an ident instead of a string literal +#[doc(hidden)] +#[proc_macro] +pub fn __static_asciiz(stream: TokenStream) -> TokenStream { + let mut iter = stream.into_iter(); + let link_section = match parse_literal_string(&mut iter) { + Ok(ParsedLiteral::Literal(l)) => {l} + other => {return error(&format!("Expected link section literal, got {:?}",other))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + + let ident = match parse_ident(&mut iter) { + Ok(ident) => ident, + Err(e) => { return error(&format!("Expected identifier, got {}",e))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + let ascii = match misc::parse_ident_or_literal(&mut iter) { + Ok(l) => {l} + Err(e) => { return error(&format!("Expected literal or ident for ascii, {}",e)); } + }; + + match iter.next() { + None => (), + Some(e) => { return error(&format!("Expected end of macro invocation, got {:?}",e))} + }; + export_name::export_ascii(&link_section, &ident, &ascii).parse().unwrap() + +} + +/// Declares a static bytestring with 0 appended, with the given link_section. Variant of [__static_asciiz] that concatenates the ident from 2 parts. +/// +/// It's quite difficult to concat attributes in Rust due to limitations on emitting non-items. I can't even get munchers to inject an attribute on a macro (that expands to an item). This is a one-shot macro that does everything for you. +/// ``` +/// use procmacro::__static_asciiz_ident2; +/// __static_asciiz_ident2!("__DATA,valid_section","IDENT_1",IDENT_2,"ascii"); +/// ``` +/// Should expand to something like +/// ``` +/// #[link_section="__DATA,valid_section"] +/// static IDENT_1IDENT_2: [u8; 6] = *b"ascii\0"; +/// ``` +#[doc(hidden)] +#[proc_macro] +pub fn __static_asciiz_ident2(stream: TokenStream) -> TokenStream { + let mut iter = stream.into_iter(); + let link_section = match parse_literal_string(&mut iter) { + Ok(ParsedLiteral::Literal(l)) => {l} + other => {return error(&format!("Expected link section literal, got {:?}",other))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + + let ident_1 = match parse_literal_string(&mut iter) { + Ok(ParsedLiteral::Literal(l)) => {l} + o => { return error(&format!("Expected identifier prefix (literal), got {:?}",o))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + + let ident_2 = match parse_ident(&mut iter) { + Ok(l) => {l} + o => { return error(&format!("Expected identifier suffix (ident), got {:?}",o))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + + + let ascii = match misc::parse_ident_or_literal(&mut iter) { + Ok(l) => {l} + Err(e) => { return error(&format!("Expected literal or ident for ascii, {}",e)); } + }; + + match iter.next() { + None => (), + Some(e) => { return error(&format!("Expected end of macro invocation, got {:?}",e))} + }; + + + export_name::export_ascii(&link_section, &(ident_1 + &ident_2), &ascii).parse().unwrap() +} + +/// Declares a static bytestring with 0 appended, by parsing an objc declaration into a selector name. Variant of [__static_asciiz] that concatenates the ident from 2 parts and parses objc declarations. +/// +/// It's quite difficult to concat attributes in Rust due to limitations on emitting non-items. I can't even get munchers to inject an attribute on a macro (that expands to an item). This is a one-shot macro that does everything for you. +/// ``` +/// use procmacro::__static_asciiz_ident_as_selector; +/// __static_asciiz_ident_as_selector!("__DATA,valid_section","IDENT_1",IDENT_2,"-(void) example"); +/// ``` +/// Should expand to something like +/// ``` +/// #[link_section="__DATA,valid_section"] +/// static IDENT_1IDENT_2: [u8; 8] = *b"example\0"; +/// ``` +#[doc(hidden)] +#[proc_macro] +pub fn __static_asciiz_ident_as_selector(stream: TokenStream) -> TokenStream { + let mut iter = stream.into_iter(); + let link_section = match parse_literal_string(&mut iter) { + Ok(ParsedLiteral::Literal(l)) => {l} + other => {return error(&format!("Expected link section literal, got {:?}",other))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + + let ident_1 = match parse_literal_string(&mut iter) { + Ok(ParsedLiteral::Literal(l)) => {l} + o => { return error(&format!("Expected identifier prefix (literal), got {:?}",o))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + + let ident_2 = match parse_ident(&mut iter) { + Ok(l) => {l} + o => { return error(&format!("Expected identifier suffix (ident), got {:?}",o))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + + + let declaration = match misc::parse_ident_or_literal(&mut iter) { + Ok(l) => {l} + Err(e) => { return error(&format!("Expected literal or ident for ascii, {}",e)); } + }; + match iter.next() { + None => (), + Some(e) => { return error(&format!("Expected end of macro invocation, got {:?}",e))} + }; + + let selector = declarations::parse_to_selector(&declaration); + if selector.is_err() { + return error(&selector.err().unwrap()); + } + + export_name::export_ascii(&link_section, &(ident_1 + &ident_2), &selector.unwrap()).parse().unwrap() +} + +/// Declares a static bytestring with 0 appended, by parsing an objc declaration into a type encoding. Variant of [__static_asciiz] that concatenates the ident from 2 parts and parses objc declarations. +/// +/// It's quite difficult to concat attributes in Rust due to limitations on emitting non-items. I can't even get munchers to inject an attribute on a macro (that expands to an item). This is a one-shot macro that does everything for you. +/// ``` +/// use procmacro::__static_asciiz_ident_as_type_encoding; +/// __static_asciiz_ident_as_type_encoding!("__DATA,valid_section","IDENT_1",IDENT_2,"-(void) example"); +/// ``` +/// Should expand to something like +/// ``` +/// #[link_section="__DATA,valid_section"] +/// static IDENT_1IDENT_2: [u8; 7] = *b"v20@0:8"; +/// ``` +#[doc(hidden)] +#[proc_macro] +pub fn __static_asciiz_ident_as_type_encoding(stream: TokenStream) -> TokenStream { + let mut iter = stream.into_iter(); + let link_section = match parse_literal_string(&mut iter) { + Ok(ParsedLiteral::Literal(l)) => {l} + other => {return error(&format!("Expected link section literal, got {:?}",other))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + let ident_1 = match parse_literal_string(&mut iter) { + Ok(ParsedLiteral::Literal(l)) => {l} + o => { return error(&format!("Expected identifier prefix (literal), got {:?}",o))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + + let ident_2 = match parse_ident(&mut iter) { + Ok(l) => {l} + o => { return error(&format!("Expected identifier suffix (ident), got {:?}",o))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + + + let declaration = match misc::parse_ident_or_literal(&mut iter) { + Ok(l) => {l} + Err(e) => { return error(&format!("Expected literal or ident for ascii, {}",e)); } + }; + match iter.next() { + None => (), + Some(e) => { return error(&format!("Expected end of macro invocation, got {:?}",e))} + }; + + let type_encoding = declarations::parse_to_type_encoding(&declaration); + if type_encoding.is_err() { + return error(&type_encoding.err().unwrap()); + } + export_name::export_ascii(&link_section, &(ident_1 + &ident_2), &type_encoding.unwrap()).parse().unwrap() +} + +///Declares a static expression with `link_name` and `link_section` directives. +/// +/// It's quite difficult to concat attributes in Rust due to limitations on emitting non-items. I can't even get munchers to inject an attribute on a macro (that expands to an item). This is a one-shot macro that does everything for you. +/// +/// ``` +/// use procmacro::__static_expr; +/// __static_expr!("__DATA,valid_section","EXPORT_NAME_1",EXPORT_NAME_2,static EXAMPLE: bool = false;); +/// ``` +/// should expand to +/// ``` +/// #[link_section="__DATA,valid_section"] +/// #[export_name="EXPORT_NAME_1EXPORT_NAME_2"] +/// static EXAMPLE: bool = false; +/// ``` +#[doc(hidden)] +#[proc_macro] +pub fn __static_expr(stream: TokenStream) -> TokenStream { + let mut iter = stream.into_iter(); + let link_section = match parse_literal_string(&mut iter) { + Ok(ParsedLiteral::Literal(l)) => {l} + other => {return error(&format!("Expected link section literal, got {:?}",other))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + + let export_name_1 = match parse_literal_string(&mut iter) { + Ok(ParsedLiteral::Literal(l)) => {l} + other => {return error(&format!("Expected export name literal (prefix), got {:?}",other))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + + let export_name_2 = match misc::parse_ident(&mut iter) { + Ok(i) => {i} + other => {return error(&format!("Expected export name (suffix) ident/literal, {:?}",other))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + let attrs = export_name::export_name_attrs(&link_section, &export_name_1, &export_name_2); + let mut attr_stream: TokenStream = attrs.parse().unwrap(); + + attr_stream.extend(iter); + attr_stream +} + +///A variant of `__static_expr` with 3 parts of the `export_name` +/// ``` +/// use procmacro::__static_expr3; +/// __static_expr3!("__DATA,valid_section","EXPORT_NAME_1",EXPORT_NAME_2,"EXPORT_NAME_3",static EXAMPLE: bool = false;); +/// ``` +/// should expand to +/// ``` +/// #[link_section="__DATA,valid_section"] +/// #[export_name="EXPORT_NAME_1EXPORT_NAME_2EXPORT_NAME_3"] +/// static EXAMPLE: bool = false; +/// ``` +#[doc(hidden)] +#[proc_macro] +pub fn __static_expr3(stream: TokenStream) -> TokenStream { + let mut iter = stream.into_iter(); + let link_section = match parse_literal_string(&mut iter) { + Ok(ParsedLiteral::Literal(l)) => {l} + other => {return error(&format!("Expected link section literal, got {:?}",other))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + + let export_name_1 = match parse_literal_string(&mut iter) { + Ok(ParsedLiteral::Literal(l)) => {l} + other => {return error(&format!("Expected export name literal (prefix), got {:?}",other))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + + let export_name_2 = match misc::parse_ident(&mut iter) { + Ok(i) => {i} + other => {return error(&format!("Expected export name (suffix) ident/literal, {:?}",other))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + + let export_name_3 = match misc::parse_literal_string(&mut iter) { + Ok(ParsedLiteral::Literal(i)) => {i} + other => {return error(&format!("Expected export name (suffix) ident/literal, {:?}",other))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + let attrs = export_name::export_name_attrs3(&link_section, &export_name_1, &export_name_2, &export_name_3); + let mut attr_stream: TokenStream = attrs.parse().unwrap(); + + attr_stream.extend(iter); + attr_stream +} +/// +/// Declares an external item. +/// ``` +/// use procmacro::__static_extern; +/// extern { +/// __static_extern!("LINK_1",LINK_2, static STATIC: u32;); +/// } +/// ``` +/// Expands to +/// ``` +/// extern { +/// #[link_name="LINK_1LINK_2"] +/// static STATIC: u32; +/// } +/// ``` +/// +#[proc_macro] +pub fn __static_extern(stream: TokenStream) -> TokenStream { + let mut iter = stream.into_iter(); + let link_1 = match parse_literal_string(&mut iter) { + Ok(ParsedLiteral::Literal(s)) => s, + other => { return error(&format!("Expected link_name (prefix) literal, {:?}",other))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + let link_2 = match parse_ident(&mut iter) { + Ok(s) => s, + other => { return error(&format!("Expected link_name (prefix) literal, {:?}",other))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + let initial_str = format!(r#" + #[link_name="{LINK_1}{LINK_2}"] + "#, LINK_1 = link_1, LINK_2 = link_2); + let mut attr_stream: TokenStream = initial_str.parse().unwrap(); + attr_stream.extend(iter); + attr_stream +} + +///This counts the inputs by counting the number of commas. +/// +/// ``` +/// use procmacro::__count; +/// let ex = __count!(a,b,c); +/// assert_eq!(ex,3); +/// ``` +#[doc(hidden)] +#[proc_macro] +pub fn __count(stream: TokenStream) -> TokenStream { + let mut count = 1; + for item in stream { + match item { + TokenTree::Punct(p) if p == ',' => {count += 1}, + _ => {} + } + } + count.to_string().parse().unwrap() +} + +///Concatenates 2 idents into a single ident. Mostly useful for working around macro hygeine. +///Note that this only works in a legal position, like expression position. +/// +/// ``` +/// use procmacro::__concat_idents; +/// let myident = 2; +/// assert_eq!(__concat_idents!("my",ident),2); +/// ``` +#[doc(hidden)] +#[proc_macro] +pub fn __concat_idents(stream: TokenStream) -> TokenStream { + let mut iter = stream.into_iter(); + let item1 = match parse_literal_string(&mut iter) { + Ok(ParsedLiteral::Literal(l)) => {l} + o => { return error(&format!("Expected first ident part, {:?}",o))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + + let item2 = match parse_ident(&mut iter) { + Ok(i) => i, + Err(e) => { return error(&format!("Expected second ident part, {}",e))} + }; + return format!("{ITEM1}{ITEM2}",ITEM1=item1,ITEM2=item2).parse().unwrap() +} + +///Concatenates two modules into a module declaraton. +/// +/// ``` +/// use procmacro::__mod; +/// __mod!(id1,id2,{ +/// const example: u8 = 0; +/// }); +/// ``` +#[doc(hidden)] +#[proc_macro] +pub fn __mod(stream: TokenStream) -> TokenStream { + let mut iter = stream.into_iter(); + let item1 = match parse_ident(&mut iter) { + Ok(l) => {l} + o => { return error(&format!("Expected first ident part, {:?}",o))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + + let item2 = match parse_ident(&mut iter) { + Ok(i) => i, + Err(e) => { return error(&format!("Expected second ident part, {}",e))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + let group = match iter.next() { + Some(TokenTree::Group(g)) => { + g.to_string() + }, + o => { return error(&format!("Expected block, got {:?}",o))} + }; + let s = format!("mod {ID1}{ID2} {BLOCK}",ID1=item1, ID2=item2,BLOCK=group); + // return error(&s); + s.parse().unwrap() +} + +///Concatenates two ids into a use declaration +/// ``` +/// mod AB { +/// pub const C:u8 = 0; +/// } +/// use procmacro::__use; +/// __use!(A,B,C); +/// mod DE { +/// pub const F:u8 = 0; +/// } +/// __use!(pub D,E,F); +/// ``` +#[doc(hidden)] +#[proc_macro] +pub fn __use(stream: TokenStream) -> TokenStream { + let mut iter = stream.into_iter(); + let mut item1 = match parse_ident(&mut iter) { + Ok(l) => {l} + _ => { + //In case the what's here is something like $pub, but empty, o will be something like an empty group. + //In that case, look ahead at the next token + match parse_ident(&mut iter) { + Ok(l) => l, + o=> return error(&format!("Expected first ident part, {:?}",o)) + } + + } + }; + let is_pub; + if item1.to_string() == "pub" { + is_pub = true; + //parse again + item1 = match parse_ident(&mut iter) { + Ok(l) => {l} + o => { return error(&format!("Expected first ident part, {:?}",o))} + }; + } + else { + is_pub = false; + } + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + + let item2 = match parse_ident(&mut iter) { + Ok(i) => i, + Err(e) => { return error(&format!("Expected second ident part, {}",e))} + }; + match iter.next() { + Some(TokenTree::Punct(p)) if p == ',' => (), + o => { return error(&format!("Expected comma, got {:?}",o))} + }; + + let item3 = match parse_ident(&mut iter) { + Ok(i) => i, + Err(e) => { return error(&format!("Expected second ident part, {}",e))} + }; + match iter.next() { + None => {} + other => { return error(&format!("Expected end of macro invocation, got {:?}",other));} + } + format!("{PUB} use {ID1}{ID2}::{ID3};", PUB=if is_pub { "pub"} else {""}, ID1=item1, ID2=item2, ID3=item3).parse().unwrap() +} + +///Parses a literal like `"-(void) foo:(int) bar"` into a literal `"foo:"` +/// ``` +/// use procmacro::__parse_declaration_to_sel; +/// __parse_declaration_to_sel!("-(void) foo:(int) bar"); +/// ``` +#[doc(hidden)] +#[proc_macro] +pub fn __parse_declaration_to_sel(stream: TokenStream) -> TokenStream { + let mut iter = stream.into_iter(); + let expr = match parse_literal_string(&mut iter) { + Ok(ParsedLiteral::Literal(l)) => {l} + o => {return error(&format!("Unexpected {:?}",o))} + }; + let selector = declarations::parse_to_selector(&expr); + if selector.is_err() { + return error(&selector.err().unwrap()); + } + let fmt = format!(r#""{}""#,selector.unwrap()); + fmt.parse().unwrap() +} \ No newline at end of file diff --git a/objr-upstream/procmacro/src/misc.rs b/objr-upstream/procmacro/src/misc.rs new file mode 100644 index 0000000..fb8f767 --- /dev/null +++ b/objr-upstream/procmacro/src/misc.rs @@ -0,0 +1,120 @@ +//! Misc helper functions +use proc_macro::{TokenTree, TokenStream}; +///Returns an error +pub fn error(error: &str) -> TokenStream { + //For whatever reason we can't use `compile_error!` with a quote + let safe_str = error.replace('"', "\\\""); + format!("compile_error!(\"{}\")",safe_str).parse().unwrap() +} + +///In some cases, procmacros may be given a type in a "group" wrapper (with a single child). +/// This appears to be the case when they are invoked by another macro. +/// +/// I do not know why. +fn unbox_group(tree: TokenTree) -> TokenTree { + match &tree { + TokenTree::Group(g) => { + let mut iter = g.stream().into_iter(); + let unboxed = match iter.next() { + Some(u) => u, + None => TokenTree::Group(g.to_owned()) + }; + //check if this is a single child or not + match iter.next() { + //additional child: do not unbox and return the original tree + Some(_) => TokenTree::Group(g.to_owned()), + //no additional child, unbox + None => unboxed + } + } + other => other.to_owned() + } +} + +#[derive(Debug)] +pub enum ParsedLiteral { + RawLiteral(String), + Literal(String) +} +impl ParsedLiteral { + pub fn unwrap_literal(self) -> String { + match self { + ParsedLiteral::Literal(l) => l, + ParsedLiteral::RawLiteral(_) => panic!("Can't use a raw literal") + } + } +} + +///Parses the a literal string, unboxing from a group if needed. +/// +/// If no literal can be parsed, returns `Err` +pub fn parse_literal_string>(iterator: &mut I) -> Result { + let next = match iterator.next() { + Some(u) => u, + None => { return Err("Nothing found.".to_string())} + }; + let unboxed_next = unbox_group(next); + match unboxed_next { + TokenTree::Literal(s) if s.to_string().starts_with('"') => { + + let mut parsed_string = s.to_string(); + parsed_string.remove(parsed_string.len()-1); + parsed_string.remove(0); + + Ok( + ParsedLiteral::Literal(parsed_string) + ) + }, + //parse raw strings like r#"test"# + TokenTree::Literal(s) if s.to_string().starts_with("r#\"") => { + //watch out for indexing in this one + let mut parsed_string = s.to_string(); + //remove 2 from the tail `"#` + parsed_string.remove(parsed_string.len()-1); + parsed_string.remove(parsed_string.len()-1); + + //remove 3 chars from the head `r#"` + parsed_string.remove(0); + parsed_string.remove(0); + parsed_string.remove(0); + Ok(ParsedLiteral::RawLiteral(parsed_string)) + } + other => { + Err(format!("unexpected {:?}",other)) + } + } +} + +///Parses identifier, unboxing from a group if needed. +/// +/// If no literal can be parsed, returns `Err` +pub fn parse_ident>(iterator: &mut I) -> Result { + let next = match iterator.next() { + Some(u) => u, + None => { return Err("Nothing found.".to_string()) } + }; + let unboxed_next = unbox_group(next); + match unboxed_next { + TokenTree::Ident(s) => { Ok(s.to_string()) } + other => { + Err(format!("unexpected {:?}", other)) + } + } +} + +///Tries to parse as an ident or a string +pub fn parse_ident_or_literal + Clone>(iterator: &mut I) -> Result { + let iterator_backup = iterator.clone(); + match parse_literal_string(iterator) { + Ok(ParsedLiteral::Literal(l)) => {Ok(l)} + _ => { + //retry as ident + *iterator = iterator_backup; + match parse_ident(iterator) { + Ok(ident) => {Ok(ident)} + Err(e) => {Err(e)} + } + } + } +} + diff --git a/objr-upstream/procmacro/src/selectors.rs b/objr-upstream/procmacro/src/selectors.rs new file mode 100644 index 0000000..dec711c --- /dev/null +++ b/objr-upstream/procmacro/src/selectors.rs @@ -0,0 +1,69 @@ +//! Selector helper functions +extern crate proc_macro; + +///An expression for a `Sel` with a dyld-time static +pub fn sel_expression(selector: &str) -> String { + format!( + r#" + {{ + #[inline(never)] unsafe fn codegen_workaround() -> ::objr::bindings::Sel {{ + #[link_section = "__TEXT,__objc_methname,cstring_literals"] + static L_OBJC_METH_VAR_NAME_: [u8; {len}] = *b"{selector}\0"; + + #[link_section = "__DATA,__objc_selrefs,literal_pointers,no_dead_strip"] + static L_OBJC_SELECTOR_REFERENCES_: &'static [u8; {len}] = &L_OBJC_METH_VAR_NAME_; + //don't let the optimizer look at the value we just set, since it will be fixedup by dyld + let read_volatile: &'static [u8; {len}] = ::core::ptr::read_volatile(&L_OBJC_SELECTOR_REFERENCES_ ); + ::objr::bindings::Sel::from_ptr( unsafe{{ std::mem::transmute(read_volatile) }} ) + }} + codegen_workaround() + }}"# + ,selector=selector,len=selector.len() + 1) +} + +///Declares a "partial" fn like `unsafe fn my_selector() -> ::objr::bindings::Sel` with no trailing `;` +pub fn make_fn_partial(fn_name: &str) -> String { + format!("unsafe fn {fn_name}() -> ::objr::bindings::Sel",fn_name=fn_name) +} + + +///Finds an appropriate rust name for a given selector +pub fn sel_to_rust_name(selector: &str) -> String { + let mut rust_build = String::new(); + let mut seen_colon_count: u8 = 0; + for char in selector.chars() { + match char { + ':' => { + //generally we replace `:` with `_` for rust + rust_build.push('_'); + seen_colon_count+=1; + } + other => { rust_build.push(other);} + } + } + /*In objc, we can have these selectors + * `height` => `fn height()` + * `height:` (with an argument) => `fn height_(arg: Type)`. Note that in Rust we need a distinct name `height_` to avoid + conflict with `height` + * `height:width:` => fn height_width(arg: Type, arg2: Type)`. No trailing underscore required here + + This selector is not legal: + x `height:width`. Since this is illegal, the name `height_width` is not 'reserved' for it, and can be used for `height:width:` instead. + + Shorter version, if our colon count is >1 we can remove the trailing `_`. + */ + if seen_colon_count > 1 { + rust_build.pop(); + } + rust_build +} + + + + +#[test] +fn build_selector() { + assert_eq!(sel_to_rust_name("height"), "height"); + assert_eq!(sel_to_rust_name("height:"), "height_"); + assert_eq!(sel_to_rust_name("height:width:"), "height_width"); +} diff --git a/objr-upstream/procmacro/src/strings.rs b/objr-upstream/procmacro/src/strings.rs new file mode 100644 index 0000000..4492082 --- /dev/null +++ b/objr-upstream/procmacro/src/strings.rs @@ -0,0 +1,63 @@ +///Emits the code we need for a static string expression. +pub fn static_string(string_literal: &str) -> String { + + format!(r#" + {{ + #[inline(never)] fn codegen_workaround() -> &'static objr::foundation::NSString {{ + /* + Pretty much we want to emit some assembly like + .section __TEXT,__cstring,cstring_literals + L_.str: ## @.str + .asciz "find this static content" + + .section __DATA,__cfstring + .p2align 3 ## @_unnamed_cfstring_ + L__unnamed_cfstring_: + .quad ___CFConstantStringClassReference + .long 1992 ## 0x7c8 + .space 4 + .quad L_.str + .quad 24 + + Specific lines are referenced in code below. + + Note that for whatever reason, rust really wants to emit .asciz directives, + but the memory layout "should" be the same... + */ + #[link_section = "__TEXT,__cstring,cstring_literals"] + static STRING_LITERAL: [u8; {LITERAL_LENGTH}] = *b"{STRING_LITERAL}\0"; + #[link(name="CoreFoundation",kind="framework")] + extern {{ + #[link_name = "\x01___CFConstantStringClassReference"] + static CFCONSTANT_STRING_CLASS_REFERENCE : &'static core::ffi::c_void; + }} + + //Some kind of magic structure that can be casted to CFString directly + //.p2align 3 + #[repr(C,packed(8))] + struct CFStringStatic {{ + //.quad ___CFConstantStringClassReference + constant_string_class_reference: &'static &'static core::ffi::c_void, + //.long 1992 + magic: u32, + // .space 4 + space: [u8; 4], + //.quad L_.str + str: &'static [u8; {LITERAL_LENGTH}], + //.quad [len] + magic_2: usize + }} + #[link_section = "__DATA,__cfstring"] + static CFSTRING_REF: CFStringStatic = CFStringStatic {{ + constant_string_class_reference: unsafe {{ &CFCONSTANT_STRING_CLASS_REFERENCE }}, + magic: 1992, + space: [0; 4], + str: &STRING_LITERAL, + magic_2: {LITERAL_LENGTH_MINUS_ONE} + }}; + unsafe{{ &*(&CFSTRING_REF as *const _ as *const objr::foundation::NSString) }} + }} + codegen_workaround() + }} + "#,STRING_LITERAL=string_literal,LITERAL_LENGTH=string_literal.len() + 1,LITERAL_LENGTH_MINUS_ONE=string_literal.len()) +} \ No newline at end of file diff --git a/objr-upstream/src/arguments.rs b/objr-upstream/src/arguments.rs new file mode 100644 index 0000000..55f6ed4 --- /dev/null +++ b/objr-upstream/src/arguments.rs @@ -0,0 +1,295 @@ +///!Rust doesn't natively support varargs, so encoding the args +///!into an "anonymous" type that implements this trait is a convenient +///! way to pass the objcargs to functions. + +use super::bindings::*; +use std::ffi::c_void; +use std::fmt::Debug; + +#[link(name="objc", kind="dylib")] +extern "C" { + fn objc_msgSend(); + //Undocumented, but part of ABI. This call goes directly to super. Do not pass go, do not try `self`. + fn objc_msgSendSuper2(); +} + +//defined in https://opensource.apple.com/source/objc4/objc4-371.2/runtime/message.h +//This is the first argument to `objc_msgSendSuper2` instead of the receiver +#[repr(C)] +struct ObjcSuper { + receiver: *mut c_void, + /* Although the "documentation" says that "super_class is the first class to search" + in fact when calling `objc_msgSendSuper2` you want to pass the class of the receiver here + (e.g, not the class to search). + + This is probably a quirk of objc_msgSendSuper2. + */ + class: *const AnyClass, +} + +///Trait describing a type that can be used as arugments. Generally, this is a tuple of all the arguments to some method. +/// +/// This type is sealed; you may not implement it from outside the crate. +/// All implementations are provided via macro. +pub trait Arguments: Sized + Debug + crate::private::Sealed { + ///Implementation deatil of [PerformsSelector::perform_primitive] + unsafe fn invoke_primitive(receiver: *mut c_void, sel: Sel, pool: &ActiveAutoreleasePool, args: Self) -> R; + ///Implementation detail of [PerformsSelectorSuper::perform_super_primitive] + unsafe fn invoke_primitive_super(obj: *mut c_void, sel: Sel, _pool: &ActiveAutoreleasePool, class: *const AnyClass, args: Self) -> R; + ///Implementation detail of [PerformsSelector::perform] + unsafe fn invoke(receiver: *mut c_void, sel: Sel, pool: &ActiveAutoreleasePool, args: Self) -> *const R; + ///Implementation detail of [PerformsSelectorSuper::perform_super] + unsafe fn invoke_super(receiver: *mut c_void, sel: Sel, pool: &ActiveAutoreleasePool, class: *const AnyClass,args: Self) -> *const R; + ///Implementation detail of [PerformsSelector::perform_result] + unsafe fn invoke_error<'a, R: ObjcInstance>(receiver: *mut c_void, sel: Sel, pool: &'a ActiveAutoreleasePool, args: Self) -> Result<*const R, AutoreleasedCell<'a, NSError>>; + ///Implementation detail of [PerformablePointer::perform_result_autorelease_to_retain] + unsafe fn invoke_error_trampoline_strong<'a, R: ObjcInstance>(obj: *mut c_void, sel: Sel, _pool: &'a ActiveAutoreleasePool, args: Self) -> Result<*const R,AutoreleasedCell<'a, NSError>>; + ///Implementation detail of [PerformsSelectorSuper::perform_super_result_autorelease_to_retain] + unsafe fn invoke_error_trampoline_strong_super<'a, R: ObjcInstance>(obj: *mut c_void, sel: Sel, _pool: &'a ActiveAutoreleasePool, class: *const AnyClass, args: Self) -> Result<*const R,AutoreleasedCell<'a, NSError>>; + ///Implementation detail of [PerformsSelectorSuper::perform_super_autorelease_to_retain] + unsafe fn invoke_error_trampoline_super<'a, R: ObjcInstance>(receiver: *mut c_void, sel: Sel, pool: &'a ActiveAutoreleasePool, class: *const AnyClass, args: Self) -> Result<*const R, AutoreleasedCell<'a, NSError>>; +} + +///Can be used as an argument in objr +/// +/// This constraint provides additional safety around transmuting fp types. +/// +/// # Safety +/// The primary constraint of this protocol is it needs to be FFI-safe (`#[repr(transparent)]` or `#[repr(C)]`). +/// Since this cannot be otherwise verified, we're going to declare it `unsafe`. +/// # See also +/// [Primitive], which implies this trait. The difference is that [Arguable] does not allow the [PerformsSelector::perform_primitive()] +/// family in its return type. +pub unsafe trait Arguable {} + +unsafe impl Arguable for &O {} +unsafe impl Arguable for *const O {} + + +///Non-reference types that are ObjC FFI-safe. This marker +/// allows access to the [PerformsSelector::perform_primitive()] family. +/// +/// # Safety +/// Type must be FFI-safe. +/// +/// # Note +/// This is unsealed because we want to allow structs to be declared as primitives in external crates. +/// +/// # See also +/// [Arguable], which is implied by this trait. The difference is that [Primitive] allows [PerformsSelector::perform_primitive()] +/// family in its return type. +pub unsafe trait Primitive: Arguable {} + + +//This is safe because these are all ffi-safe. +unsafe impl Primitive for Sel {} +unsafe impl Arguable for Sel {} + +unsafe impl Primitive for bool{} +unsafe impl Arguable for bool{} + +unsafe impl Primitive for *mut c_void {} +unsafe impl Arguable for *mut c_void {} + +unsafe impl Primitive for *const c_void {} +unsafe impl Arguable for *const c_void {} + +unsafe impl Primitive for f64 {} +unsafe impl Arguable for f64 {} + +unsafe impl Primitive for () {} +unsafe impl Arguable for () {} + +unsafe impl Primitive for u64{} +unsafe impl Arguable for u64{} +unsafe impl Primitive for u32{} +unsafe impl Arguable for u32{} +unsafe impl Primitive for u16{} +unsafe impl Arguable for u16{} +unsafe impl Primitive for u8{} +unsafe impl Arguable for u8{} + + +unsafe impl Primitive for *const u8 {} +unsafe impl Arguable for *const u8 {} +unsafe impl Primitive for *mut u8 {} +unsafe impl Arguable for *mut u8 {} + +unsafe impl Primitive for *const i8 {} +unsafe impl Arguable for *const i8 {} +unsafe impl Primitive for *mut i8 {} +unsafe impl Arguable for *mut i8 {} + +unsafe impl Arguable for i64 {} +unsafe impl Primitive for i64 {} +unsafe impl Arguable for i32 {} +unsafe impl Primitive for i32 {} +unsafe impl Arguable for i16 {} +unsafe impl Primitive for i16 {} +unsafe impl Arguable for i8 {} +unsafe impl Primitive for i8 {} + +///Implementation macro for declaring [Argument] types. +macro_rules! arguments_impl { + ( + $($identifier:ident : $type:ident),* + ) => ( + //seal the type + impl<$($type:Arguable),*> crate::objr::private::Sealed for ($($type,)*) where $($type: Debug),* {} + impl<$($type:Arguable),*> Arguments for ($($type,)*) where $($type: Debug),* { + #[inline] unsafe fn invoke_primitive(obj: *mut c_void, sel: Sel, _pool: &ActiveAutoreleasePool, ($($identifier,)*): Self) -> R { + //autoreleasepool is encouraged by signature but not used + let impcast = objc_msgSend as unsafe extern fn(); + let imp: unsafe extern fn(*mut c_void, Sel $(, $type)*) -> R = + std::mem::transmute(impcast); + imp(obj, sel $(, $identifier)*) + } + #[inline] unsafe fn invoke_primitive_super(obj: *mut c_void, sel: Sel, _pool: &ActiveAutoreleasePool, class: *const AnyClass, ($($identifier,)*): Self) -> R { + let objc_super = ObjcSuper { + receiver: obj, + class: class + }; + let impcast = objc_msgSendSuper2 as unsafe extern fn(); + let imp: unsafe extern fn(*const ObjcSuper, Sel $(, $type)*) -> R = + std::mem::transmute(impcast); + imp(&objc_super, sel $(, $identifier)*) + } + #[inline] unsafe fn invoke(obj: *mut c_void, sel: Sel, _pool: &ActiveAutoreleasePool, ($($identifier,)*): Self) -> *const R { + //autoreleasepool is encouraged by signature but not used + let impcast = objc_msgSend as unsafe extern fn(); + let imp: unsafe extern fn(*mut c_void, Sel $(, $type)*) -> *mut c_void = + std::mem::transmute(impcast); + let ptr = imp(obj, sel $(, $identifier)*); + ptr as *const R + } + #[inline] unsafe fn invoke_super(obj: *mut c_void, sel: Sel, _pool: &ActiveAutoreleasePool,class: *const AnyClass, ($($identifier,)*): Self) -> *const R { + let objc_super = ObjcSuper { + receiver: obj, + class: class + }; + let impcast = objc_msgSendSuper2 as unsafe extern fn(); + let imp: unsafe extern "C" fn(*const ObjcSuper, Sel $(, $type)*) -> *mut c_void = + std::mem::transmute(impcast); + let ptr = imp(&objc_super, sel $(, $identifier)*); + ptr as *const R + } + + ///This function combines various common behaviors in a fast implementation. + /// In particular I want to make sure we generate the right machinecode for `objc_retainAutoreleasedReturnValue` + /// + /// 1. Invoke / performSelector + /// 2. Assumes trailing error parameter + /// 3. Caller wants +1 / StrongCell, but callee returns +0 / autoreleased. Resolved via the magic trampoline `objc_retainAutoreleasedReturnValue`. + /// + #[inline] unsafe fn invoke_error_trampoline_strong<'a, R: ObjcInstance>(obj: *mut c_void, sel: Sel, pool: &'a ActiveAutoreleasePool, ($($identifier,)*): Self) -> Result<*const R,AutoreleasedCell<'a, NSError>> { + use crate::performselector::objc_retainAutoreleasedReturnValue; + let impcast = objc_msgSend as unsafe extern fn(); + let mut error: *const NSError = std::ptr::null(); + let imp: unsafe extern fn(*mut c_void, Sel, $( $type, )* &mut *const NSError) -> *const R = std::mem::transmute(impcast); + let ptr = imp(obj,sel, $($identifier,)* &mut error ); + //ok to call this with nil + objc_retainAutoreleasedReturnValue(ptr as *const c_void); + if ptr != std::ptr::null_mut() { + Ok(ptr) + } + else { + //I'm pretty sure it's street-legal to assume this + //although if it's not, don't sue me + Err(NSError::assume_nonnil(error).assume_autoreleased(pool)) + } + } + #[inline] unsafe fn invoke_error<'a, R: ObjcInstance>(receiver: *mut c_void, sel: Sel, pool: &'a ActiveAutoreleasePool, ($($identifier,)*): Self) -> Result<*const R, AutoreleasedCell<'a, NSError>> { + let impcast = objc_msgSend as unsafe extern fn(); + let mut error: *const NSError = std::ptr::null(); + let imp: unsafe extern fn(*mut c_void, Sel, $( $type, )* &mut *const NSError) -> *const R = std::mem::transmute(impcast); + let ptr = imp(receiver,sel, $($identifier,)* &mut error ); + if ptr != std::ptr::null_mut() { + Ok(ptr) + } + else { + //I'm pretty sure it's street-legal to assume this + //although if it's not, don't sue me + Err(NSError::assume_nonnil(error).assume_autoreleased(pool)) + } + } + + #[inline] unsafe fn invoke_error_trampoline_strong_super<'a, R: ObjcInstance>(obj: *mut c_void, sel: Sel, pool: &'a ActiveAutoreleasePool, class: *const AnyClass, ($($identifier,)*): Self) -> Result<*const R,AutoreleasedCell<'a, NSError>> { + let objc_super = ObjcSuper { + receiver: obj, + class: class + }; + use crate::performselector::objc_retainAutoreleasedReturnValue; + let impcast = objc_msgSendSuper2 as unsafe extern fn(); + let mut error: *const NSError = std::ptr::null(); + let imp: unsafe extern fn(*const ObjcSuper, Sel, $( $type, )* &mut *const NSError) -> *const R = std::mem::transmute(impcast); + let ptr = imp(&objc_super,sel, $($identifier,)* &mut error ); + //ok to call this with nil + objc_retainAutoreleasedReturnValue(ptr as *const c_void); + if ptr != std::ptr::null_mut() { + Ok(ptr) + } + else { + //I'm pretty sure it's street-legal to assume this + //although if it's not, don't sue me + Err(NSError::assume_nonnil(error).assume_autoreleased(pool)) + } + + } + #[inline] unsafe fn invoke_error_trampoline_super<'a, R: ObjcInstance>(receiver: *mut c_void, sel: Sel, pool: &'a ActiveAutoreleasePool, class: *const AnyClass, ($($identifier,)*): Self) -> Result<*const R, AutoreleasedCell<'a, NSError>> { + let objc_super = ObjcSuper { + receiver: receiver, + class: class + }; + let impcast = objc_msgSendSuper2 as unsafe extern fn(); + let mut error: *const NSError = std::ptr::null(); + let imp: unsafe extern fn(*const ObjcSuper, Sel, $( $type, )* &mut *const NSError) -> *const R = std::mem::transmute(impcast); + let ptr = imp(&objc_super,sel, $($identifier,)* &mut error ); + if ptr != std::ptr::null_mut() { + Ok(ptr) + } + else { + //I'm pretty sure it's street-legal to assume this + //although if it's not, don't sue me + Err(NSError::assume_nonnil(error).assume_autoreleased(pool)) + } + } + + } + + ); +} + +//4 arguments shoudl be enough for everybody +arguments_impl!(); +arguments_impl!(a: A); +arguments_impl!(a: A, b: B); +arguments_impl!(a: A, b: B, c: C); +arguments_impl!(a: A, b: B, c: C, d: D); +arguments_impl!(a: A, b: B, c: C, d: D, e: E); +arguments_impl!(a: A, b: B, c: C, d: D, e: E, f: F); +arguments_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G); +arguments_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H); +arguments_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I); + +#[test] +fn perform_super() { + use objr::bindings::*; + + //We need an arbitrary subclass for this test + objc_class! { + pub struct NSNull { + @class(NSNull) + } + } + let pool = unsafe{ AutoreleasePool::new() }; + + let o = NSNull::class().alloc_init(&pool); + + let args = (); + //perform "super" description + let d: *const NSString = unsafe{ <()>::invoke_super(&o as &NSNull as *const NSNull as *mut NSNull as *mut c_void, Sel::description(), &pool, NSNull::class().as_anyclass(), args) }; + let g: &NSString = unsafe{ &*d}; + let super_description = g.to_str(&pool); + assert!(super_description.starts_with(" *const c_void; + pub fn objc_autoreleasePoolPop(ptr: *const c_void); +} + +///Marker type that indicates you have an active autorelease pool. +/// +/// This type is generally appropriate for passing around as an argument. In practice, it is zero-sized, +/// so it should be the zero-cost abstraction. +/// +/// Generally, you work with borrows of this type. The lifetime of the borrow +/// is the lifetime that the autoreleasepool is statically guaranteed to be active. This lets +/// you check autorelease behavior statically. +/// +/// There are two ways to construct this type: +/// 1. by dereferencing an [AutoreleasePool] (preferred) +///2. [ActiveAutoreleasePool::assume_autoreleasepool()]. +#[derive(Debug)] +pub struct ActiveAutoreleasePool { + ///don't allow anyone else to construct this + /// !Send !Sync + _marker: PhantomData<*const ()> +} + +impl ActiveAutoreleasePool { + ///This function makes the [ActiveAutoreleasePool] marker type guaranteeing we have an autoreleasepool + /// active on the thread. + /// + /// # Safety + /// This is generally unsafe, but if you are certain an autoreleasepool is active on the thread, + /// you can use this constructor to create your own marker tpe. + pub const unsafe fn assume_autoreleasepool() -> ActiveAutoreleasePool { + ActiveAutoreleasePool {_marker: PhantomData } + } +} +///Tracks an active autoreleasepool. +/// +/// This is generally used at the "top level" to create a new pool, for a +/// type to use as an argument instead, see [ActiveAutoreleasePool]. +/// +/// This type can be dereferenced into [ActiveAutoreleasePool]. +/// +/// Pops the pool on drop. +#[derive(Debug)] +pub struct AutoreleasePool { + // !Send, !Sync + ptr: *const c_void, + pool: ActiveAutoreleasePool, +} + +impl Deref for AutoreleasePool { + type Target = ActiveAutoreleasePool; + + fn deref(&self) -> &Self::Target { + &self.pool + } +} + +///Pops the pool +impl Drop for AutoreleasePool { + fn drop(&mut self) { + unsafe{ objc_autoreleasePoolPop(self.ptr) } + } +} + +pub fn autoreleasepool R,R>(f: F) -> R { + let a = unsafe{ AutoreleasePool::new() }; + f(&a) +} + +impl AutoreleasePool { + ///Creates a new pool. The pool will be dropped when this type is dropped. + /// + /// # Safety + /// Autorelease pools must be dropped in reverse order to when they are created. If you don't want to maintain + /// this invariant yourself, see the [autoreleasepool] safe wrapper. + pub unsafe fn new() -> Self { + AutoreleasePool { + ptr: objc_autoreleasePoolPush(), + pool: ActiveAutoreleasePool::assume_autoreleasepool() + } + } +} diff --git a/objr-upstream/src/class.rs b/objr-upstream/src/class.rs new file mode 100644 index 0000000..94415a4 --- /dev/null +++ b/objr-upstream/src/class.rs @@ -0,0 +1,251 @@ +///! Implementation of ObjC classes. Classes are distinct from instances (which could be, for example, protocols). +use std::ffi::{c_void, CStr}; +use super::performselector::PerformablePointer; +use super::bindings::*; +use std::os::raw::c_char; +use core::marker::PhantomData; +use std::fmt::Formatter; + +#[link(name="objc", kind="dylib")] +extern "C" { + fn objc_lookUpClass(name: * const c_char) -> *mut c_void; +} + +///Untyped pointer to ObjC class. +/// +/// The actual class type is erased. Any use of this type is likely unsafe. +#[derive(Debug)] +#[repr(transparent)] +pub struct AnyClass(c_void); + +impl PartialEq for AnyClass { + fn eq(&self, other: &Self) -> bool { + (self as *const Self) == (other as *const Self) + } +} + +///A trait for Rust types that map to ObjC classes. +/// +/// This is similar to [ObjcInstance] (and requires it) but imposes additional class requirements. +/// +/// In particular, this rules out the possibility it is a protocol. +/// +/// +/// # Stability +/// It is not stable API to impelment this trait directly. Instead use the [objc_class!] macro. +/// +/// # Safety +/// This is safe because the linker checks that this is a valid class +pub trait ObjcClass: ObjcInstance + Sized { + fn class() -> &'static Class; +} + + + +///Typed pointer to ObjC Class. Analogous to `*const T`, but points to the class, not the instance. +/// +/// Used to call "class methods" like `[alloc]`. +/// +/// To create this type, it's recommended to use `Class::new()`. For more information, see [objc_class!]. +#[repr(transparent)] +#[derive(Debug)] +pub struct Class(c_void, PhantomData); + +///Classes can use performSelector +unsafe impl PerformablePointer for Class {} + +impl PartialEq for Class { + fn eq(&self, other: &Self) -> bool { + //pointer equality + let s = self as *const Self; + let o = other as *const Self; + s == o + } +} + +impl Class { + ///Dynamically creates a Class from some string by querying the ObjC runtime. Note that in most cases, [NSObject::class()] in combination + /// with [objc_class!] macro is a faster implementation because it uses compile-time knowledge. + pub unsafe fn from_str(cstr: &CStr) -> &'static Self { + let dynamic_class = objc_lookUpClass(cstr.as_ptr()); + &*(dynamic_class as *const Self) + } + ///Converts to an anyclass + pub fn as_anyclass(&self) -> &'static AnyClass { + unsafe{ &*(self as *const _ as *const AnyClass) } + } +} + + +impl Class { + ///`[[Class alloc] init]` + /// + pub fn alloc_init(&self, pool: &ActiveAutoreleasePool) -> StrongCell { + unsafe { + //todo: optimize with objc_alloc_init + let mut cell = self.alloc(pool); + T::init(&mut cell, pool); + let immutable = cell as *const T; + T::assume_nonnil(immutable).assume_retained() + } + } + ///`[Class alloc]` + /// + /// # Safety + /// Unsafe because the underlying memory is uninitialized after this call + pub unsafe fn alloc(&self, pool: &ActiveAutoreleasePool) -> *mut T { + Self::perform(self as *const Class as *mut _, Sel::alloc(), pool, ()) as *const T as *mut T + } + + ///See [ObjcInstanceBehavior::assume_nonmut_perform()] + pub unsafe fn assume_nonmut_perform(&self) -> *mut Self { + self as *const Self as *mut Self + } +} + +impl std::fmt::Display for Class { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let r = unsafe { + let pool = ActiveAutoreleasePool::assume_autoreleasepool() ; + let description: *const NSString = Self::perform_autorelease_to_retain(self.assume_nonmut_perform(), Sel::description(), &pool,()); + NSString::assume_nonnil(description).assume_retained() + }; + f.write_fmt(format_args!("{}",r)) + } +} + +///This declares an instance type which is also a class. See [objc_instance!] for a version which is not a class. +/// ``` +/// use objr::bindings::*; +/// objc_class! { +/// //Declare a struct with this name, representing our objc class +/// pub struct Example { +/// @class(NSObject) +/// } +/// } +/// autoreleasepool(|pool| { +/// let instance = Example::class().alloc_init(&pool); +/// let class = Example::class(); +/// }); +/// +/// ``` +/// +/// This version does not support generics, to declare a wrapper type (that can be generic), see [objc_class_newtype!] +#[macro_export] +macro_rules! objc_class { + ( + $(#[$attribute:meta])* + $pub:vis + struct $objctype:ident { + @class($objcname:ident) + } + ) => { + ::objr::bindings::objc_instance! { + $(#[$attribute])* + $pub struct $objctype; + } + ::objr::bindings::__objc_implement_class!{$objctype,$objcname} + }; +} + +/** +Declares a newtype that wraps an existing [objc_class]. + +See also: +* [objc_class]. The oldtype must be declared with this macro. +* [objc_instance_newtype], the equivalent macro for [objc_instance]. + +Downcasts to the raw type will be implemented for you. Upcasts will not, implement them yourself with [objr::bindings::ObjcInstanceBehavior::cast()] if applicable. + +```no_run +use objr::bindings::*; +objc_class! { + struct NSObject { + @class(NSObject) + } +} +objc_class_newtype! { + struct NSSecondObject: NSObject; +} +let s: &NSSecondObject = todo!(); +let e: &NSObject = s.into(); + +let s: &mut NSSecondObject = todo!(); +let e: &mut NSObject = s.into(); +``` + +unlike [objc_class!], this macro supports generic types, allowing you to wrap some other type with generics bolted on top. + +At the moment, restrictions on generic arguments are not supported at the type level, but you can add them on your own impl blocks +``` +use objr::bindings::*; +objc_class! { + struct NSObject { @class(NSObject) } +} +objc_class_newtype! { + struct SecondObject: NSObject; +} +//further restriction +impl SecondObject { } +``` + +Although newtypes declared with this macro conform to ObjcClass, keep in mind that their newtypeness is a Rust construct, +and is not visible to ObjC: + +``` +use objr::bindings::*; +objc_class_newtype! { + struct NotNSObject: NSObject; +} +fn static_assert_isclass(t: &T) {} + +autoreleasepool(|pool| { + //create a plain old NSObject + let oldtype = NSObject::class().alloc_init(pool); + //upgrade it to newtype + let newtype: &NotNSObject = unsafe{ oldtype.cast() }; + //confirm newtype conforms to ObjcClass + static_assert_isclass(newtype); + //however, it isn't a distinct class. It was NSObject the whole time! + assert_eq!(NSObject::class().as_anyclass(),NotNSObject::class().as_anyclass()) +}) +``` +*/ +#[macro_export] +macro_rules! objc_class_newtype { + ( + $(#[$attribute:meta])* + $pub:vis + struct $newtype:ident $(<$($T:ident),+>)? : $oldtype:ident; + ) => { + ::objr::bindings::objc_instance_newtype! { + $(#[$attribute])* + $pub struct $newtype $(<$($T),+>)?: $oldtype; + } + impl $(<$($T),+>)? objr::bindings::ObjcClass for $newtype $(<$($T),+>)? { + fn class() -> &'static Class { + unsafe{ std::mem::transmute($oldtype::class()) } + } + } + } +} + + +#[test] +fn alloc_ns_object() { + use std::ffi::CString; + let class = unsafe { Class::::from_str(CString::new("NSObject").unwrap().as_c_str() ) }; + println!("{}",class); +} +#[test] +fn init_ns_object() { + use crate::autorelease::AutoreleasePool; + let pool = unsafe{ AutoreleasePool::new() }; + let class = NSObject::class(); + let class2 = NSObject::class(); + assert_eq!(class, class2); + let instance = class.alloc_init(&pool); + let description = instance.description(&pool); + assert!(description.to_str(&pool).starts_with("(context: &mut Option) -> *mut c_void { + println!("Thunk_void"); + let f = context.take().unwrap(); + f(); + std::ptr::null_mut() +} +///This function catches an objc exception raised in the closure. +/// +/// Return values are not supported, this is primarily intended to facilitate debugging. +pub fn try_unwrap_void(closure: F){ + println!("Try unwrap void"); + let thunk_fn = thunk_void:: as extern "C" fn(&mut Option) -> *mut c_void; + let mut closure_indirect = Some(closure); + unsafe{ hard_exception(std::mem::transmute(thunk_fn), std::mem::transmute(&mut closure_indirect)) }; +} + + +#[test] fn test_catch() { + try_unwrap_void(|| { + println!("Hello world"); + }) +} + + diff --git a/objr-upstream/src/hard-exception.m b/objr-upstream/src/hard-exception.m new file mode 100644 index 0000000..ddea97e --- /dev/null +++ b/objr-upstream/src/hard-exception.m @@ -0,0 +1,11 @@ +#include +void hard_exception(void (fn)(void *context), void *context) { + NSLog(@"hard_exception"); + @try { + fn(context); + } + @catch (id ex) { + NSLog(@"objc exception: %@",ex); + abort(); + } +} \ No newline at end of file diff --git a/objr-upstream/src/lib.rs b/objr-upstream/src/lib.rs new file mode 100644 index 0000000..85b7f95 --- /dev/null +++ b/objr-upstream/src/lib.rs @@ -0,0 +1,226 @@ +/*! +# Drew's very fast objc library +This library provides low-level bindings to ObjC, which is in practice the ABI for macOS. You might compare +this crate with [objc](https://crates.io/crates/objc), [fruity](https://docs.rs/fruity/0.2.0/fruity/), [objcrs](https://crates.io/crates/objrs) +and many others. + +Distinctive features of this library include: +* Zero-cost abstractions, including the elusive "compile-time selectors" as well as many other new and exciting static technologies +* Advanced performance and codesize optimizations suitable for real-time, game, and graphical applications +* Smart pointers that integrate ObjC memory management into safe Rust abstractions +* Write ObjC subclasses directly in Rust +* Emits code similar to real ObjC compilers, for speed and future-compatibility +* Macro system for productively hand-coding bindings for new ObjC APIs +* Low level, ObjC-like APIs that you can build on to expose a Rusty interface – or not, for extra control and performance +* Minimal API coverage, leaving the question of which APIs to use and how to expose them to Rust for other crates +* Focus on modern, Apple-platform ObjC +* Free for noncommercial use + +# Detailed general examples + +## Using an external class + +```rust +//We intend to write bindings for ObjC APIs +use objr::bindings::*; +objc_class! { + //Rust wrapper type + pub struct NSDate { + //ObjC class name + @class(NSDate) + } +} +autoreleasepool(|pool| { + //In this library, autoreleasepools are often arguments to ObjC-calling APIs, providing static guarantees you created one. + //Forgetting this is a common ObjC bug. + let date = NSDate::class().alloc_init(&pool); + println!("{}",date); // 2021-06-21 19:03:15 +0000 +}) +``` + +Compare this with [[objc_instance!]] for non-class instances. + +## Binding an ObjC API + +```rust +use objr::bindings::*; +objc_class! { + //Rust wrapper type + pub struct NSDate { + @class(NSDate) + } +} +//Declares a group of static selectors. +objc_selector_group! { + pub trait NSDateSelectors { + @selector("dateByAddingTimeInterval:") + } + //Adds support for these selectors to our `Sel` APIs. + impl NSDateSelectors for Sel {} +} + +//Declare APIs directly on our `NSDate` wrapper type +impl NSDate { + fn dateByAddingTimeInterval(&self, pool: &ActiveAutoreleasePool, interval: f64) + //Although the underlying ObjC API returns a +0 unowned reference, + //We create a binding that returns +1 retained instead. We might do this + //because it's the preferred pattern of our application. + //For more details, see the documentation of [objc_instance!] + -> StrongCell { + //Use of ObjC is unsafe. There is no runtime or dynamic checking of your work here, + //so you must provide a safe abstraction to callers (or mark the enclosing function unsafe). + unsafe { + /*Convert from an autoreleased return value to a strong one. + This uses tricks used by real ObjC compilers and is far faster than calling `retain` yourself. + */ + let raw = Self::perform_autorelease_to_retain( + //the objc method we are calling does not mutate the receiver + self.assume_nonmut_perform(), + ///Use the compile-time selector we declared above + Sel::dateByAddingTimeInterval_(), + ///Static checking that we have an autoreleasepool available + pool, + ///Arguments. Note the trailing `,`. Arguments are tuple types. + (interval,)); + //assume the result is nonnil + Self::assume_nonnil(raw) + //assume the object is +1 convention (it is, because we called perform_autorelease_to_retain above) + .assume_retained() + } + } +} +autoreleasepool(|pool| { + //In this library, autoreleasepools are often arguments to ObjC-calling APIs, providing compile-time guarantees you created one. + //Forgetting this is a common ObjC bug. + let date = NSDate::class().alloc_init(&pool); + let new_date = date.dateByAddingTimeInterval(&pool, 23.5); +}) + +``` + +For more examples, see the documentation for [objc_instance!]. + +# Feature index + +* Statically declare [selectors](objc_selector_group!()) and [classes](objc_class!()), [string literals](foundation::objc_nsstring!()), [enums](bindings::objc_enum!()), etc. so they don't have to be looked up at runtime +* Leverage the Rust typesystem to elide `retain`/`release`/`autorelease` calls in many cases. +* Participate in [runtime autorelease eliding](objr::performselector::PerformsSelector::perform_autorelease_to_retain()) which reduces memory overhead when calling system code +This means that for programs that are mostly Rust, codegeneration may be significantly better even than real ObjC programs. +* Pointer packing for `Option<&NSObject>` +* Smart pointer system, with support for [bindings::StrongCell] and [bindings::AutoreleasedCell] +* [Subclassing directly from Rust](objc_subclass!()) +* (limited) support for [mutability and exclusive references](objc_instance!()#Mutability) in imported types + +Not yet implemented, but planned or possible: + +* iOS support +* Exceptions (Debug-quality API available now, see [[bindings::try_unwrap_void]]) + +# Design limitations + +This library intends to follow normal guidelines for safe Rust. However, calling into ObjC means there's +a giant ball of `unsafe` somewhere. + +This library makes the assumption that the underlying ball of ObjC is implemented correctly. In particular, +it avoids runtime checks that ObjC is implemented correctly, so to the extent that it isn't, you may encounter UB. + +For more information, see the safety section of [objc_instance!()#Safety]. + +# objr expanded universe + +objr is part of an expanded universe of related crates that take a similar approach +to binding Apple technologies to Rust. + +* [blocksr](https://github.com/drewcrawford/blocksr) provides Clang/ObjC blocks +* [dispatchr](http://github.com/drewcrawford/dispatchr) binds libdispatch/GCD +*/ +extern crate self as objr; +pub mod macros; +mod class; + +mod objectpointers; + +mod nsobject; +mod nsstring; +mod autorelease; +mod arguments; + +mod performselector; +mod objcinstance; +mod typealias; +mod sel; +mod nserror; +mod subclass; +mod exception; + + +///This prelude provides a "foundation-like" experience. This brings +/// in various foundation types, like NSObject, NSString, etc. +/// +/// In this crate we generally only implement types that are strictly necessary, +/// for other foundation types see other crates. +pub mod foundation { + pub use super::nsstring::NSString; + pub use super::nsobject::NSObject; + pub use super::nsobject::NSObjectTrait; + pub use super::nsobject::NSObjectSelectors; + pub use super::class::ObjcClass; + pub use super::nserror::{NSError}; + pub use procmacro::objc_nsstring; + pub use super::autorelease::autoreleasepool; + pub use super::bindings::ObjcInstanceBehavior; + pub use super::nserror::ResultNSError; + +} + +///This namespace includes items that are appropriate for writing bindings +pub mod bindings { + pub use super::autorelease::{ActiveAutoreleasePool,AutoreleasePool}; + pub use super::objectpointers::{StrongCell,AutoreleasedCell,StrongMutCell,AutoreleasedMutCell,StrongLifetimeCell}; + pub use super::sel::Sel; + pub use super::nsobject::NSObjectTrait; + pub use super::nsobject::NSObject; + pub use super::objcinstance::{ObjcInstance,OptionalInstanceBehavior,NonNullImmutable,NullableBehavior}; + pub use super::performselector::{PerformsSelector,PerformablePointer,PerformsSelectorSuper}; + pub use super::class::{Class}; + pub use super::foundation::*; + pub use objr::objcinstance::NullableCellBehavior; + //import macros + pub use crate::objc_instance; + pub use crate::objc_class; + pub use crate::objc_enum; + pub use crate::objc_selector_group; + pub use crate::objc_subclass; + pub use crate::objc_instance_newtype; + pub use crate::objc_class_newtype; + pub use procmacro::{__objc_implement_class,ObjcInstance,__static_expr,__static_extern,__static_asciiz_ident_as_selector,__static_asciiz_ident_as_type_encoding,__count,__concat_idents,__static_asciiz,__static_expr3}; + pub use super::class::AnyClass; + pub use super::arguments::{Primitive,Arguable}; + pub use super::exception::{try_unwrap_void}; + pub use super::objcinstance::ObjcInstanceBehavior; + + ///Used by macros, not public API + #[doc(hidden)] + pub use super::sel::_SyncWrapper; + + //used by macros + #[doc(hidden)] + pub use procmacro::{_objc_selector_decl,_objc_selector_impl,__use,__mod}; + +} + +mod private { + ///"Sealed trait" pattern. Traits will inherit from this trait to indicate they cannot be implemented outside the crate. + /// + /// See [the documentation](https://rust-lang.github.io/api-guidelines/future-proofing.html) for details. + /// + /// We are free to implement `Sealed` on any type from inside the crate; this is often necessary to implement some other `Trait: Sealed` on the type. + pub trait Sealed {} +} + + + + + + + diff --git a/objr-upstream/src/macros.rs b/objr-upstream/src/macros.rs new file mode 100644 index 0000000..7dac500 --- /dev/null +++ b/objr-upstream/src/macros.rs @@ -0,0 +1,45 @@ +//! Implements a variety of macros for simple objc binding declarations + + + + +///Helps generate bindings for an objc enum, as a struct with const members. +/// +/// # example +/// +/// ``` +///# use objr::bindings::*; +///objc_enum! { +/// pub struct MTLPixelFormat; +/// impl MTLPixelFormat { +/// MTLPixelFormatInvalid = 0 +/// } +/// } +///``` +/// # Notes +/// This macro requires +/// * a struct with a single field +/// * implementation block +/// * value-level macros, like `API_AVAILABLE`, to be removed. If you need to figure out a situation for old OS, do it yourself. +/// You can find and remove such lines with the regex `API_AVAILABLE\(.*\)`. +/// * Certain complex comments need to be removed, although simple block comments appear to work in my testing. +#[macro_export] +macro_rules! objc_enum { + ( + $(#[$attribute:meta])* + $pub:vis struct $enum:ident<$type:ty>; + impl $ignore:ident { + $($a:ident = $b:expr),* + } + ) => ( + $(#[$attribute])* + $pub struct $enum($type); + #[allow(non_upper_case_globals)] + impl $enum { + $($pub const $a: $enum = $enum($b);)* + $pub const fn field(&self) -> $type { self.0 } + } + + ) +} + diff --git a/objr-upstream/src/nserror.rs b/objr-upstream/src/nserror.rs new file mode 100644 index 0000000..23861ec --- /dev/null +++ b/objr-upstream/src/nserror.rs @@ -0,0 +1,35 @@ +//! NSError implementation + +use super::bindings::*; +objc_class! { + pub struct NSError { + @class(NSError) + } +} + + +pub trait ResultNSError { + ///A friendlier unwrap for [NSError] that prints the error if you encounter it. + fn unwrap_nserror(self, pool: &ActiveAutoreleasePool) -> T; +} +impl ResultNSError for Result> { + fn unwrap_nserror(self, pool: &ActiveAutoreleasePool) -> T { + match self { + Ok(t) => { t} + Err(e) => { + panic!("{}",e.description(pool)) + } + } + } +} + +impl ResultNSError for Result> { + fn unwrap_nserror(self, pool: &ActiveAutoreleasePool) -> T { + match self { + Ok(t) => { t} + Err(e) => { + panic!("{}",e.description(pool)) + } + } + } +} \ No newline at end of file diff --git a/objr-upstream/src/nsobject.rs b/objr-upstream/src/nsobject.rs new file mode 100644 index 0000000..85737dd --- /dev/null +++ b/objr-upstream/src/nsobject.rs @@ -0,0 +1,80 @@ +//! Bindings for NSObject +//! +use objr::bindings::{ActiveAutoreleasePool,Sel}; + +use super::nsstring::NSString; + + +use super::objcinstance::ObjcInstance; +use super::performselector::PerformsSelector; +use super::bindings::*; + + +extern {} +objc_selector_group!( + pub trait NSObjectSelectors { + @selector("alloc") + @selector("description") + @selector("respondsToSelector:") + @selector("init") + @selector("conformsToProtocol:") + @selector("dealloc") + @selector("copy") + } + impl NSObjectSelectors for Sel {} + ); + +///Trait for NSObject. This will be autoimplemented by all [ObjcInstance]. +/// +/// This type provides bindings to common `NSObject` functions. +pub trait NSObjectTrait: Sized + ObjcInstance { + fn description<'a>(&self, pool: &ActiveAutoreleasePool) -> StrongCell; + //objc_method_declaration!{autoreleased fn description() -> NSString; } + fn responds_to_selector(&self, pool: &ActiveAutoreleasePool, sel: Sel) -> bool; + + fn copy(&self, pool: &ActiveAutoreleasePool) -> StrongCell; + + ///Calls `[instance init]`.; + unsafe fn init(receiver: *mut *mut Self, pool: &ActiveAutoreleasePool); + ///erases type to NSObject + fn as_nsobject(&self) -> &NSObject; +} +//"description" will not work unless CoreFoundation is linked +impl NSObjectTrait for T { + fn description<'a>(&self, pool: &ActiveAutoreleasePool) -> StrongCell { + unsafe { + let raw = Self::perform_autorelease_to_retain(self.assume_nonmut_perform(), Sel::description(), pool, ((),)); + NSString::assume_nonnil(raw).assume_retained() + } + } + fn responds_to_selector(&self, pool: &ActiveAutoreleasePool, sel: Sel) -> bool { + unsafe { + Self::perform_primitive(self.assume_nonmut_perform(), Sel::respondsToSelector_(), pool, (sel,)) + } + } + fn copy(&self, pool: &ActiveAutoreleasePool) -> StrongCell { + unsafe { + let r = Self::perform(self.assume_nonmut_perform(), Sel::copy(), pool, ()); + Self::assume_nonnil(r).assume_retained() + } + } + ///Initializes the object by calling `[self init]` + /// + ///By objc convention, `init` may return a distinct pointer than the one that's passed in. + /// For this reason, a mutable reference is required. + unsafe fn init(receiver: *mut *mut Self, pool: &ActiveAutoreleasePool) { + //init can return a distinct pointer + //upcast return type to mutable since it matches the argument + let ptr = (Self::perform(*receiver,Sel::init(), pool, ())) as *const T as *mut T; + *receiver = ptr; + } + + fn as_nsobject(&self) -> &NSObject { + unsafe {&* (self as *const Self as *const NSObject)} + } +} +objc_class! { + pub struct NSObject { + @class(NSObject) + } +} diff --git a/objr-upstream/src/nsstring.rs b/objr-upstream/src/nsstring.rs new file mode 100644 index 0000000..9186ec3 --- /dev/null +++ b/objr-upstream/src/nsstring.rs @@ -0,0 +1,111 @@ +//! Provides NSString +//! +use super::bindings::*; +use std::ffi::{CStr}; +use std::hash::{Hash, Hasher}; +use std::os::raw::{c_char}; +use crate::objcinstance::NonNullImmutable; +use objr::typealias::NSUInteger; + +objc_class! { + pub struct NSString { + @class (NSString) + } +} + +objc_selector_group!( + pub trait NSStringSelectors { + @selector("UTF8String") + @selector("initWithBytes:length:encoding:") + @selector("isEqualToString:") + @selector("hash") + } + impl NSStringSelectors for Sel {} +); + +#[allow(non_upper_case_globals)] +const NSUTF8StringEncoding: NSUInteger = 4; + + +impl PartialEq for NSString { + fn eq(&self, other: &Self) -> bool { + unsafe { + //I am reasonably confident this doesn't allocate + let pool = ActiveAutoreleasePool::assume_autoreleasepool(); + NSString::perform_primitive(self.assume_nonmut_perform(), Sel::isEqualToString_(),&pool, (other,) ) + } + } +} +impl Eq for NSString {} +impl Hash for NSString { + fn hash(&self, state: &mut H) { + unsafe { + todo!(); + } + } +} + +impl NSString { + ///Converts to a stringslice + pub fn to_str(&self, pool: &ActiveAutoreleasePool) -> &str { + unsafe { + let str_pointer: *const c_char = Self::perform_primitive(self.assume_nonmut_perform(), Sel::UTF8String(), pool, ()); + //todo: using utf8 directly might be faster as this involves an up-front strlen in practice + let msg = CStr::from_ptr(str_pointer); + msg.to_str().unwrap() + } + } + ///Copies the string into foundation storage + pub fn with_str_copy(str: &str, pool: &ActiveAutoreleasePool) -> StrongCell { + unsafe { + let instance = Self::class().alloc(pool); + let bytes = str.as_bytes().as_ptr(); + let len = str.as_bytes().len() as NSUInteger; + + let instance: *const NSString = Self::perform(instance,Sel::initWithBytes_length_encoding(),pool, (bytes,len,NSUTF8StringEncoding)); + //although this method is technically nullable, the fact that the string is already statically known to be utf8 + //suggests we should be fine + NonNullImmutable::assume_nonnil(instance).assume_retained() + } + } +} + + + +#[test] fn from_str() { + use crate::autorelease::AutoreleasePool; + let example = "example string here"; + let pool = unsafe{ AutoreleasePool::new() }; + let nsstring = NSString::with_str_copy(example, &pool); + assert_eq!(nsstring.to_str(&pool), example); +} + +#[test] fn static_str() { + use crate::autorelease::AutoreleasePool; + let pool = unsafe{ AutoreleasePool::new() }; + + let test = objc_nsstring!("My example literal"); + let description = test.description(&pool); + assert_eq!(description.to_str(&pool), "My example literal"); +} + +#[test] fn hash_str() { + use std::collections::hash_map::DefaultHasher; + + autoreleasepool(|pool| { + let s1 = objc_nsstring!("example string goes here"); + let s2 = NSString::with_str_copy("example string goes here",pool); + let s2_p: &NSString = &s2; + assert_eq!(s1,s2_p); + + let mut hashstate = DefaultHasher::new(); + let mut hashstate2 = DefaultHasher::new(); + assert_eq!(s1.hash(&mut hashstate),s2_p.hash(&mut hashstate2)); + + fn assert_cell(_h: &H) {} + assert_cell(s1); + assert_cell(&s2); + assert_cell(s2_p); + }); + +} \ No newline at end of file diff --git a/objr-upstream/src/objcinstance.rs b/objr-upstream/src/objcinstance.rs new file mode 100644 index 0000000..f876cd5 --- /dev/null +++ b/objr-upstream/src/objcinstance.rs @@ -0,0 +1,535 @@ +use std::ptr::NonNull; +use crate::bindings::{StrongCell, AutoreleasedCell, StrongLifetimeCell, StrongMutCell}; +use crate::autorelease::ActiveAutoreleasePool; + +///Marks that a given type is an objc type, e.g. its instances are an objc object. +///This is the case for classes, but also for protocols. +/// +/// # Stability +/// It is not stable API to implement this trait yourself. Instead, declare a conforming +/// type via [objc_instance!] macro. +/// +pub trait ObjcInstance {} + + +///A nonnull, but immutable type. This allows various optimizations like pointer-packing `Option`. +/// +#[repr(transparent)] +#[derive(Debug)] +pub struct NonNullImmutable(NonNull); + +impl NonNullImmutable { + pub(crate) fn from_reference(ptr: &T) -> Self { + unsafe{ NonNullImmutable::assume_nonnil(ptr) } + } + ///Assumes the object has been retained and converts to a StrongCell. + /// + /// # Safety + /// You must guarantee each of the following: + /// * Object was retained (+1) + /// * Object is not deallocated + /// * Object was initialized + /// * Object is 'static, that is, it has no references to external (Rust) memory. + /// If this is not the case, see [NonNullImmutable::assume_retained_limited]. + pub unsafe fn assume_retained(self) -> StrongCell { + StrongCell::assume_retained(self.0.as_ref()) + } + + ///Assumes the object has been retained and converts to a StrongLifetimeCell. + /// + /// # Safety + /// You must guarantee each of the following: + /// * Object was retained (+1) + /// * Object is not deallocated + /// * Object was initialized + /// * That the object can remain valid for the lifetime specified. e.g., all "inner pointers" or "borrowed data" involved + /// in this object will remain valid for the lifetime specified, which is unbounded. + /// * That all objc APIs which end up seeing this instance will either only access it for the lifetime specified, + /// or will take some other step (usually, copying) the object into a longer lifetime. + pub unsafe fn assume_retained_limited<'a>(self) -> StrongLifetimeCell<'a, T> where T: 'a { + StrongLifetimeCell::assume_retained_limited(self.0.as_ref()) + } + ///Assumes the object has been autoreleased and converts to an AutoreleasedCell. + /// + /// # Safety: + /// You must guarantee each of the following: + /// * Object is autoreleased already + /// * Object is not deallocated + /// * Object was initialized + pub unsafe fn assume_autoreleased<'a>(self, pool: &'a ActiveAutoreleasePool) -> AutoreleasedCell<'a, T> { + AutoreleasedCell::assume_autoreleased(self.as_ref(), pool) + } + ///Converts to a raw pointer + pub(crate) fn as_ptr(&self) -> *const T { + self.0.as_ptr() + } + ///Assumes the passed pointer is non-nil. + /// + /// # Safety + /// You must guarantee each of the following: + /// * Pointer is non-nil + /// * Points to a valid objc object of the type specified + pub(crate) unsafe fn assume_nonnil(ptr: *const T) -> Self { + Self(NonNull::new_unchecked(ptr as *mut T)) + } + + ///Dereferences the inner pointer. + /// + /// # Safety + /// You must guarantee each of the following + /// * Object is not deallocated + /// * Object will not be deallocated for the lifetime of `self` (e.g., the lifetime of the returned reference) + /// * Object was initialized + unsafe fn as_ref(&self) -> &T { + self.0.as_ref() + } + + ///Retains the inner pointer and converts to [StrongCell] + /// + /// # Safety + /// You must guarantee each of the following + /// * Object is not deallocated + /// * object was initialized + pub unsafe fn retain(&self) -> StrongCell { + StrongCell::retaining(self.as_ref()) + } + +} + +///Behavior we define for any [ObjcInstance]. +pub trait ObjcInstanceBehavior { + + ///Casts the type to another type. + /// + /// # Safety + /// There is no guarantee that the source type is compatible with the destination type. + unsafe fn cast(&self) -> &R; + + ///Casts the type to another type. + /// + /// # Safety + /// There is no guarantee that the source type is compatible with the destination type. + /// To the extent that you create two pointers pointing to the same instance, + /// this may be UB + unsafe fn cast_mut(&mut self) -> &mut R; + + ///Assuming the pointer is non-nil, returns a pointer type. + /// + /// The opposite of this function is [Self::nullable]. + /// + /// # Safety + /// You must guarantee each of the following: + /// * Pointer is non-nil + /// * Points to a valid objc object of the type specified + unsafe fn assume_nonnil(ptr: *const Self) -> NonNullImmutable; + + ///Safely casts the object to an `Option`. Suitable for implementing nullable functions. + fn nullable(ptr: *const Self) -> Option>; + + ///Allows you to call [objr::bindings::PerformsSelector::perform] from a nonmutating context. + /// + /// This function should not be used for general-purpose pointer casting. + /// + /// # Safety + /// This is only safe when the underlying objc method does not mutate its contents. See [objc_instance#Mutability] for details. + unsafe fn assume_nonmut_perform(&self) -> *mut Self; +} + +impl ObjcInstanceBehavior for T { + unsafe fn cast(&self) -> &R { + &*(self as *const _ as *const R) + } + unsafe fn cast_mut(&mut self) -> &mut R { + &mut *(self as *mut _ as *mut R) + } + unsafe fn assume_nonnil(ptr: *const Self) -> NonNullImmutable { + NonNullImmutable(NonNull::new_unchecked(ptr as *mut Self)) + } + + fn nullable(ptr: *const Self) -> Option> { + if ptr.is_null() { + None + } + else { + //we checked this above + Some(unsafe{ Self::assume_nonnil(ptr) }) + } + } + + unsafe fn assume_nonmut_perform(&self) -> *mut Self { + self as *const Self as *mut Self + } + +} + +///Helper for Option +pub trait NullableBehavior { + type T: ObjcInstance; + ///Assumes the object has been autoreleased and converts to an Option + /// + /// # Safety: + /// You must guarantee each of the following: + /// * Object (if any) is autoreleased already + /// * Object (if any) is not deallocated + /// * Object (if any) was initialized + unsafe fn assume_autoreleased<'a>(self, pool: &'a ActiveAutoreleasePool) -> Option>; + ///Assumes the object has been retained and converts to a StrongCell. + /// + /// # Safety + /// You must guarantee each of the following: + /// * Object was retained (+1) + /// * Object (if any) is not deallocated + /// * Object (if any) was initialized + unsafe fn assume_retained(self) -> Option>; + + ///Retains the inner pointer and converts to [StrongCell] + /// + /// # Safety + /// You must guarantee each of the following + /// * Object (if any) is not deallocated + /// * object (if any) was initialized + unsafe fn retain(self) -> Option>; + + ///Assumes the object has been retained and converts to a StrongLifetimeCell. + /// + /// # Safety + /// You must guarantee each of the following: + /// * Object (if any) was retained (+1) + /// * Object (if any) is not deallocated + /// * Object (if any) was initialized + /// * That the object (if any) can remain valid for the lifetime specified. e.g., all "inner pointers" or "borrowed data" involved + /// in this object will remain valid for the lifetime specified, which is unbounded. + /// * That all objc APIs which end up seeing this instance will either only access it for the lifetime specified, + /// or will take some other step (usually, copying) the object into a longer lifetime. + unsafe fn assume_retained_limited<'a>(self) -> Option> where Self::T: 'a; +} +impl NullableBehavior for Option> { + type T = O; + + unsafe fn assume_autoreleased<'a>(self, pool: &'a ActiveAutoreleasePool) -> Option> { + self.map(|m| m.assume_autoreleased(pool)) + } + + unsafe fn assume_retained(self) -> Option> { + self.map(|m| m.assume_retained()) + } + + unsafe fn retain(self) -> Option> { + self.map(|m| m.retain()) + } + unsafe fn assume_retained_limited<'a>(self) -> Option> where Self::T: 'a { + self.map(|m| m.assume_retained_limited()) + } +} + +///Helper for Option +pub trait NullableCellBehavior { + type T: ObjcInstance; + ///Converts to a mutable version. + /// + /// # Safety + /// You are responsible to check: + /// * There are no other references to the type, mutable or otherwise + /// * The type is in fact "mutable", whatever that means. Specifically, to whatever extent `&mut` functions are forbidden + /// generally, you must ensure it is appropriate to call them here. + unsafe fn assume_mut(self) -> Option>; +} +impl NullableCellBehavior for Option> { + type T = O; + + unsafe fn assume_mut(self) -> Option> { + self.map(|p| p.assume_mut()) + } +} + +/** +Defines a struct (binding) for a specific ObjC type. This doesn't assume the type is a class, if it is a class consider [objc_class!]. + +The type will automagically conform to [objr::bindings::ObjcInstance], but will not conform to [objr::bindings::ObjcClass]. + +# Example + +``` +#![link(name="Foundation",kind="framework")] +use objr::bindings::*; +objc_instance! { + pub struct NSExample; +} +``` + +# The problem + +ObjC and Rust disagree on a great many things. Rust prefers +stack allocation, ObjC types are all heap-allocated. Rust expects static +lifetime proofs, ObjC has significant runtime memory management. Rust expects + to know things like whether a reference is exclusive or not that ObjC withholds. + And of course silly things like `snake_case_methods()` vs `camelCaseMethods`. + +This library is in the unenviable position of trying to please everybody, which cannot really +be done, but meanwhile I have software to write, so here is the Grand Compromise in use around here. + +# Representing ObjC types + +ObjC types are declared as 'opaque' Rust types. While these types technically have a memory layout in Rust, +the memory layout is not the same as the corresponding ObjC layout. Therefore, such types are "effectively" DSTs, +and cannot be stored on the stack or dereferenced. For more information, see unstable feature [RFC 1861](https://rust-lang.github.io/rfcs/1861-extern-types.html). +We implement a "similar" feature in stable Rust. + +In short, this is the type situation for Rust code: + +1. `example: NSExample`. This type effectively is not instantiable, but you can imagine it as the object "owned" by the ObjC runtime. Since you can't + move it out of the runtime you cannot use instances of it. There are some gotchas of this type even without constructing it, e.g. its memory + layout may be different than the situation in ObjC really would be for example. +2. `example: *mut NSExample`. What an ObjC (non-ARC) developer would think of as a normal reference, a Rust raw pointer. +3. `example: *const NSExample`. What an ObjC (non-ARC) developer would think of as an immutable reference, as some mutable methods may not be available. +4. `example: &mut NSExample` 2, but checked by the borrowchecker. One limitation of this type is it is UB if you make it `nil`, so consider modeling with `Option`. While this type is appropriate for parameters, it is somewhat unusual for return values as ObjC is reluctant to relate object lifetimes to each other. +5. `example: &NSExample` 3, but checked by the borrowchecker. One limitation of this type is it is UB if you make it `nil`, so consider modeling with `Option`. hile this type is appropriate for parameters, it is somewhat unusual for return values as ObjC is reluctant to relate object lifetimes to each other. + +ARC is its own topic, which in Rust is handled by various smart pointers. See [objr::bindings::StrongCell] and [objr::bindings::AutoreleasedCell] for details on the pointer types. + +Let's stub out a binding for some ObjC type `NSExample`: + +``` +//we're writing bindings +use objr::bindings::*; +///NSExample is some objc instance (such as a protocol or similar). +//If it were a class, consider objc_class! for extra features. +objc_instance! { + //declares a Rust struct for this type. + //Note that there is no real connection to the actual objc type, you can name it anything. + //The connection arises by casting some pointer to this type, such as the result of [PerformsSelector::perform]. + pub struct NSExample; +} +//We can write normal Rust functions on our type +impl NSExample { + fn new() -> Self { todo!() } + + //I generally follow ObjC syntax for method names, although I'm not gonna tell you what to do. + #[allow(non_snake_case)] + fn instanceMethod(&self) { todo!() } +} +``` +Declaring our type with the `objc_instance!` macro performs several tasks: + +1. It declares a type which we can use as a standin for the ObjC type, in the Rust typesystem. This allows +Rust programs to be typesafe when they work with ObjC objects +2. It provides a container in which to write bindings for the underlying ObjC type +3. It allows end users to call methods and get familiar behavior like [std::fmt::Display]. + +# Safety + +This library *intends* to follow the normal Rust safety guarantees, although there are a few areas that are +more risky than other libraries. + +In general, ObjC is a giant ball of unsafe, opaque code. If you are using this macro, or using something that uses this macro, +you are calling into that giant ball of unsafe code, and who knows if it's sound or not. + +With that background, there are really two bad options: + +a) Insert various runtime checks everywhere. This is slow and stuff still slips through. +b) Just assume ObjC works as specified. This is fast and more stuff slips through. + +This library picks b. Now we cover the topic with examples. + + +## FFI-safety + +[ObjcInstance] type is declared `#[repr(C)]` and pointers to it are valid objc pointers. +So they can be passed directly to any method that expects an ObjC argument. For example, C functions, +fucntions that implement subclassing inside Rust, etc. + +Real ObjC objects can (usually) not be allocated on the stack. This macro should prevent +owned pointers from being constructed (e.g. on the stack). + +## Memory management + +You can find out more about in the documentation +for [StrongCell](objr::bindings::StrongCell) or [AutoreleasedCell](objr::bindings::AutoreleasedCell). Suffice it to say here that use of either cell +largely protects you from dangling pointers, but ObjC is pretty much one giant `unsafe` block, so +it's always possible the ObjC side accidentally frees your memory or even does it on purpose. + +Return types for your binding require special consideration. In general, ObjC memory rules are "by convention", +based on the method name, widely assumed by ObjC programmers, or be buggy and do the opposite thing in rare cases. + +However, there are deep mysteries of ObjC not even known to most ObjC programmers. In practice, the return type +you want is usually [StrongCell](objr::bindings::StrongCell), even in cases where the function is known to +be autoreleased (+0 convention). Why is this? + +In fact, the conventional model that ObjC methods return "either" +0 (autoreleased) or +1 (retain/create/copy) is out of date. +Most +0 methods don't return an autorelease object, but return the result of [`_objc_autoreleaseReturnValue`](https://clang.llvm.org/docs/AutomaticReferenceCounting.html#id63). +This obscure runtime function walks up the stack frame to inspect callers. Callers that are "dumb" get the +0 object, +but smart callers can get a +1 object. + +To be a smart caller, call a function like [`objr::bindings::PerformsSelector::perform_autorelease_to_retain`]. This will promote your +0 pointer to +1, +which can then be passed to [StrongCell](objr::bindings::StrongCell). + +## Nullability + +Another issue is that in ObjC, whether APIs are "nullable" (return a null pointer, usually called 'nil', treated in practice +like Rust `Option::None`) is also by convention. + +Unfortunately, in Rust it is UB to construct a reference to null. Therefore a choice needs to be made about +whether an ObjC pointer should be interepreted as `Option<&T>` or `&T` and the wrong one may UB. + +In general, this library takes the view that ObjC functions are correctly implemented. Therefore, when something is +documented or "widely known" to be nonnull we use `&T` without checking. This follows the precedent of languages like Swift, +although Swift has had trouble with this too. For more information, see [SR-8622](https://bugs.swift.org/browse/SR-8622). + +## Mutability + +The fact is that every ObjC object is behind many shared mutable references. ObjC has no law against +mutating its own state at any time, and effectly all pointers in the language are always mutable. This is undesireable +to Rust developers who may be used to holding inner references to a type and using the borrow checker to prove +that the type is not mutated during the lifetime of inner references. This pattern of inner references +is substantially less likely for ObjC objects although it does crop up in the context of a few types. + +ObjC does have a concept of mutability/immutability, through type pairs (like `NSString` vs `NSMutableString`). +This can be used to achieve some version of mutability guarantees, however `NSString` may do some "inner mutation" somewhere, +so as the basis for a Rust system it isn't great. + +Instead, I have implemented `&` and `&mut` as orthogonal to `NSString` vs `NSMutableString`. You can have `&mut NSString` +and `&NSMutableString`. + +Methods that I have reason to suspect mutate the inner storage are declared `fn mutating(&mut self)`, while methods I think +do not are implemented `fn nonmutating(&self)`. In practice, this means a lot of the `NSMutable` type methods are (`&mut`) and +the former are `&`. + +This generally works as Rust developers expect, with the proviso that it relies on, yet again, convention. In practice, +there is no law that ObjC can't release your references internally if you call some "immutable" method, so maybe your safe code +can do UB. I consider these to be bugs, and please file them if you encounter them, but effectively, I think it's preferable +for "immutable" methods to be immutable, than for everything to be `&mut`. + +There are some methods that can create "additional" `&mut` references to a type, these are declared `unsafe` because +they may be used to violate Rust's exclusive references. + +## Exceptions + +ObjC exceptions are analogous to Rust panics. In practice they abort your program, there is technically some way to handle them +but nobody does, and the decision to support that is a very unfortunate design mistake that now lives on forever. + +More unfortunately, encountering an ObjC exception in Rust is UB. This is substantially worse than a normal abort, +because you may not even get a reasonable abort or error message. + +Since these are primarily not intended to be handled, it is undesireable to try to catch them. Instead, the recommended approach +is to validate arguments on the Rust side (such as with a Rust assert or panic) so that they won't encountered on the ObjC side. +Or alternatively, to mark bindings as `unsafe` when there is some suspicion that ObjC exceptions may occur and push the problem +into the caller. + +There is a [objr::bindings::try_unwrap_void] function which can upgrade the UB to a hard abort. +This function is expensive and not recommended for general use, but it is useful for debugging when you get a weird crash +and need to see an exception print to understand what is wrong. + +Having exceptions as UB is a bit scary. Once again though, we are following in the footsteps of Swift which does something very +similar. Unfortunately, Swift is better at wringing a proper error message out of the exception, even though it isn't totally +reliable either. + +# Generic types +Both ObjC and Rust support generics, which are vaguely similar concepts. However, ObjC's notion of generics is highly 'bolted +on top': it serves as a compile-time assertion that some function accepts or returns a particular type, but it does not +actually constrain the runtime behavior, not does specialization create a distinct type. + +The best way to project this in Rust is to project the "bolted on top" model. Therefore (and also for technical reasons), this +macro does not accept generic arguments, but [objc_instance_newtype] does. + + */ +#[macro_export] +macro_rules! objc_instance { + ( + $(#[$attribute:meta])* + $pub:vis + struct $objctype:ident; + ) => { + //Idea here is we don't allow the type to be constructed where it is declared. + //Doing so would allow stack allocation. + //By nesting inside a separate module, the inner field is private. + ::objr::bindings::__mod!(no_construct,$objctype, { + $(#[$attribute])* + #[repr(transparent)] + #[derive(::objr::bindings::ObjcInstance,Debug)] + pub struct $objctype(core::ffi::c_void); + }); + ::objr::bindings::__use!($pub no_construct,$objctype,$objctype); + }; +} + +/** +Declares a newtype that wraps an existing objc instance type. + +Downcasts to the raw type will be implemented for you. Upcasts will not, implement them yourself with [objr::bindings::ObjcInstanceBehavior::cast()] if applicable. +```no_run +use objr::bindings::*; +objc_instance! { + struct NSExample; +} +objc_instance_newtype! { + struct SecondExample: NSExample; +} +let s: &SecondExample = todo!(); +let e: &NSExample = s.into(); + +let s: &mut SecondExample = todo!(); +let e: &mut NSExample = s.into(); +``` + +unlike [objc_instance!], this macro supports generic types, allowing you to wrap some other type with generics bolted on top. + +At the moment, restrictions on generic arguments are not supported at the type level, but you can add them on your own impl blocks + +``` +use objr::bindings::*; +objc_instance! { + struct NSExample; +} +objc_instance_newtype! { + struct SecondExample: NSExample; +} +//further restriction +impl SecondExample { } +``` +*/ +#[macro_export] +macro_rules! objc_instance_newtype { + ( + $(#[$attribute:meta])* + $pub:vis + struct $newtype:ident $(<$($T:ident),+>)? : $oldtype:ident; + ) => { + ::objr::bindings::__mod!(no_construct,$newtype, { + $(#[$attribute])* + #[repr(transparent)] + #[derive(Debug)] + pub struct $newtype$(<$($T),+>)? (core::ffi::c_void, $($(std::marker::PhantomData<$T>),+)? ); + }); + ::objr::bindings::__use!($pub no_construct,$newtype,$newtype); + impl $(<$($T),+>)? ObjcInstance for $newtype $(<$($T),+>)? {} + impl<'a,$($($T),*)?> From<&'a $newtype $(<$($T),+>)? > for &'a $oldtype { + fn from(f: &'a $newtype $(<$($T),+>)?) -> &'a $oldtype { + unsafe{ f.cast() } + } + } + impl<'a,$($($T),*)?> From<&'a mut $newtype $(<$($T),+>)? > for &'a mut $oldtype { + fn from(f: & 'a mut $newtype $(<$($T),+>)?) -> &'a mut $oldtype { + unsafe{ f.cast_mut() } + } + } + + } +} + + +///Defines some behavior on `Option<&ObjcInstance>` +pub trait OptionalInstanceBehavior { + ///Gets a pointer for the option. If `self` is `nil`, the pointer will be `null`, otherwise it will be the underlying reference. + fn as_ptr(&self) -> *const Deref; +} + +impl OptionalInstanceBehavior for Option<&T> { + fn as_ptr(&self) -> *const T { + if let Some(&s) = self.as_ref() { + s + } + else { + std::ptr::null() + } + } +} \ No newline at end of file diff --git a/objr-upstream/src/objectpointers.rs b/objr-upstream/src/objectpointers.rs new file mode 100644 index 0000000..4b04cee --- /dev/null +++ b/objr-upstream/src/objectpointers.rs @@ -0,0 +1,506 @@ +/*! object pointer types + +For safe types: + +1. AutoreleasedCell - part of an autorelease pool +2. StrongCell - Compiler emits retain/release calls. + +Mutable variants: + +1. AutoreleasedMutCell - like [AutoreleasedCell] but mutable +2. StrongMutCell - like [StrongCell] but mutable + +Lifetime variants: +1. StrongLifetimeCell - like [StrongCell] but tracks some explicit lifetime. Often used for objects that borrow Rust storage. + + +See documentation for particular cells. +*/ + +use core::ffi::{c_void}; +use crate::bindings::{ActiveAutoreleasePool,ObjcInstance}; +use std::marker::PhantomData; +use crate::objcinstance::NonNullImmutable; +use std::ptr::NonNull; +use std::fmt::{Debug}; +use std::hash::{Hash, Hasher}; + +extern "C" { + fn objc_autoreleaseReturnValue(object: *const c_void) -> *const c_void; +} + +///Turning this on may help debug retain/release +const DEBUG_MEMORY: bool = false; + + +#[link(name="objc", kind="dylib")] +extern "C" { + fn objc_retain(ptr: *const c_void) -> *const c_void; + fn objc_release(ptr: *const c_void); + fn objc_autorelease(ptr: *const c_void); +} + + +/** +An objc object that is part of an autorelease pool + +The pool is used to lexically scope the lifetime of the pointer. +*/ +#[derive(Debug)] +pub struct AutoreleasedCell<'a, T> { + ptr: NonNullImmutable, + ///for lifetime + marker: PhantomData<&'a T> +} + +impl<'a, T: ObjcInstance> AutoreleasedCell<'a, T> { + + ///Converts to [Self] by autoreleasing the reference. + pub fn autoreleasing(cell: &T, _pool: &'a ActiveAutoreleasePool) -> Self { + unsafe { + objc_autorelease(cell as *const _ as *const c_void) + } + Self{ + ptr: NonNullImmutable::from_reference(cell), + marker: Default::default() + } + } + ///Converts to [Self] by assuming the pointer is already autoreleased. + /// + /// This is the case for many objc methods, depending on convention. + pub unsafe fn assume_autoreleased(ptr: &T, _pool: &'a ActiveAutoreleasePool) -> Self { + AutoreleasedCell { + ptr: NonNullImmutable::from_reference(ptr), + marker: PhantomData::default() + } + } + + ///Converts to a mutable version. + /// + /// # Safety + /// You are responsible to check: + /// * There are no other references to the type, mutable or otherwise + /// * The type is in fact "mutable", whatever that means. Specifically, to whatever extent `&mut` functions are forbidden + /// generally, you must ensure it is appropriate to call them here. + pub unsafe fn assume_mut(self) -> AutoreleasedMutCell<'a, T> { + let r = + AutoreleasedMutCell { + ptr: NonNull::new_unchecked(self.ptr.as_ptr() as *mut T), + marker: Default::default() + }; + std::mem::forget(self); + r + } +} +impl<'a, T: ObjcInstance> std::ops::Deref for AutoreleasedCell<'a, T> { + type Target = T; + #[inline] fn deref(&self) -> &T { + unsafe{ &*self.ptr.as_ptr() } + } +} + + +impl<'a, T: ObjcInstance> std::fmt::Display for AutoreleasedCell<'a, T> where T: std::fmt::Display { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let ptr = unsafe{ &*self.ptr.as_ptr() }; + std::fmt::Display::fmt(ptr, f) + } +} +impl<'a, T: PartialEq + ObjcInstance> PartialEq for AutoreleasedCell<'a, T> { + fn eq(&self, other: &Self) -> bool { + let a: &T = self; + let b: &T = other; + a == b + } +} + +impl<'a, T: Eq + ObjcInstance> Eq for AutoreleasedCell<'a, T> {} + +impl<'a, T: Hash + ObjcInstance> Hash for AutoreleasedCell<'a, T> { + fn hash(&self, state: &mut H) { + let a: &T = self; + a.hash(state); + } +} + +/** +An objc object that is part of an autorelease pool + +The pool is used to lexically scope the lifetime of the pointer. + */ +#[derive(Debug)] +pub struct AutoreleasedMutCell<'a, T> { + ptr: NonNull, + ///for lifetime + marker: PhantomData<&'a T> +} + +impl<'a, T: ObjcInstance> AutoreleasedMutCell<'a, T> { + + ///Converts to [Self] by autoreleasing the reference. + pub fn autoreleasing(cell: &mut T, _pool: &'a ActiveAutoreleasePool) -> Self { + unsafe { + objc_autorelease(cell as *const _ as *const c_void) + } + Self{ + ptr: unsafe{ NonNull::new_unchecked(cell) }, + marker: Default::default() + } + } + ///Converts to [Self] by assuming the pointer is already autoreleased. + /// + /// This is the case for many objc methods, depending on convention. + pub unsafe fn assume_autoreleased(ptr: &mut T, _pool: &'a ActiveAutoreleasePool) -> Self { + Self { + ptr: NonNull::new_unchecked(ptr), + marker: PhantomData::default() + } + } +} + +impl<'a, T: ObjcInstance> std::ops::Deref for AutoreleasedMutCell<'a, T> { + type Target = T; + #[inline] fn deref(&self) -> &T { + unsafe{ &*self.ptr.as_ptr() } + } +} +impl<'a, T: ObjcInstance> std::ops::DerefMut for AutoreleasedMutCell<'a, T> { + #[inline] fn deref_mut(&mut self) -> &mut T { + unsafe{ &mut *self.ptr.as_mut() } + } +} + + +impl<'a, T: ObjcInstance> std::fmt::Display for AutoreleasedMutCell<'a, T> where T: std::fmt::Display { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let ptr = unsafe{ &*self.ptr.as_ptr() }; + f.write_fmt(format_args!("{}",ptr)) + } +} + +impl<'a, T: PartialEq + ObjcInstance> PartialEq for AutoreleasedMutCell<'a, T> { + fn eq(&self, other: &Self) -> bool { + let a: &T = self; + let b: &T = other; + a == b + } +} +impl<'a, T: Eq + ObjcInstance> Eq for AutoreleasedMutCell<'a, T> {} +impl<'a, T: Hash + ObjcInstance> Hash for AutoreleasedMutCell<'a, T> { + fn hash(&self, state: &mut H) { + let a: &T = self; + a.hash(state); + } +} + +/** +A strong pointer to an objc object. + +This is often the type you want as the return +type when implementing an ObjC binding. + +When this type is created, we will `retain` (unless using an unsafe [StrongCell::assume_retained()] constructor) +When the obj is dropped, we will `release`. + +In ObjC, the compiler tries to elide retain/release but it +may not be possible due to lack of global knowledge, in which +case it inserts `retain` as a precaution. + +In Rust we have global knowledge of lifetimes so we can +elide more perfectly. However this requires splitting up +objc `strong` into an explicit typesystem. + +This type emits `retain`/`release` unconditionally. Therefore +you can think of it like the "worst case" of objc `strong`, the +case where the compiler cannot elide anything. You can also think of +it as a "lifetime eraser", that is we erase knowledge of the object lifetime, +so we assume we need to retain. + +This is often used at the border of an objc binding. + +For an elided 'best case' version, see `RefCell`. +*/ +#[derive(Debug)] +pub struct StrongCell(NonNullImmutable); +impl StrongCell { + pub fn retaining(cell: &T) -> Self { + unsafe { + objc_retain(cell as *const T as *const c_void); + Self::assume_retained(cell) + } + } + + ///Converts to [AutoreleasedCell] by calling `autorelease` on `self`. + /// + ///Safe, but needs to be a moving function, because the StrongCell will not be valid once we + /// decrement its reference counter. + pub fn autoreleasing<'a>(cell: &Self, pool: &'a ActiveAutoreleasePool) -> AutoreleasedCell<'a, T> { + AutoreleasedCell::autoreleasing(cell, pool) + } + ///Converts to [Self] by assuming the argument is already retained. + /// + /// This is usually the case for some objc methods with names like `new`, `copy`, `init`, etc. + /// # Safety + /// You are responsible to check: + /// * That the type is retained + /// * That the type is 'static, that is, it has no references to external (Rust) memory. + /// If this is not the case, see [StrongLifetimeCell]. + pub unsafe fn assume_retained(reference: &T) -> Self { + StrongCell(NonNullImmutable::from_reference(reference)) + } + + ///Converts to a mutable version. + /// + /// # Safety + /// You are responsible to check: + /// * There are no other references to the type, mutable or otherwise + /// * The type is in fact "mutable", whatever that means. Specifically, to whatever extent `&mut` functions are forbidden + /// generally, you must ensure it is appropriate to call them here. + pub unsafe fn assume_mut(self) -> StrongMutCell { + let r = StrongMutCell( + NonNull::new_unchecked(self.0.as_ptr() as *mut T), + ); + std::mem::forget(self); + r + } + ///Attempts to use the "trampoline" trick to return an autoreleased value to objc. + /// + /// This is largely used when implementing a subclass. + /// + ///You must return the return value of this function, to your caller to get optimized results. + /// Results are not guaranteed to be optimized, in part because inline assembly is not stabilized. + #[inline(always)] pub fn return_autoreleased(self) -> *const T { + let ptr = self.0.as_ptr(); + std::mem::forget(self); //LEAK + unsafe{ objc_autoreleaseReturnValue(ptr as *const c_void) as *const T } + } +} + +impl Clone for StrongCell { + fn clone(&self) -> Self { + StrongCell::retaining(&self) + } +} +impl Drop for StrongCell { + fn drop(&mut self) { + unsafe { + if DEBUG_MEMORY { + println!("Drop {} {:p}",std::any::type_name::(), self); + } + objc_release(self.0.as_ptr() as *const _ as *const c_void); + } + } +} +impl std::ops::Deref for StrongCell { + type Target = T; + #[inline] fn deref(&self) -> &T { + unsafe{ &*self.0.as_ptr()} + } +} + +impl<'a, T: ObjcInstance> std::fmt::Display for StrongCell where T: std::fmt::Display { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let ptr = unsafe{ &*(self.0.as_ptr())}; + f.write_fmt(format_args!("{}",ptr)) + } +} +impl PartialEq for StrongCell { + fn eq(&self, other: &Self) -> bool { + let a: &T = self; + let b: &T = other; + a == b + } +} +impl Eq for StrongCell {} +impl Hash for StrongCell { + fn hash(&self, state: &mut H) { + let a: &T = self; + a.hash(state); + } +} +//We do it in objc all the time... +unsafe impl Send for StrongCell {} + +//If the underlying objc instance is sync, so are we... +unsafe impl Sync for StrongCell {} + +///Like StrongCell, but restricted to a particular lifetime. +/// +/// This is typically used for objects that borrow some Rust data +#[derive(Debug)] +pub struct StrongLifetimeCell<'a, T: ObjcInstance>(NonNullImmutable,PhantomData<&'a ()>); +impl<'a, T: ObjcInstance> StrongLifetimeCell<'a, T> { + pub fn retaining(cell: &'a T) -> Self { + unsafe { + objc_retain(cell as *const T as *const c_void); + Self::assume_retained_limited(cell) + } + } + + ///Converts to [AutoreleasedCell] by calling `autorelease` on `self`. + /// + ///Safe, but needs to be a moving function, because the StrongCell will not be valid once we + /// decrement its reference counter. + pub fn autoreleasing<'b: 'a>(cell: &'a Self, pool: &'b ActiveAutoreleasePool) -> AutoreleasedCell<'b, T> { + AutoreleasedCell::autoreleasing(cell, pool) + } + ///Converts to [Self] by assuming the argument is already retained. + /// + /// This is usually the case for some objc methods with names like `new`, `copy`, `init`, etc. + /// # Safety + /// You are repsonsible to check: + /// * That the type is retained + /// * That the type can remain valid for the lifetime specified. e.g., all "inner pointers" or "borrowed data" involved + /// in this object will remain valid for the lifetime specified, which is unbounded. + /// * That all objc APIs which end up seeing this pointer will either only access it for the lifetime specified, + /// or will take some other step (usually, copying) the object into a longer lifetime. + pub unsafe fn assume_retained_limited(reference: &'a T) -> Self { + StrongLifetimeCell(NonNullImmutable::from_reference(reference), PhantomData::default()) + } +} + +impl<'a, T: ObjcInstance> Drop for StrongLifetimeCell<'a, T> { + fn drop(&mut self) { + unsafe { + if DEBUG_MEMORY { + println!("Drop {} {:p}",std::any::type_name::(), self); + } + objc_release(self.0.as_ptr() as *const _ as *const c_void); + } + } +} +impl<'a, T: ObjcInstance> std::ops::Deref for StrongLifetimeCell<'a, T> { + type Target = T; + #[inline] fn deref(&self) -> &T { + unsafe{ &*self.0.as_ptr()} + } +} + +impl<'a, T: ObjcInstance> std::fmt::Display for StrongLifetimeCell<'a, T> where T: std::fmt::Display { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let ptr = unsafe{ &*(self.0.as_ptr())}; + f.write_fmt(format_args!("{}",ptr)) + } +} +impl<'a, T: PartialEq + ObjcInstance> PartialEq for StrongLifetimeCell<'a, T> { + fn eq(&self, other: &Self) -> bool { + let a: &T = self; + let b: &T = other; + a == b + } +} +impl<'a, T: Eq + ObjcInstance> Eq for StrongLifetimeCell<'a, T> {} +impl<'a, T: Hash + ObjcInstance> Hash for StrongLifetimeCell<'a, T> { + fn hash(&self, state: &mut H) { + let a: &T = self; + a.hash(state); + } +} + +///[StrongCell], but mutable +#[derive(Debug)] +pub struct StrongMutCell(NonNull); +impl StrongMutCell { + pub fn retaining(cell: &mut T) -> Self { + unsafe { + objc_retain(cell as *const T as *const c_void); + Self::assume_retained(cell) + } + } + + ///Converts to [AutoreleasedCell] by calling `autorelease` on `self`. + /// + ///Safe, but needs to be a moving function, because the StrongCell will not be valid once we + /// decrement its reference counter. + pub fn autoreleasing<'a>(cell: &mut Self, pool: &'a ActiveAutoreleasePool) -> AutoreleasedMutCell<'a, T> { + AutoreleasedMutCell::autoreleasing(cell, pool) + } + + ///Converts to [StrongCell], e.g. dropping the mutable portion. + /// + /// This consumes the cell, e.g. you can't have an exclusive and nonexclusive reference to the same object. + pub fn as_const(self) -> StrongCell { + let r: StrongCell = unsafe{ StrongCell::assume_retained(&self) }; + std::mem::forget(self); + r + } + +} + +impl StrongMutCell { + ///Converts to [Self] by assuming the argument is already retained. + /// + /// This is usually the case for some objc methods with names like `new`, `copy`, `init`, etc. + /// # Safety + /// If this isn't actually retained, will UB + pub unsafe fn assume_retained(reference: &mut T) -> Self { + //safe because we're using a reference + StrongMutCell(NonNull::new_unchecked(reference)) + } + + ///Attempts to use the "trampoline" trick to return an autoreleased value to objc. + /// + /// This is largely used when implementing a subclass. + /// + /// You must return the return value of this function, to your caller to get optimized results. + /// Results are not guaranteed to be optimized, in part because inline assembly is not stabilized. + #[inline(always)] pub fn return_autoreleased(self) -> *mut T { + let ptr = self.0.as_ptr(); + std::mem::forget(self); //LEAK + unsafe{ objc_autoreleaseReturnValue(ptr as *const c_void) as *const T as *mut T } + } +} + +///we send in objc all the time +unsafe impl Send for StrongMutCell {} + +impl Drop for StrongMutCell { + fn drop(&mut self) { + unsafe { + if DEBUG_MEMORY { + println!("Drop {} {:p}",std::any::type_name::(), self); + } + objc_release(self.0.as_ptr() as *const _ as *const c_void); + } + } +} +impl std::ops::Deref for StrongMutCell { + type Target = T; + #[inline] fn deref(&self) -> &T { + unsafe{ &*self.0.as_ptr()} + } +} +impl std::ops::DerefMut for StrongMutCell { + #[inline] fn deref_mut(&mut self) -> &mut T { + unsafe{ &mut *self.0.as_ptr()} + } +} + +impl<'a, T: ObjcInstance> std::fmt::Display for StrongMutCell where T: std::fmt::Display { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let ptr = unsafe{ &*(self.0.as_ptr())}; + f.write_fmt(format_args!("{}",ptr)) + } +} +impl PartialEq for StrongMutCell { + fn eq(&self, other: &Self) -> bool { + let a: &T = self; + let b: &T = other; + a == b + } +} +impl Eq for StrongMutCell {} +impl Hash for StrongMutCell { + fn hash(&self, state: &mut H) { + let a: &T = self; + a.hash(state); + } +} + + + + + + + + diff --git a/objr-upstream/src/performselector.rs b/objr-upstream/src/performselector.rs new file mode 100644 index 0000000..0b243eb --- /dev/null +++ b/objr-upstream/src/performselector.rs @@ -0,0 +1,201 @@ +use std::ffi::c_void; +use super::arguments::{Arguments}; +use super::arguments::Primitive; +use super::objectpointers::{AutoreleasedCell}; +use super::sel::Sel; +use super::objcinstance::ObjcInstance; +use super::autorelease::ActiveAutoreleasePool; +use crate::bindings::{NSError,ObjcClass}; +use crate::class::AnyClass; + + +///Types that can be performedSelector. +/// +/// # Stability +/// Do not implement this type directly. Instead use [objc_instance!] or [objc_class!]. +/// +/// # Safety +/// This requires the underlying type to be FFI-safe and a valid ObjC pointer. +/// +//- not documentation +//This cannot be sealed because we intend it to be implemented on every ObjcInstance +pub unsafe trait PerformablePointer {} + +//should be safe because ObjcInstance is FFI-safe +unsafe impl PerformablePointer for O {} + +///Trait where we can also call methods on super. This requires knowing a superclass. +/// # Stability +/// Do not implement this type directly. Instead use [objc_instance!] or [objc_class!]. +/// +/// # Safety +/// This requires the underlying type to be FFI-safe and a valid Objc pointer. +/// +// - not documentation +//This cannot be sealed because we intend it to be implemented on every ObjCClass +pub unsafe trait PerformableSuper: PerformablePointer { + fn any_class() -> &'static AnyClass; +} +//should be OK since ObjcClass is FFI-safe +unsafe impl PerformableSuper for O { + fn any_class() -> &'static AnyClass { + //safe because these are memory-compatible and we are downcasting + unsafe{ std::mem::transmute(Self::class()) } + } +} +#[link(name="objc",kind="dylib")] +extern { + //https://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-runtime-objc-retainautoreleasedreturnvalue + pub(crate) fn objc_retainAutoreleasedReturnValue(id: *const c_void) -> *mut c_void; +} + + +///Trait that provides `PerformSelector` implementations. Autoimplelmented for `T: PerformablePointer` +/// +/// # Stability +/// Do not implement this trait yourself. Instead use [objc_instance!] or [objc_class!] +pub trait PerformsSelector { + ///Performs selector, returning a primitive type. + /// # Safety + /// See the safety section of [objc_instance!]. + unsafe fn perform_primitive(receiver: *mut Self, selector: Sel, pool: &ActiveAutoreleasePool, args: A) -> R; + + ///Performs, returning the specified [ObjcInstance]. You must coerce this into some type according to your knowledge of ObjC convention. + unsafe fn perform(receiver: *mut Self, selector: Sel, pool: &ActiveAutoreleasePool, args: A) -> *const R; + ///Performs, returning the result of the specified [ObjcInstance]. You must coerce this into some type according to your knowledge of ObjC convention. + /// + /// By convention, the error value is an autoreleased [NSError]. + /// + ///# Safety + ///See the safety section of [objc_instance!]. + unsafe fn perform_result<'a, A: Arguments, R: ObjcInstance>(receiver: *mut Self, selector: Sel, pool: &'a ActiveAutoreleasePool, args: A) -> Result<*const R, AutoreleasedCell<'a, NSError>>; + + ///Performs, returning the specified [ObjcInstance]. + /// + /// This variant assumes 1) the calling convention is +0, 2) the type returned to you is +1. The implementation + /// knows a trick to perform this conversion faster than you can do it manually. + ///# Safety + ///See the safety section of [objc_instance!]. + unsafe fn perform_autorelease_to_retain(receiver: *mut Self, selector: Sel, pool: &ActiveAutoreleasePool, args: A) -> *const R; + + ///Performs, returning the specified [ObjcInstance]. + /// + /// This variant assumes 1) the calling convention is +0, 2) the type returned to you is +1. The implementation + /// knows a trick to perform this conversion faster than you can do it manually. + ///By convention, the error value is an autoreleased [NSError]. + ///# Safety + ///See the safety section of [objc_instance!]. + unsafe fn perform_result_autorelease_to_retain(receiver: *mut Self, selector: Sel, pool: &ActiveAutoreleasePool, args: A) -> Result<*const R, AutoreleasedCell<'_, NSError>>; +} + +///implementation detail of perform_autorelease_to_strong_nonnull +/// written here to ensure tailcall optimization +/// +/// # Safety +/// Issues include: +/// 1. ptr argument is raw and we don't check anything +/// 2. This function logically increments a reference count (may be elided at runtime) +/// +/// Optimal performance of this function requires the compiler to do tailcall optimization. +/// Hopefully I've written it clearly enough for it to understand. +#[inline(always)] unsafe fn magic_retaining_trampoline(ptr: *mut c_void,selector: Sel, pool: &ActiveAutoreleasePool, args: A) -> *const R { + let c: *mut c_void = Arguments::invoke_primitive(ptr, selector,pool,args); + objc_retainAutoreleasedReturnValue(c) as *const R +} +/// Variant of [magic_retaining_trampoline] for super. +/// # Safety +/// In addition to the issues of [magic_retaining_trampoline], there is no verification that you have passed the correct super_class. +#[inline(always)] unsafe fn magic_retaining_trampoline_super(ptr: *mut c_void,selector: Sel, pool: &ActiveAutoreleasePool, class: *const AnyClass, args: A) -> *const R { + let c: *mut c_void = Arguments::invoke_primitive_super(ptr, selector,pool,class, args); + objc_retainAutoreleasedReturnValue(c) as *const R +} + +impl PerformsSelector for T { + #[inline] unsafe fn perform_primitive(receiver: *mut Self, selector: Sel, pool: &ActiveAutoreleasePool, args: A) -> R { + Arguments::invoke_primitive(receiver as *mut _, selector, pool,args) + } + + #[inline] unsafe fn perform(receiver: *mut Self, selector: Sel, pool: &ActiveAutoreleasePool, args: A) -> *const R { + Arguments::invoke(receiver as *mut c_void, selector, pool, args) + } + + #[inline] unsafe fn perform_result<'a, A: Arguments, R: ObjcInstance>(receiver: *mut Self, selector: Sel, pool: &'a ActiveAutoreleasePool, args: A) -> Result<*const R, AutoreleasedCell<'a, NSError>> { + Arguments::invoke_error(receiver as *mut c_void, selector, pool, args) + } + + #[inline] unsafe fn perform_autorelease_to_retain(receiver: *mut Self, selector: Sel, pool: &ActiveAutoreleasePool, args: A) -> *const R { + magic_retaining_trampoline(receiver as *mut c_void, selector, pool, args) + + } + + #[inline] unsafe fn perform_result_autorelease_to_retain<'a, A: Arguments, R: ObjcInstance>(receiver: *mut Self, selector: Sel, pool: &'a ActiveAutoreleasePool, args: A) -> Result<*const R, AutoreleasedCell<'a, NSError>> { + Arguments::invoke_error_trampoline_strong(receiver as *mut c_void, selector, pool, args) + } +} + +///Variants of the perform functions that talk to `super` instead of `self`. In general, this is supported on classes. +pub trait PerformsSelectorSuper { + ///Performs selector, returning a primitive type. + /// + /// # Safety + ///See the safety section of [objc_instance!]. + unsafe fn perform_super_primitive(receiver: *mut Self, selector: Sel, pool: &ActiveAutoreleasePool, args: A) -> R; + + ///Performs, returning the specified [ObjcInstance]. You must coerce this into some type according to your knowledge of ObjC convention. + /// + /// # Safety + ///See the safety section of [objc_instance!]. + unsafe fn perform_super(receiver: *mut Self, selector: Sel, pool: &ActiveAutoreleasePool, args: A) -> *const R; + ///Performs, returning the result of the specified [ObjcInstance]. You must coerce this into some type according to your knowledge of ObjC convention. + /// + /// By convention, the error value is an autoreleased [NSError]. + /// + /// + /// # Safety + ///See the safety section of [objc_instance!]. + unsafe fn perform_super_result(receiver: *mut Self, selector: Sel, pool: &ActiveAutoreleasePool, args: A) -> Result<*const R, AutoreleasedCell<'_, NSError>>; + + ///Performs, returning the specified [ObjcInstance]. + /// + /// This variant assumes 1) the calling convention is +0, 2) the type returned to you is +1. The implementation + /// knows a trick to perform this conversion faster than you can do it manually. + /// + /// + /// # Safety + ///See the safety section of [objc_instance!]. + unsafe fn perform_super_autorelease_to_retain(receiver: *mut Self, selector: Sel, pool: &ActiveAutoreleasePool, args: A) -> *const R; + + ///Performs, returning the specified [ObjcInstance]. + /// + /// This variant assumes 1) the calling convention is +0, 2) the type returned to you is +1. The implementation + /// knows a trick to perform this conversion faster than you can do it manually. + ///By convention, the error value is an autoreleased [NSError]. + /// + /// # Safety + ///See the safety section of [objc_instance!]. + unsafe fn perform_super_result_autorelease_to_retain(receiver: *mut Self, selector: Sel, pool: &ActiveAutoreleasePool, args: A) -> Result<*const R, AutoreleasedCell<'_, NSError>>; + +} + +impl PerformsSelectorSuper for T { + #[inline] unsafe fn perform_super_primitive(receiver: *mut Self, selector: Sel, pool: &ActiveAutoreleasePool, args: A) -> R { + Arguments::invoke_primitive_super(receiver as *mut c_void, selector, pool,Self::any_class(), args) + } + + #[inline] unsafe fn perform_super(receiver: *mut Self, selector: Sel, pool: &ActiveAutoreleasePool, args: A) -> *const R { + Arguments::invoke_super(receiver as *mut c_void, selector, pool, Self::any_class(), args) + } + + #[inline] unsafe fn perform_super_result(receiver: *mut Self, selector: Sel, pool: &ActiveAutoreleasePool, args: A) -> Result<*const R, AutoreleasedCell<'_, NSError>> { + Arguments::invoke_error_trampoline_super(receiver as *mut c_void, selector, pool, Self::any_class(), args) + } + + #[inline] unsafe fn perform_super_autorelease_to_retain(receiver: *mut Self, selector: Sel, pool: &ActiveAutoreleasePool, args: A) -> *const R { + magic_retaining_trampoline_super(receiver as *mut c_void, selector, pool, Self::any_class(), args) + } + + #[inline] unsafe fn perform_super_result_autorelease_to_retain(receiver: *mut Self, selector: Sel, pool: &ActiveAutoreleasePool, args: A) -> Result<*const R, AutoreleasedCell<'_, NSError>> { + Arguments::invoke_error_trampoline_strong_super(receiver as *mut c_void, selector, pool, Self::any_class(), args) + } +} + diff --git a/objr-upstream/src/sel.rs b/objr-upstream/src/sel.rs new file mode 100644 index 0000000..c593497 --- /dev/null +++ b/objr-upstream/src/sel.rs @@ -0,0 +1,90 @@ +use std::ffi::{c_void, CString}; +use std::os::raw::c_char; + +#[link(name="objc", kind="dylib")] +extern "C" { + fn sel_registerName(string: *const c_char) -> *const c_void; +} + +///ObjC-compatible selector. This type is repr-transparent and can go over the wire as an arg. +#[derive(Copy,Clone,Debug)] +#[repr(transparent)] +pub struct Sel(*const c_void); +impl Sel { + ///Dynamically creates `Sel` from a string by quering the ObjC runtime. Note that in most cases, [objc_selector_group!()] is a faster method + /// to get selectors. + pub fn from_str(string: &str) -> Self { + let cstring = CString::new(string).unwrap(); + + Sel(unsafe { sel_registerName(cstring.as_ptr()) }) + } + pub unsafe fn ptr(&self) -> *const c_void { + self.0 + } + pub const fn from_ptr(ptr: *const c_void) -> Sel { + Sel(ptr) + } + +} + +///Primarily used by [objc_subclass!] and similar. +#[repr(transparent)] +#[doc(hidden)] +pub struct _SyncWrapper(pub T); +unsafe impl core::marker::Sync for _SyncWrapper {} + + +//this magic is needed for dyld to think our program is objc and fixup our symbols +#[link_section = "__DATA,__objc_imageinfo,regular,no_dead_strip"] +#[export_name = "\x01L_OBJC_IMAGE_INFO"] +#[used] +static IMAGE_INFO: [u32; 2] = [0, 64]; + + +///Statically declares a selector and makes it available for use. +/// +/// Before the program entrypoint, dyld will identify these selectors and replace them +/// with the value known to the ObjC runtime. This is substantially faster than `Sel::from_str()` which is a runtime behavior +/// that involves acquiring a lock. +/// +/// # Example +/// ``` +/// use objr::objc_selector_group; +/// use objr::bindings::*; +/// objc_selector_group!( +/// //Declare a trait. The trait will have members for each selector. +/// trait NSObjectSelectors { +/// //each ObjC selector, in normal ObjC selector syntax +/// @selector("description") +/// @selector("respondsToSelector:") +/// @selector("init") +/// } +/// //Implement the trait on Sel. This allows the use of `Sel::description()` etc. +/// impl NSObjectSelectors for Sel {} +/// ); +/// unsafe { +/// let my_selector = Sel::description(); +/// } +/// ``` +#[macro_export] +macro_rules! objc_selector_group { + ( + $(#[$attribute:meta])* + $pub:vis trait $trait:ident { + $( + @selector($selector:literal))* + } + impl $trait2:ident for Sel {} + ) => ( + $pub trait $trait { + $( + objr::bindings::_objc_selector_decl!{$selector} + )* + } + impl $trait for objr::bindings::Sel { + $( + objr::bindings::_objc_selector_impl!{$selector} + )* + } + ) +} diff --git a/objr-upstream/src/subclass.rs b/objr-upstream/src/subclass.rs new file mode 100644 index 0000000..a6fb387 --- /dev/null +++ b/objr-upstream/src/subclass.rs @@ -0,0 +1,744 @@ +#[macro_export] +#[doc(hidden)] +macro_rules! __objc_sublcass_implpart_method_prelude { + ($MethodT:ident,$MethodListT:ident) => { + #[repr(C)] + struct $MethodT { + //in objc-runtime.h this is declared as SEL + name: *const u8, + types: *const u8, + imp: *const c_void + } + + //need a variably-sized type? Const generics to the rescue! + #[repr(C)] + struct $MethodListT { + //I think we place 24 in here, although high bits may be used at runtime? + magic: u32, + //method count + count: u32, + methods: [MethodT; SIZE], + } + + } +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __objc_subclass_implpart_a { + ($pub:vis,$identifier:ident,$objcname:ident,$superclass:ident, + //these ivars are imported from external scope to achieve macro hygiene + $IvarListT:ident,$ClassRoT:ident,$CLASS_NAME:ident,$CLASS_FLAGS:ident,$METACLASS_FLAGS:ident,$CLASST:ident, + $NSSUPER_CLASS:ident,$OBJC_EMPTY_CACHE:ident) => { + use core::ffi::c_void; + #[repr(C)] + struct $IvarListT { + //some dispute about whether this is the size of ivar_list_t, + //a magic number, or both. In practice it's 32 + magic: u32, + count: u32, + //todo: support multiple ivars. For now, just inline the contents of an ivar, which are + //points to FRAGILE_BASE_CLASS_OFFSET + offset: *const u32, + name: *const u8, + r#type: *const u8, + alignment: u32, + size: u32 + } + //see https://opensource.apple.com/source/objc4/objc4-680/runtime/objc-runtime-new.h.auto.html + #[repr(C)] + struct $ClassRoT { + flags: u32, + //Think this is 40 for metaclasses, not sure for regular classes + instance_start: u32, + instance_size: u32, + reserved: u32, //clang emits .space 4 + //Usually 0, although I've seen 1 when the ivar is an `id`. + ivar_layout: *const c_void, //.quad 0 + name: *const u8, + base_method_list: *const c_void, //MethodListT + base_protocols: *const c_void, + ivars: *const IvarListT, + weak_ivar_layout: *const c_void, + base_properties: *const c_void, + } + objr::bindings::__static_asciiz!("__TEXT,__objc_classname,cstring_literals",$CLASS_NAME,$objcname); + + //declare RO_FLAGS options + const RO_FLAGS_METACLASS: u32 = 1; + const RO_FLAGS_HIDDEN:u32 = 1<<4; + const RO_FLAGS_ARR:u32 = 1<<7; + + + const $CLASS_FLAGS: u32 =RO_FLAGS_HIDDEN | RO_FLAGS_ARR; + const METACLASS_FLAGS: u32 =RO_FLAGS_METACLASS | RO_FLAGS_HIDDEN | RO_FLAGS_ARR; + + //declare metaclass RoT + objr::bindings::__static_expr!("__DATA,__objc_const", "_OBJC_METACLASS_RO_$_",$objcname, + static METACLASS_RO: objr::bindings::_SyncWrapper<$ClassRoT> = + objr::bindings::_SyncWrapper(ClassRoT { + flags: METACLASS_FLAGS, + instance_start: 40, + instance_size: 40, + reserved:0, + ivar_layout: std::ptr::null(), + name: &CLASS_NAME as *const u8, + base_method_list: std::ptr::null(), + base_protocols: std::ptr::null(), + ivars: std::ptr::null(), + weak_ivar_layout:std::ptr::null(), + base_properties: std::ptr::null(), + }); + ); + + //note: Class RoT instance needs to wait for ivar configuration + //it cannot appear in the prelude. + + //However we can declare class type + #[repr(C)] + pub struct $CLASST { + //points to metaclass + isa: *const *const c_void, + superclass: *const *const c_void, + // needs to be populated with extern OBJC_EMPTY_CACHE symbol + cache: *const *const c_void, + vtable: *const c_void, + ro: *const ClassRoT + } + + //And some external symbols (only relies on $superclass) + #[link(name="CoreFoundation",kind="framework")] + extern { + #[link_name="OBJC_METACLASS_$_NSObject"] + static NSOBJECT_METACLASS: *const c_void; + + //In addition to that, we likely want symbols for whatever + //our superclass is, if distinct + //Some foundation types are abstract and therefore tricky to subclass + objr::bindings::__static_extern!("OBJC_CLASS_$_",$superclass, + static $NSSUPER_CLASS: *const c_void; + ); + objr::bindings::__static_extern!("OBJC_METACLASS_$_",$superclass, + static NSSUPER_METACLASS: *const c_void; + ); + } + #[link(name="objc",kind="dylib")] + extern { + #[link_name="_objc_empty_cache"] + static $OBJC_EMPTY_CACHE: *const c_void; + } + + //metaclass instance can go in prelude + objr::bindings::__static_expr!("__DATA,__objc_data", "OBJC_METACLASS_$_",$objcname, + static METACLASS: objr::bindings::_SyncWrapper<$CLASST> = objr::bindings::_SyncWrapper($CLASST { + isa: unsafe{ &NSOBJECT_METACLASS}, + superclass: unsafe{ &NSSUPER_METACLASS}, + cache: unsafe{ &OBJC_EMPTY_CACHE}, + vtable: std::ptr::null(), + ro: &METACLASS_RO.0 + }); + ); + + } +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __objc_subclass_implpart_class_ro { + ($objcname:ident, + $CLASS_RO:ident,$ClassRoT:ident,$CLASS_FLAGS:expr,$payload:ty,$CLASS_NAME:expr,$IVARLISTEXPR:expr,$METHODLISTEXPR:expr) => { + objr::bindings::__static_expr!("__DATA,__objc_const", "_OBJC_CLASS_RO_$_",$objcname, + static $CLASS_RO: objr::bindings::_SyncWrapper<$ClassRoT> = objr::bindings::_SyncWrapper($ClassRoT { + flags: $CLASS_FLAGS, + //not sure where these come from + instance_start: 8, + //8 plus whatever the size of our payload is + instance_size: 8 + std::mem::size_of::<$payload>() as u32, + reserved:0, + ivar_layout: std::ptr::null(), + name: &$CLASS_NAME as *const u8, + //In the case that we have methods, we want this to be the method list + base_method_list: $METHODLISTEXPR, + base_protocols: std::ptr::null(), + //in the case that we have ivars, we need a ptr to ivar layout here + ivars: $IVARLISTEXPR, + weak_ivar_layout: std::ptr::null(), + base_properties: std::ptr::null(), + }); + ); + } +} + +///Declares a method list +#[macro_export] +#[doc(hidden)] +macro_rules! __objc_subclass_implpart_method_list { + ( + $objcname:ident, + [$($objcmethod: literal, $methodfn: expr),+], + $METHOD_LIST:ident + ) => { + //method prelude + //declare idents inside the prelude + objr::__objc_sublcass_implpart_method_prelude!(MethodT,MethodListT); + + $( + objr::bindings::__static_asciiz_ident_as_selector!("__TEXT,__objc_methname,cstring_literals","METHNAME_",$methodfn,$objcmethod); + /*todo: The real objc compiler deduplicates these values across different functions. + I'm unclear on exactly what the value of deduplicating this is. From studying compiled binaries + it appears that the *linker* also deduplicates local (`L`) symbols of this type, so I'm + uncertain if deduplicating this at the compile phase has any effect really. + + Leaving this for now. + */ + objr::bindings::__static_asciiz_ident_as_type_encoding!("__TEXT,__objc_methtype,cstring_literals","METHTYPE_",$methodfn,$objcmethod); + )+ + + const COUNT: usize = objr::bindings::__count!($($methodfn),*); + objr::bindings::__static_expr!("__DATA,__objc_const","_OBJC_$_INSTANCE_METHODS_",$objcname, + static $METHOD_LIST: objr::bindings::_SyncWrapper> = objr::bindings::_SyncWrapper( + MethodListT { + magic: 24, + count: COUNT as u32, + methods: [ + $( + MethodT { + name: & objr::bindings::__concat_idents!("METHNAME_",$methodfn) as *const u8, + types: & objr::bindings::__concat_idents!("METHTYPE_",$methodfn) as *const u8, + imp: $methodfn as *const c_void + } + ),* + ] + + } + ); + ); + } +} +///Declares an ivarlist (e.g., payload variants) +#[macro_export] +#[doc(hidden)] +macro_rules! __objc_subclass_implpart_ivar_list { + ($objcname: ident, $payloadtype:ty, $FRAGILE_BASE_CLASS_OFFSET: ident, $IVAR_LIST:ident) => { + objr::bindings::__static_asciiz!("__TEXT,__objc_methname,cstring_literals",IVAR_NAME,"payload"); + //don't explain to objc what type this is + objr::bindings::__static_asciiz!("__TEXT,__objc_methtype,cstring_literals",IVAR_TYPE,"?"); + + //This symbol seems involved in solving the fragile base class problem. + //I am told that if the superclass changes its layout, this type. + //will be updated to point to the new layout. + //By default, we put this to 8 since we think our type starts at position 8 + //into the object? + objr::bindings::__static_expr3!("__DATA,__objc_ivar", "OBJC_IVAR_$_",$objcname,".payload", + static $FRAGILE_BASE_CLASS_OFFSET: u32 = 8; + ); + + objr::bindings::__static_expr!("__DATA,__objc_const", "_OBJC_INSTANCE_VARIABLES_",$objcname, + static $IVAR_LIST: objr::bindings::_SyncWrapper = objr::bindings::_SyncWrapper( + IvarListT { + magic: 32, + count: 1, + offset: &FRAGILE_BASE_CLASS_OFFSET, + name: &IVAR_NAME as *const u8, + r#type: &IVAR_TYPE as *const u8, + alignment: std::mem::align_of::<$payloadtype>() as u32, + size: std::mem::size_of::<$payloadtype>() as u32, + } + ); + ); + } +} +///This macro implements some methods on the wrapper type +///to access the underlying payload. +#[macro_export] +#[doc(hidden)] +macro_rules! __objc_subclass_impl_payload_access { + ($pub:vis, $identifier:ident,$payload:ty, $FRAGILE_BASE_CLASS_OFFSET:ident) => { + impl $identifier { + /// Gets a mutable reference to the underlying payload. + /// + /// # Safety + /// You must guarantee you are called from an exclusive, mutable context. + /// + /// # Design + /// Similar to `UnsafeCell`, but + /// 1. Difficult to initialize a cell here + /// 2. I'm not sure if `UnsafeCell` is FFI-safe + /// 3. In practice, you need to initialize the objc memory close to 100% of the time to avoid UB. + #[allow(dead_code)] + $pub unsafe fn payload_mut(&self) -> &mut $payload { + //convert to u8 to get byte offset + let self_addr = (self as *const _ as *const u8); + //offset by FRAGILE_BASE_CLASS + //Note that a real objc compiler will optimize `FRAGILE_BASE_CLASS_OFFSET` to 8 + //when the superclass is known to be `NSObject` (e.g. the class is not fragile). + //I am skipping that optimization for now. + //todo: Maybe optimize this further + + //Note that we need to read_volatile here to get the real runtime payload, + //not the payload known at compile time + let payload_addr = self_addr.offset(std::ptr::read_volatile(&$FRAGILE_BASE_CLASS_OFFSET) as isize); + + let payload_typed_addr =std::mem::transmute(payload_addr); + payload_typed_addr + } + #[allow(dead_code)] + $pub fn payload(&self) -> &$payload { + unsafe { self.payload_mut() } //coerce to non-mut + } + } + } +} +#[macro_export] +#[doc(hidden)] +macro_rules! __objc_subclass_implpart_finalize { + ($pub:vis,$identifier:ident,$objcname:ident,$superclass:ident, + //these are imported into our scope + $CLASST:ident,$CLASS_RO:ident,$NSSUPER_CLASS:expr,$OBJC_EMPTY_CACHE:expr + ) => { + //declare class + objr::bindings::__static_expr!("__DATA,__objc_data", "OBJC_CLASS_$_",$objcname, + pub static CLASS: objr::bindings::_SyncWrapper<$CLASST> = objr::bindings::_SyncWrapper($CLASST { + isa: unsafe{ std::mem::transmute(&METACLASS )} , + superclass: unsafe{ &$NSSUPER_CLASS} , + cache: unsafe{ &$OBJC_EMPTY_CACHE}, + vtable: std::ptr::null(), + ro: &$CLASS_RO.0 + }); + ); + + use objr::bindings::{objc_instance}; + + //declare our wrapper type + //The declared type will be FFI-safe to an objc pointer, see documentation + //for objc_instance!. + objc_instance! { + pub struct $identifier; + } + //We avoid using `objc_class!` macro here since it imports an external ObjC class. + //As we are exporting a class, we provide our own conformance. + //Should be safe because we're declaring the type + impl objr::bindings::ObjcClass for $identifier { + #[inline] fn class() -> &'static ::objr::bindings::Class { + unsafe{ &*(&CLASS.0 as *const _ as *const ::objr::bindings::Class) } + } + } + } +} + +///Emits the subclass impl in the case have a payload +#[macro_export] +#[doc(hidden)] +macro_rules! __objc_subclass_impl_with_payload_no_methods { + ( + $pub:vis,$identifier:ident,$objcname:ident,$superclass:ident,$payload:ty + ) => { + + objr::__objc_subclass_implpart_a!($pub,$identifier,$objcname,$superclass, + //declare these identifiers into our local scope + IvarListT,ClassRoT,CLASS_NAME,CLASS_FLAGS,METACLASS_FLAGS,CLASST,NSSUPER_CLASS,OBJC_EMPTY_CACHE); + //payload variant requires an ivar list + objr::__objc_subclass_implpart_ivar_list!($objcname,$payload,FRAGILE_BASE_CLASS_OFFSET, IVAR_LIST); + + objr::__objc_subclass_implpart_class_ro!($objcname, CLASS_RO,ClassRoT,CLASS_FLAGS,$payload,CLASS_NAME,&IVAR_LIST.0, + std::ptr::null() //Since we have no methods, we pass null for METHODLISTEXPR + ); + objr::__objc_subclass_implpart_finalize!($pub,$identifier,$objcname,$superclass,CLASST,CLASS_RO,NSSUPER_CLASS,OBJC_EMPTY_CACHE); + objr::__objc_subclass_impl_payload_access!($pub,$identifier,$payload,FRAGILE_BASE_CLASS_OFFSET); + + } +} +#[macro_export] +#[doc(hidden)] +macro_rules! __objc_subclass_impl_no_payload_no_methods { + ($pub:vis,$identifier:ident,$objcname:ident,$superclass:ident) => { + objr::__objc_subclass_implpart_a!($pub,$identifier,$objcname,$superclass, + //declare these identifiers into our local scope + IvarListT,ClassRoT,CLASS_NAME,CLASS_FLAGS,METACLASS_FLAGS,CLASST,NSSUPER_CLASS,OBJC_EMPTY_CACHE); + + objr::__objc_subclass_implpart_class_ro!($objcname, CLASS_RO,ClassRoT,CLASS_FLAGS, + (), //for the no-payload case, use an empty type + CLASS_NAME, + //IVAREXPRESSION: use the null pointer since we have no payload + std::ptr::null(), + //METHLISTEXPRESSION: Use the null pointer since we have no methods + std::ptr::null() + ); + objr::__objc_subclass_implpart_finalize!($pub,$identifier,$objcname,$superclass,CLASST,CLASS_RO,NSSUPER_CLASS,OBJC_EMPTY_CACHE); + } +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __objc_subclass_impl_no_payload_with_methods { + ($pub:vis,$identifier:ident,$objcname:ident,$superclass:ident, + [ $($objcmethod:literal => $methodfn:expr $(,)* )+ ] + ) => { + + objr::__objc_subclass_implpart_a!($pub,$identifier,$objcname,$superclass, + //declare these identifiers into our local scope + IvarListT,ClassRoT,CLASS_NAME,CLASS_FLAGS,METACLASS_FLAGS,CLASST,NSSUPER_CLASS,OBJC_EMPTY_CACHE); + + objr::__objc_subclass_implpart_method_list!( $objcname, [$($objcmethod, $methodfn),*], METHOD_LIST); + + objr::__objc_subclass_implpart_class_ro!($objcname, CLASS_RO,ClassRoT,CLASS_FLAGS, + (), //for the no-payload case, use an empty type + CLASS_NAME, + //use the null pointer for our ivar expression since we have no payload + std::ptr::null(), + //transmute our method_list into c_void + unsafe{ std::mem::transmute(&METHOD_LIST.0) } + ); + objr::__objc_subclass_implpart_finalize!($pub,$identifier,$objcname,$superclass,CLASST,CLASS_RO,NSSUPER_CLASS,OBJC_EMPTY_CACHE); + } +} + +///Variant with payload and methods +#[macro_export] +#[doc(hidden)] + +macro_rules! __objc_subclass_impl_with_payload_with_methods { +($pub: vis, $identifier:ident,$objcname:ident,$superclass:ident,$payload:ty, [$($objcmethod:literal => $methodfn:expr $(,)* )+ ]) => + { + objr::__objc_subclass_implpart_a!($pub,$identifier,$objcname,$superclass, + //declare these identifiers into our local scope + IvarListT,ClassRoT,CLASS_NAME,CLASS_FLAGS,METACLASS_FLAGS,CLASST,NSSUPER_CLASS,OBJC_EMPTY_CACHE); + //variant with payload + objr::__objc_subclass_implpart_ivar_list!($objcname,$payload,FRAGILE_BASE_CLASS_OFFSET, IVAR_LIST); + //variant with methods + objr::__objc_subclass_implpart_method_list!( $objcname, [$($objcmethod, $methodfn),* ], METHOD_LIST); + objr::__objc_subclass_implpart_class_ro!($objcname, CLASS_RO,ClassRoT,CLASS_FLAGS, + $payload, + CLASS_NAME, + unsafe {std::mem::transmute(&IVAR_LIST.0)}, + unsafe{ std::mem::transmute(&METHOD_LIST.0) } + ); + objr::__objc_subclass_implpart_finalize!($pub,$identifier,$objcname,$superclass,CLASST,CLASS_RO,NSSUPER_CLASS,OBJC_EMPTY_CACHE); + objr::__objc_subclass_impl_payload_access!($pub, $identifier,$payload,FRAGILE_BASE_CLASS_OFFSET); + } +} + + +//subclass "real" implementation here +///Declares an objc subclass. +/// ```rust +/// use objr::objc_subclass; +/// objc_subclass! { +/// //Declare a Rust type named `Example`, which maps to the underlying objc class +/// pub struct Example { +/// //In the ObjC runtime, our type will be named `Example` +/// @class(Example) +/// //And will have `NSNull` as its superclass +/// @superclass(NSNull) +/// //Do not allocate any ivar storage for the class +/// payload: (), +/// methods: [] +/// } +/// } +/// ``` +/// +/// # Methods +/// +/// To declare a method on the subclass, use a syntax like +/// ```ignore +/// methods = [ +/// "-(void) mySelector" => unsafe myRustFunction +/// ] +/// ``` +/// +/// Where the left part is an ObjC declaration and the right part is a Rust function. Couple of notes: +/// +/// 1. Rust function must be `extern "C"`. Failing to do this is UB. +/// 2. The first two arguments to the Rust function are the pointer to Self, and the selector. +/// (arguments that are repr-transparent to these are OK as well). +/// 3. All arguments and return values must be FFI-safe. +/// +/// Here's a simple example +/// ``` +/// use objr::bindings::*; +/// extern "C" fn example(objcSelf: Example, //repr-transparent to the pointer type +/// sel: Sel) { +/// println!("Hello from rustdoc!"); +/// } +/// objc_subclass! { +/// pub struct Example { +/// @class(Example) +/// @superclass(NSObject) +/// payload: (), +/// methods: [ "-(void) example" => unsafe example ] +/// } +/// } +/// ``` +/// +/// ## Returning values +/// +/// In general, if you're implementing a method of +1 (that is, retain/strong) convention, you need to return a retained value. +/// This means you must use [std::mem::forget] on a StrongCell. +/// +/// Alternatively, if you're implementing a method of +0 (that is, autorelease) convention, you need to return an autoreleased value. +/// While you can create an [objr::bindings::AutoreleasedCell] yourself, the best strategy is usually to return [objr::bindings::StrongCell::return_autoreleased()]. +/// +/// ## Dealloc +/// +/// You can supply an implementation of dealloc in order to roll your own 'drop' behavior. +/// +/// Note that unlike "modern ARC" objc, you must chain to `[super dealloc]`. +/// +/// ### `.cxx_destruct` +/// +/// A real objc compiler uses a different strategy for the compiler generated deinitializer than `deinit`. When +/// the you create an objc class with `id` (e.g., strong) payloads, the compiler synthesizes a `.cxx_destruct` +/// selector and uses special runtime flags to indicate this selector should be called. This allows +/// compiler synthesis to co-exist with a user-written `deinit`. +/// +/// This is not currently supported by the macro but may be added in the future. +/// +/// ## Arguments +/// The first argument to your C function is a pointer to `self`, and the second argument is a selector-pointer. +/// You may use any memory-compatible types for these arguments in Rust. For example, the self argument can be +/// * `*const c_void` or `*mut c_void`. +/// * `*const Example` or `*mut Example` (it's memory-compatible with the `*const c_void`). Convenience functions are implemented +/// on the wrapper type so this may be the useful one. Keep in mind that it's up to you to not mutate from an immutable context. +/// For more info, see [objc_instance!#safety] +/// +/// For the selector argument, typically you use `Sel`. `*const c_void` and `*const c_char` are also allowed. +/// +/// # Payloads +/// Your ObjC type may have its own storage, inside the object. This obviates the need +/// to allocate any external storage or somehow map between Rust and ObjC memory. +/// +/// Currently, a single field is supported. However, this field can be a Rust struct. +/// Payloads may also be 0-sized, for example `()` may be used. +/// +/// To specify a payload, you use one of the following "payload specifiers" +/// +/// ## `()` +/// Indicates a zero-sized payload. +/// +/// Note that there is a subtle difference between using the tokens `()` and specifying a payload of 0-size (ex, `unsafe ininitialized nondrop ()`). +/// In the former case, we emit no payload to objc. In the latter case, we emit storage of 0 size. The `()` syntax is preferred. +/// +/// ## `unsafe uninitialized nondrop T` +/// +/// Storage for type T will be created. This is +/// * uninitialized. It is UB to read this before initialization. Presumably, you need to write an objc `init` method and ensure it is called. +/// If you somehow read this memory without initialization, this is UB. +/// * nondrop. Drop will never be called on this type +/// * `unsafe`, no memory management is performed. +/// +/// +/// ``` +/// use objr::bindings::*; +/// objc_subclass! { +/// //Declare a Rust type named `Example`, which maps to the underlying objc class +/// pub struct Example { +/// //In the ObjC runtime, our type will be named `Example` +/// @class(Example) +/// //And will have `NSNull` as its superclass +/// @superclass(NSNull) +/// //The following storage will be allocated. See the payload section. +/// payload: unsafe uninitialized nondrop u8, +/// methods: ["-(id) init" => unsafe init] +/// } +/// } +/// +/// extern "C" fn init(objcSelf: *mut Example, sel: Sel) -> *const Example { +/// let new_self: &Example = unsafe{ &*(Example::perform_super(objcSelf, Sel::init(), &ActiveAutoreleasePool::assume_autoreleasepool(), ()))}; +/// //initialize the payload to 5 +/// *(unsafe{new_self.payload_mut()}) = 5; +/// //return self per objc convention +/// new_self +/// } +///``` +/// ### Payload memory management +/// One thing to keep in mind is that in general, memory management is significantly +/// different in ObjC and most Rust patterns simply do not work. +/// +/// Suppose you try to have a `struct Payload<'a> {&'a Type}` payload. A few issues with this: +/// +/// 1. Currently, Rust does not understand that `Payload` is inside `Example`. Therefore, +/// the borrowchecker does not check that `'a` is valid for the lifetime of `Example`. +/// +/// 2. Even if this worked, in practice ObjC types are usually donated to the runtime +/// either explicitly or implicitly. The extent of this is not necessarily documented +/// by ObjC people. For example, in `https://lapcatsoftware.com/articles/working-without-a-nib-part-12.html` +/// it's discussed that `NSWindow` effectively had its lifetime extended in an SDK +/// release, with little in the way of documentation (in fact, I can only find discussion +/// of it there). In practice, this "just happens" in ObjC. +/// +/// Therefore, your options are generally some combination of: +/// +/// 1. Store `'static` data only +/// 2. Use `StrongCell` for ObjC types. This is simlar to what ObjC does internally anyway. +/// 3. Use `Rc` or similar for Rust data. +/// 4. I'm not gonna be the safety police and tell you not to use raw pointers, +/// but you are on your own as far as the unbounded lifetimes of ObjC objects. +/// +/// Keep in mind that for several of these, you need to implement your own dealloc that calls drop. +/// +/// ### Coda on init +/// +/// The payload is born in an uninitialized state, which means any use of it is undefined. Obviously, +/// you need to init it in some initializer. +/// +/// Less obviously, it is tricky to init it correctly. For example, you assign to the payload, you may +/// drop the "prior" (uninitialized) value, which is UB. +/// +/// In theory, [std::mem::MaybeUninit] would solve this – assuming you remember to wrap all your values (or the payload itself). +/// In practice however, [std::mem::MaybeUnint.assume_init()] requires moving the value outside the payload, +/// which cannot really be done in this case. See `https://github.com/rust-lang/rust/issues/63568` for details. +/// +/// The alternative is to write into your payload_mut with [std::ptr::write], which does not drop the uninitialized value. +/// +#[macro_export] +macro_rules! objc_subclass { + ( + $pub:vis struct $identifier:ident { + @class($objcname:ident) + @superclass($superclass:ident) + payload: unsafe uninitialized nondrop $payload:ty, + methods: [] + } + ) => { + objr::__objc_subclass_impl_with_payload_no_methods!($pub,$identifier,$objcname,$superclass,$payload); + }; + ( + $pub:vis struct $identifier:ident { + @class($objcname:ident) + @superclass($superclass:ident) + payload: (), + methods: [] + } + ) => { + objr::__objc_subclass_impl_no_payload_no_methods!($pub,$identifier,$objcname,$superclass); + }; + ( + $pub:vis struct $identifier:ident { + @class($objcname:ident) + @superclass($superclass:ident) + payload: (), + methods: [ $($objcmethod:literal => unsafe $methodfn:expr $(,)?)+ ] + } + ) => { + objr::__objc_subclass_impl_no_payload_with_methods!($pub,$identifier,$objcname,$superclass, + [ $($objcmethod => $methodfn )* ] + ); + }; + ( + $pub:vis struct $identifier:ident { + @class($objcname:ident) + @superclass($superclass:ident) + payload: unsafe uninitialized nondrop $payload:ty, + methods: [ $($objcmethod:literal => unsafe $methodfn:expr $(,)?)+ ] + } + ) => { + objr::__objc_subclass_impl_with_payload_with_methods!($pub,$identifier,$objcname,$superclass,$payload, + [ $($objcmethod => $methodfn )* ] + ); + }; + + +} + + +#[cfg(test)] +mod example { + use objr::bindings::*; + objc_subclass! { + pub struct Example { + @class(Example) + @superclass(NSObject) + payload: (), + methods: [ + "-(id) init" => unsafe sample + ] + } +} + + extern "C" fn sample(objc_self: &Example, _sel: Sel) -> *const Example { + println!("init from rust"); + unsafe{ Example::perform_super(objc_self.assume_nonmut_perform(), Sel::init(), &ActiveAutoreleasePool::assume_autoreleasepool(), ()) } + } +} + +#[cfg(test)] +mod example_payload_no_methods { + use objr::bindings::*; + objc_subclass! { + pub struct ExamplePN { + @class(ExamplePN) + @superclass(NSObject) + payload: unsafe uninitialized nondrop u8, + methods: [] + } + } +} + +#[cfg(test)] +mod example_payload_methods { + use objr::bindings::*; + objc_subclass! { + pub struct ExamplePayloadMethods { + @class(ExamplePayloadMethods) + @superclass(NSObject) + payload: unsafe uninitialized nondrop u8, + methods: [ + "-(id) init" => unsafe sample + ] + } +} + + extern "C" fn sample(objc_self: &ExamplePayloadMethods, _sel: Sel) -> *const ExamplePayloadMethods { + let new_self: &ExamplePayloadMethods = unsafe{ &*ExamplePayloadMethods::perform_super(objc_self.assume_nonmut_perform(), Sel::init(), &ActiveAutoreleasePool::assume_autoreleasepool(), () ) }; + *(unsafe{new_self.payload_mut()}) = 5; + new_self + } +} + +#[cfg(test)] +mod example_dealloc { + pub static DEALLOC_COUNT: AtomicBool = AtomicBool::new(false); + use objr::bindings::*; + use std::sync::atomic::{AtomicBool, Ordering}; + objc_subclass! { + pub struct ExampleDealloc { + @class(ExampleDealloc) + @superclass(NSObject) + payload: unsafe uninitialized nondrop u8, + methods: [ + "-(void) dealloc" => unsafe dealloc + ] + } +} + + extern "C" fn dealloc(objc_self: &mut ExampleDealloc, _sel: Sel) { + let _: () = unsafe{ ExampleDealloc::perform_super_primitive(objc_self, Sel::from_str("dealloc"), &ActiveAutoreleasePool::assume_autoreleasepool(), ())}; + DEALLOC_COUNT.store(true,Ordering::SeqCst); + } +} + + + +#[test] fn subclass() { + use objr::bindings::*; + + let pool = unsafe{ AutoreleasePool::new() }; + let _ = example::Example::class().alloc_init(&pool); +} +#[test] fn subclass_dealloc() { + use objr::bindings::*; + use std::sync::atomic::Ordering; + let pool = unsafe{ AutoreleasePool::new() }; + assert!(example_dealloc::DEALLOC_COUNT.load(Ordering::SeqCst) == false); + let _ = example_dealloc::ExampleDealloc::class().alloc_init(&pool); + //ex dropped here + assert!(example_dealloc::DEALLOC_COUNT.load(Ordering::SeqCst) == true); + +} + +#[test] fn initialize_payload() { + use objr::bindings::*; + let pool = unsafe{ AutoreleasePool::new() }; + let ex = example_payload_methods::ExamplePayloadMethods::class().alloc_init(&pool); + assert!(*ex.payload() == 5); +} \ No newline at end of file diff --git a/objr-upstream/src/typealias.rs b/objr-upstream/src/typealias.rs new file mode 100644 index 0000000..dc28112 --- /dev/null +++ b/objr-upstream/src/typealias.rs @@ -0,0 +1,6 @@ +//! These are typealiases to the types used in objc + +use std::os::raw::{c_ulong}; + +#[cfg(target_pointer_width = "64")] +pub(crate) type NSUInteger = c_ulong; \ No newline at end of file diff --git a/objr/Cargo.lock b/objr/Cargo.lock new file mode 100644 index 0000000..97a74da --- /dev/null +++ b/objr/Cargo.lock @@ -0,0 +1,606 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" + +[[package]] +name = "cast" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "cc" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "bitflags", + "textwrap", + "unicode-width", +] + +[[package]] +name = "criterion" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" +dependencies = [ + "atty", + "cast", + "clap", + "criterion-plot", + "csv", + "itertools", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_cbor", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if", + "lazy_static", +] + +[[package]] +name = "csv" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +dependencies = [ + "bstr", + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "itertools" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a60553f9a9e039a333b4e9b20573b9e9b9c0bb3a11e201ccc48ef4283456d673" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "objr" +version = "0.1.0" +dependencies = [ + "cc", + "criterion", +] + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "plotters" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d88417318da0eaf0fdcdb51a0ee6c3bed624333bff8f946733049380be67ac1c" + +[[package]] +name = "plotters-svg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521fa9638fa597e1dc53e9412a4f9cefb01187ee1f7413076f9e6749e2885ba9" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "proc-macro2" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" + +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" + +[[package]] +name = "web-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/objr/Cargo.toml b/objr/Cargo.toml index f26bc9a..ad8da1c 100644 --- a/objr/Cargo.toml +++ b/objr/Cargo.toml @@ -7,13 +7,3 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] - -[dev-dependencies] -criterion = "0.3" - -[[bench]] -name = "basic_bench" -harness = false - -[build-dependencies] -cc = "1" \ No newline at end of file diff --git a/objr/src/lib.rs b/objr/src/lib.rs index 5c224b9..4021334 100644 --- a/objr/src/lib.rs +++ b/objr/src/lib.rs @@ -1,15 +1,3 @@ -mod performselector; -mod objcinstance; - pub mod bindings { - pub use super::performselector::{PerformsSelector}; + pub trait PerformsSelector {} } - - - - - - - - - diff --git a/objr/src/objcinstance.rs b/objr/src/objcinstance.rs deleted file mode 100644 index e69de29..0000000 diff --git a/objr/src/performselector.rs b/objr/src/performselector.rs deleted file mode 100644 index d59a273..0000000 --- a/objr/src/performselector.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub trait PerformsSelector { -} - - - -