Skip to content

Commit a7b3ff6

Browse files
committed
Add static classes
1 parent e4a53a7 commit a7b3ff6

35 files changed

+1138
-92
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,12 +395,12 @@ jobs:
395395
# Not using --all-features because that would enable e.g. gnustep
396396
args: ${{ env.ARGS }} ${{ env.TESTARGS }} --features ${{ env.FEATURES }},${{ env.UNSTABLE_FEATURES }}
397397

398-
- name: Test static selectors
398+
- name: Test static class and selectors
399399
if: ${{ !matrix.dinghy && (matrix.runtime || 'apple') == 'apple' }}
400400
uses: actions-rs/cargo@v1
401401
with:
402402
command: test
403-
args: ${{ env.ARGS }} ${{ env.TESTARGS }} --features unstable-static-sel
403+
args: ${{ env.ARGS }} ${{ env.TESTARGS }} --features unstable-static-sel,unstable-static-class
404404

405405
- name: Run assembly tests
406406
if: ${{ !contains(matrix.runtime, 'compiler-rt') }}

objc2-foundation/src/declare_macro.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ macro_rules! declare_class {
515515
use $crate::__std::sync::Once;
516516

517517
use $crate::objc2::declare::ClassBuilder;
518-
use $crate::objc2::runtime::Protocol;
518+
use $crate::objc2::runtime::{Class, Protocol};
519519
static REGISTER_CLASS: Once = Once::new();
520520

521521
REGISTER_CLASS.call_once(|| {
@@ -555,7 +555,8 @@ macro_rules! declare_class {
555555
let _cls = builder.register();
556556
});
557557

558-
$crate::objc2::class!($name)
558+
// We just registered the class, so it should be available
559+
Class::get(stringify!($name)).unwrap()
559560
}
560561
}
561562

objc2/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

77
## Unreleased - YYYY-MM-DD
88

9+
### Added
10+
* Added the `"unstable-static-class"` and `"unstable-static-class-inlined"`
11+
feature flags to make the `class!` macro zero cost.
12+
913

1014
## 0.3.0-beta.1 - 2022-07-19
1115

objc2/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ malloc = ["malloc_buf"]
5151
# https://github.com/madsmtm/objc2/issues/new
5252
unstable-static-sel = ["objc2-proc-macros"]
5353
unstable-static-sel-inlined = ["unstable-static-sel"]
54+
unstable-static-class = ["objc2-proc-macros"]
55+
unstable-static-class-inlined = ["unstable-static-class"]
5456

5557
# Uses nightly features to make AutoreleasePool zero-cost even in debug mode
5658
unstable-autoreleasesafe = []

objc2/examples/introspection.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ use objc2::{class, msg_send, msg_send_id};
44
#[cfg(feature = "malloc")]
55
use objc2::{sel, Encode};
66

7+
#[cfg(feature = "apple")]
8+
#[link(name = "Foundation", kind = "framework")]
9+
extern "C" {}
10+
711
fn main() {
812
// Get a class
913
let cls = class!(NSObject);

objc2/examples/talk_to_me.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ use std::ffi::c_void;
1212
#[cfg(feature = "apple")]
1313
#[link(name = "AVFoundation", kind = "framework")]
1414
extern "C" {}
15+
#[cfg(feature = "apple")]
16+
#[link(name = "Foundation", kind = "framework")]
17+
extern "C" {}
1518

1619
const UTF8_ENCODING: NSUInteger = 4;
1720

objc2/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,11 @@ mod test_utils;
218218
#[cfg(feature = "malloc")]
219219
mod verify;
220220

221+
// Hack to make doctests work
222+
#[cfg(all(feature = "apple", feature = "unstable-static-class"))]
223+
#[link(name = "Foundation", kind = "framework")]
224+
extern "C" {}
225+
221226
/// Hacky way to make GNUStep link properly to Foundation while testing.
222227
///
223228
/// This is a temporary solution to make our CI work for now!

objc2/src/macros.rs

Lines changed: 175 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,35 @@
11
/// Gets a reference to a [`Class`] from the given name.
22
///
3+
/// [`Class`]: crate::runtime::Class
4+
///
5+
///
36
/// # Panics
47
///
58
/// Panics if no class with the given name can be found.
69
///
710
/// To check for a class that may not exist, use [`Class::get`].
811
///
9-
/// [`Class`]: crate::runtime::Class
1012
/// [`Class::get`]: crate::runtime::Class::get
1113
///
14+
///
15+
/// # Features
16+
///
17+
/// If the experimental `"unstable-static-class"` feature is enabled, this
18+
/// will emit special statics that will be replaced by dyld when the program
19+
/// starts up.
20+
///
21+
/// Errors that were previously runtime panics may now turn into linker errors
22+
/// if you try to use a class which is not available. Additionally, you may
23+
/// have to call `msg_send![cls, class]` on the result if you want to use it
24+
/// in a dynamic context (e.g. dynamically declaring classes).
25+
///
26+
/// See the [corresponding section][sel#features] in the [`sel!`] macro for
27+
/// more details on the limitations of this. The
28+
/// `"unstable-static-class-inlined"` corresponds to the
29+
/// `"unstable-static-sel-inlined"` feature here.
30+
///
31+
/// [sel#features]: crate::sel#features
32+
///
1233
/// # Examples
1334
///
1435
/// ```no_run
@@ -17,6 +38,15 @@
1738
/// ```
1839
#[macro_export]
1940
macro_rules! class {
41+
($name:ident) => {{
42+
$crate::__class_inner!($name)
43+
}};
44+
}
45+
46+
#[doc(hidden)]
47+
#[macro_export]
48+
#[cfg(not(feature = "unstable-static-class"))]
49+
macro_rules! __class_inner {
2050
($name:ident) => {{
2151
use $crate::__macro_helpers::{concat, panic, stringify, CachedClass, None, Some};
2252
static CACHED_CLASS: CachedClass = CachedClass::new();
@@ -177,18 +207,12 @@ macro_rules! __sel_inner {
177207

178208
#[doc(hidden)]
179209
#[macro_export]
180-
macro_rules! __sel_inner_statics_apple_generic {
210+
macro_rules! __inner_statics_apple_generic {
181211
{
212+
@image_info;
182213
$image_info_section:literal;
183-
$var_name_section:literal;
184-
$selector_ref_section:literal;
185-
$data:expr,
186214
$($idents:ident)+
187215
} => {
188-
use $crate::__macro_helpers::{u8, UnsafeCell};
189-
use $crate::ffi::__ImageInfo;
190-
use $crate::runtime::Sel;
191-
192216
/// We always emit the image info tag, since we need it to:
193217
/// - End up in the same codegen unit as the other statics below.
194218
/// - End up in the final binary so it can be read by dyld.
@@ -202,7 +226,17 @@ macro_rules! __sel_inner_statics_apple_generic {
202226
$crate::__macro_helpers::__hash_idents!($($idents)+),
203227
)]
204228
#[used] // Make sure this reaches the linker
205-
static _IMAGE_INFO: __ImageInfo = __ImageInfo::system();
229+
static _IMAGE_INFO: $crate::ffi::__ImageInfo = $crate::ffi::__ImageInfo::system();
230+
};
231+
{
232+
@sel;
233+
$var_name_section:literal;
234+
$selector_ref_section:literal;
235+
$data:expr,
236+
$($idents:ident)+
237+
} => {
238+
use $crate::__macro_helpers::{u8, UnsafeCell};
239+
use $crate::runtime::Sel;
206240

207241
const X: &[u8] = $data.as_bytes();
208242

@@ -262,47 +296,128 @@ macro_rules! __sel_inner_statics_apple_generic {
262296
UnsafeCell::new(Sel::__internal_from_ptr(NAME_DATA.as_ptr().cast()))
263297
};
264298
};
299+
{
300+
@class;
301+
$class_ref_section:literal;
302+
$name:ident
303+
} => {
304+
use $crate::__macro_helpers::UnsafeCell;
305+
use $crate::runtime::Class;
306+
307+
extern "C" {
308+
/// Link to the Objective-C class static.
309+
///
310+
/// This uses the special symbol that static and dynamic linkers
311+
/// knows about.
312+
///
313+
/// Failure modes:
314+
/// - Unknown class: Static linker error.
315+
/// - OS version < Class introduced version: Dynamic linker error
316+
/// on program startup.
317+
/// - Deployment target > Class introduced version: No error,
318+
/// though _should_ be a static linker error.
319+
///
320+
/// Ideally, we'd have some way of allowing this to be weakly
321+
/// linked, and return `Option<&Class>` in that case, but Rust
322+
/// doesn't have the capability to do so yet!
323+
/// <https://github.com/rust-lang/rust/issues/29603>
324+
/// <https://stackoverflow.com/a/16936512>
325+
/// <http://sealiesoftware.com/blog/archive/2010/4/8/Do-it-yourself_Objective-C_weak_import.html>
326+
#[link_name = $crate::__macro_helpers::concat!(
327+
"OBJC_CLASS_$_",
328+
$crate::__macro_helpers::stringify!($name),
329+
)]
330+
static CLASS: Class;
331+
}
332+
333+
/// SAFETY: Same as `REF` above.
334+
#[link_section = $class_ref_section]
335+
#[export_name = $crate::__macro_helpers::concat!(
336+
"\x01L_OBJC_CLASSLIST_REFERENCES_$_",
337+
$crate::__macro_helpers::__hash_idents!($name),
338+
)]
339+
static mut REF: UnsafeCell<&Class> = unsafe {
340+
UnsafeCell::new(&CLASS)
341+
};
342+
};
265343
}
266344

345+
// These sections are found by reading clang/LLVM sources
267346
#[doc(hidden)]
268347
#[macro_export]
269348
#[cfg(all(feature = "apple", not(all(target_os = "macos", target_arch = "x86"))))]
270-
macro_rules! __sel_inner_statics {
271-
($($args:tt)*) => {
272-
// Found by reading clang/LLVM sources
273-
$crate::__sel_inner_statics_apple_generic! {
349+
macro_rules! __inner_statics {
350+
(@image_info $($args:tt)*) => {
351+
$crate::__inner_statics_apple_generic! {
352+
@image_info;
274353
"__DATA,__objc_imageinfo,regular,no_dead_strip";
354+
$($args)*
355+
}
356+
};
357+
(@sel $($args:tt)*) => {
358+
$crate::__inner_statics_apple_generic! {
359+
@sel;
275360
"__TEXT,__objc_methname,cstring_literals";
276361
"__DATA,__objc_selrefs,literal_pointers,no_dead_strip";
277362
$($args)*
278363
}
279364
};
365+
(@class $($args:tt)*) => {
366+
$crate::__inner_statics_apple_generic! {
367+
@class;
368+
"__DATA,__objc_classrefs,regular,no_dead_strip";
369+
$($args)*
370+
}
371+
};
280372
}
281373

282374
#[doc(hidden)]
283375
#[macro_export]
284376
#[cfg(all(feature = "apple", target_os = "macos", target_arch = "x86"))]
285-
macro_rules! __sel_inner_statics {
286-
($($args:tt)*) => {
287-
$crate::__sel_inner_statics_apple_generic! {
377+
macro_rules! __inner_statics {
378+
(@image_info $($args:tt)*) => {
379+
$crate::__inner_statics_apple_generic! {
380+
@image_info;
288381
"__OBJC,__image_info,regular";
382+
$($args)*
383+
}
384+
};
385+
(@sel $($args:tt)*) => {
386+
$crate::__inner_statics_apple_generic! {
387+
@sel;
289388
"__TEXT,__cstring,cstring_literals";
290389
"__OBJC,__message_refs,literal_pointers,no_dead_strip";
291390
$($args)*
292391
}
293392
};
393+
(@class $($args:tt)*) => {
394+
// TODO
395+
$crate::__macro_helpers::compile_error!(
396+
"The `\"unstable-static-class\"` feature is not yet supported on 32bit macOS!"
397+
)
398+
// TODO: module info
399+
};
294400
}
295401

296402
#[doc(hidden)]
297403
#[macro_export]
298404
#[cfg(not(feature = "apple"))]
299-
macro_rules! __sel_inner_statics {
300-
($($args:tt)*) => {
405+
macro_rules! __inner_statics {
406+
(@image_info $($args:tt)*) => {
407+
// TODO
408+
};
409+
(@sel $($args:tt)*) => {
301410
// TODO
302411
$crate::__macro_helpers::compile_error!(
303412
"The `\"unstable-static-sel\"` feature is not yet supported on GNUStep!"
304413
)
305414
};
415+
(@class $($args:tt)*) => {
416+
// TODO
417+
$crate::__macro_helpers::compile_error!(
418+
"The `\"unstable-static-class\"` feature is not yet supported on GNUStep!"
419+
)
420+
};
306421
}
307422

308423
#[doc(hidden)]
@@ -312,8 +427,9 @@ macro_rules! __sel_inner_statics {
312427
not(feature = "unstable-static-sel-inlined")
313428
))]
314429
macro_rules! __sel_inner {
315-
($($args:tt)*) => {{
316-
$crate::__sel_inner_statics!($($args)*);
430+
($data:expr, $($idents:ident)+) => {{
431+
$crate::__inner_statics!(@image_info $($idents)+);
432+
$crate::__inner_statics!(@sel $data, $($idents)+);
317433

318434
/// HACK: Wrap the access in a non-generic, `#[inline(never)]`
319435
/// function to make the compiler group it into the same codegen unit
@@ -340,8 +456,44 @@ macro_rules! __sel_inner {
340456
#[macro_export]
341457
#[cfg(all(feature = "unstable-static-sel-inlined"))]
342458
macro_rules! __sel_inner {
343-
($($args:tt)*) => {{
344-
$crate::__sel_inner_statics!($($args)*);
459+
($data:expr, $($idents:ident)+) => {{
460+
$crate::__inner_statics!(@image_info $($idents)+);
461+
$crate::__inner_statics!(@sel $data, $($idents)+);
462+
463+
#[allow(unused_unsafe)]
464+
// SAFETY: See above
465+
unsafe { *REF.get() }
466+
}};
467+
}
468+
469+
#[doc(hidden)]
470+
#[macro_export]
471+
#[cfg(all(
472+
feature = "unstable-static-class",
473+
not(feature = "unstable-static-class-inlined")
474+
))]
475+
macro_rules! __class_inner {
476+
($name:ident) => {{
477+
$crate::__inner_statics!(@image_info $name);
478+
$crate::__inner_statics!(@class $name);
479+
480+
#[inline(never)]
481+
fn objc_static_workaround() -> &'static Class {
482+
// SAFETY: Same as __sel_inner
483+
unsafe { *REF.get() }
484+
}
485+
486+
objc_static_workaround()
487+
}};
488+
}
489+
490+
#[doc(hidden)]
491+
#[macro_export]
492+
#[cfg(all(feature = "unstable-static-class-inlined"))]
493+
macro_rules! __class_inner {
494+
($name:ident) => {{
495+
$crate::__inner_statics!(@image_info $name);
496+
$crate::__inner_statics!(@class $name);
345497

346498
#[allow(unused_unsafe)]
347499
// SAFETY: See above

objc2/src/rc/test_object.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,11 @@ impl RcTestObject {
130130
builder.add_method(sel!(dealloc), dealloc as unsafe extern "C" fn(_, _));
131131
}
132132

133-
builder.register();
133+
let _cls = builder.register();
134134
});
135135

136-
class!(RcTestObject)
136+
// Can't use `class!` here since `RcTestObject` is dynamically created.
137+
Class::get("RcTestObject").unwrap()
137138
}
138139

139140
pub(crate) fn new() -> Id<Self, Owned> {

0 commit comments

Comments
 (0)