diff --git a/impl/src/lib.rs b/impl/src/lib.rs index 2b300c0..428274c 100644 --- a/impl/src/lib.rs +++ b/impl/src/lib.rs @@ -8,25 +8,51 @@ mod args; mod attr; -mod declaration; -mod element; mod hash; mod linker; +mod singleton; +mod slice; mod ty; use crate::args::Args; use crate::hash::hash; use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::{quote, quote_spanned}; use syn::parse_macro_input; +use syn::spanned::Spanned; #[proc_macro_attribute] pub fn distributed_slice(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args as Args); let expanded = match args { - Args::None => declaration::expand(parse_macro_input!(input)), - Args::Path(path) => element::expand(path, None, parse_macro_input!(input)), - Args::PathPos(path, pos) => element::expand(path, pos, parse_macro_input!(input)), + Args::None => slice::declaration::expand(parse_macro_input!(input)), + Args::Path(path) => slice::element::expand(path, None, parse_macro_input!(input)), + Args::PathPos(path, pos) => slice::element::expand(path, pos, parse_macro_input!(input)), + }; + + TokenStream::from(expanded) +} + +#[proc_macro_attribute] +pub fn disjointed_static(args: TokenStream, input: TokenStream) -> TokenStream { + let args2: TokenStream2 = args.clone().into(); + let args = parse_macro_input!(args as Args); + + let expanded = match args { + Args::None => singleton::declaration::expand(parse_macro_input!(input)), + Args::Path(path) => singleton::item::expand(path, parse_macro_input!(input)), + Args::PathPos(path, _) => { + let sort = quote_spanned! {args2.span()=> + compile_error!("disjointed_static does not accept a sort key"); + }; + let item = singleton::item::expand(path, parse_macro_input!(input)); + quote! { + #sort + #item + } + } }; TokenStream::from(expanded) diff --git a/impl/src/singleton/declaration.rs b/impl/src/singleton/declaration.rs new file mode 100644 index 0000000..e3fb9f5 --- /dev/null +++ b/impl/src/singleton/declaration.rs @@ -0,0 +1,211 @@ +use crate::{attr, linker, ty}; +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::parse::{Parse, ParseStream, Result}; +use syn::{Attribute, Error, Ident, Token, Type, Visibility}; + +struct Declaration { + attrs: Vec, + vis: Visibility, + ident: Ident, + ty: Type, +} + +impl Parse for Declaration { + fn parse(input: ParseStream) -> Result { + let attrs = input.call(Attribute::parse_outer)?; + let vis: Visibility = input.parse()?; + input.parse::()?; + let mut_token: Option = input.parse()?; + if let Some(mut_token) = mut_token { + return Err(Error::new_spanned( + mut_token, + "static mut is not supported by disjointed_static", + )); + } + let ident: Ident = input.parse()?; + input.parse::()?; + let ty: Type = input.parse()?; + + let eq_token: Option = input.parse()?; + if eq_token.is_some() { + input.parse::()?; + } + + input.parse::()?; + + Ok(Declaration { + attrs, + vis, + ident, + ty, + }) + } +} + +pub fn expand(input: TokenStream) -> TokenStream { + let msg = "disjointed_static is not implemented for this platform"; + let error = Error::new_spanned(&input, msg); + let unsupported_platform = error.to_compile_error(); + + let decl: Declaration = match syn::parse2(input) { + Ok(decl) => decl, + Err(err) => return err.to_compile_error(), + }; + + let mut attrs = decl.attrs; + let vis = decl.vis; + let ident = decl.ident; + let mut ty = decl.ty; + let name = ident.to_string(); + + let linkme_path = match attr::linkme_path(&mut attrs) { + Ok(path) => path, + Err(err) => return err.to_compile_error(), + }; + + ty::populate_static_lifetimes(&mut ty); + + let used = if cfg!(feature = "used_linker") { + quote!(#[used(linker)]) + } else { + quote!(#[used]) + }; + + let linux_section = linker::linux::section(&ident); + let linux_section_start = linker::linux::section_start(&ident); + let linux_section_stop = linker::linux::section_stop(&ident); + let linux_dupcheck = linux_section.replacen("linkme", "linkm2", 1); + let linux_dupcheck_start = linux_section_start.replacen("linkme", "linkm2", 1); + let linux_dupcheck_stop = linux_section_stop.replacen("linkme", "linkm2", 1); + + let macho_section = linker::macho::section(&ident); + let macho_section_start = linker::macho::section_start(&ident); + let macho_section_stop = linker::macho::section_stop(&ident); + let macho_dupcheck = macho_section.replacen("linkme", "linkm2", 1); + let macho_dupcheck_start = macho_section_start.replacen("linkme", "linkm2", 1); + let macho_dupcheck_stop = macho_section_stop.replacen("linkme", "linkm2", 1); + + let windows_section = linker::windows::section(&ident); + let windows_section_start = linker::windows::section_start(&ident); + let windows_section_stop = linker::windows::section_stop(&ident); + let windows_dupcheck = windows_section.replacen("linkme", "linkm2", 1); + let windows_dupcheck_start = windows_section_start.replacen("linkme", "linkm2", 1); + let windows_dupcheck_stop = windows_section_stop.replacen("linkme", "linkm2", 1); + + let illumos_section = linker::illumos::section(&ident); + let illumos_section_start = linker::illumos::section_start(&ident); + let illumos_section_stop = linker::illumos::section_stop(&ident); + let illumos_dupcheck = illumos_section.replacen("linkme", "linkm2", 1); + let illumos_dupcheck_start = illumos_section_start.replacen("linkme", "linkm2", 1); + let illumos_dupcheck_stop = illumos_section_stop.replacen("linkme", "linkm2", 1); + + let freebsd_section = linker::freebsd::section(&ident); + let freebsd_section_start = linker::freebsd::section_start(&ident); + let freebsd_section_stop = linker::freebsd::section_stop(&ident); + let freebsd_dupcheck = freebsd_section.replacen("linkme", "linkm2", 1); + let freebsd_dupcheck_start = freebsd_section_start.replacen("linkme", "linkm2", 1); + let freebsd_dupcheck_stop = freebsd_section_stop.replacen("linkme", "linkm2", 1); + + let call_site = Span::call_site(); + let link_section_macro_str = format!("_linkme_macro_{}", ident); + let link_section_macro = Ident::new(&link_section_macro_str, call_site); + + quote! { + #(#attrs)* + #vis static #ident: #linkme_path::DisjointedStatic<#ty> = { + #[cfg(any( + target_os = "none", + target_os = "linux", + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "windows", + target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "freebsd", + target_os = "psp", + ))] + extern "Rust" { + #[cfg_attr(any(target_os = "none", target_os = "linux", target_os = "android", target_os = "fuchsia", target_os = "psp"), link_name = #linux_section)] + #[cfg_attr(any(target_os = "macos", target_os = "ios", target_os = "tvos"), link_name = #macho_section)] + #[cfg_attr(target_os = "windows", link_name = #windows_section)] + #[cfg_attr(target_os = "illumos", link_name = #illumos_section)] + #[cfg_attr(target_os = "freebsd", link_name = #freebsd_section)] + static LINKME_SINGLETON: #ty; + + #[cfg(not(target_os = "windows"))] + #[cfg_attr(any(target_os = "none", target_os = "linux", target_os = "android", target_os = "fuchsia", target_os = "psp"), link_name = #linux_dupcheck_start)] + #[cfg_attr(any(target_os = "macos", target_os = "ios", target_os = "tvos"), link_name = #macho_dupcheck_start)] + #[cfg_attr(target_os = "illumos", link_name = #illumos_dupcheck_start)] + #[cfg_attr(target_os = "freebsd", link_name = #freebsd_dupcheck_start)] + static DUPCHECK_START: #linkme_path::__private::usize; + + #[cfg(not(target_os = "windows"))] + #[cfg_attr(any(target_os = "none", target_os = "linux", target_os = "android", target_os = "fuchsia", target_os = "psp"), link_name = #linux_dupcheck_stop)] + #[cfg_attr(any(target_os = "macos", target_os = "ios", target_os = "tvos"), link_name = #macho_dupcheck_stop)] + #[cfg_attr(target_os = "illumos", link_name = #illumos_dupcheck_stop)] + #[cfg_attr(target_os = "freebsd", link_name = #freebsd_dupcheck_stop)] + static DUPCHECK_STOP: #linkme_path::__private::usize; + } + + #[cfg(target_os = "windows")] + #[link_section = #windows_dupcheck_start] + static DUPCHECK_START: () = (); + + #[cfg(target_os = "windows")] + #[link_section = #windows_dupcheck_stop] + static DUPCHECK_STOP: () = (); + + #used + #[cfg_attr(any(target_os = "none", target_os = "linux", target_os = "android", target_os = "fuchsia", target_os = "psp"), link_section = #linux_dupcheck)] + #[cfg_attr(any(target_os = "macos", target_os = "ios", target_os = "tvos"), link_section = #macho_dupcheck)] + #[cfg_attr(target_os = "windows", link_section = #windows_dupcheck)] + #[cfg_attr(target_os = "illumos", link_section = #illumos_dupcheck)] + #[cfg_attr(target_os = "freebsd", link_section = #freebsd_dupcheck)] + static DUPCHECK: #linkme_path::__private::usize = 1; + + #[cfg(not(any( + target_os = "none", + target_os = "linux", + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "windows", + target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "freebsd", + target_os = "psp", + )))] + #unsupported_platform + + unsafe { + #linkme_path::DisjointedStatic::private_new( + #name, + #linkme_path::__private::ptr::addr_of!(LINKME_SINGLETON), + #linkme_path::__private::ptr::addr_of!(DUPCHECK_START), + #linkme_path::__private::ptr::addr_of!(DUPCHECK_STOP), + ) + } + }; + + #[doc(hidden)] + #[macro_export] + macro_rules! #link_section_macro { + ($item:item) => { + #used + #[cfg_attr(any(target_os = "none", target_os = "linux", target_os = "android", target_os = "fuchsia", target_os = "psp"), export_name = #linux_section)] + #[cfg_attr(any(target_os = "macos", target_os = "ios", target_os = "tvos"), export_name = #macho_section)] + #[cfg_attr(target_os = "windows", export_name = #windows_section)] + #[cfg_attr(target_os = "illumos", export_name = #illumos_section)] + #[cfg_attr(target_os = "freebsd", export_name = #freebsd_section)] + $item + }; + } + + #[doc(hidden)] + #vis use #link_section_macro as #ident; + } +} diff --git a/impl/src/singleton/item.rs b/impl/src/singleton/item.rs new file mode 100644 index 0000000..732bdc1 --- /dev/null +++ b/impl/src/singleton/item.rs @@ -0,0 +1,90 @@ +use crate::{attr, ty}; +use proc_macro2::{Span, TokenStream}; +use quote::{quote, quote_spanned}; +use syn::parse::{Error, Parse, ParseStream, Result}; +use syn::{Attribute, Ident, Path, Token, Type, Visibility}; + +pub struct Item { + attrs: Vec, + vis: Visibility, + ident: Ident, + ty: Type, + expr: TokenStream, + orig_item: Option, + start_span: Span, + end_span: Span, +} + +impl Parse for Item { + fn parse(input: ParseStream) -> Result { + let attrs = input.call(Attribute::parse_outer)?; + let vis: Visibility = input.parse()?; + input.parse::()?; + let mut_token: Option = input.parse()?; + if let Some(mut_token) = mut_token { + return Err(Error::new_spanned( + mut_token, + "static mut is not supported by disjointed_static", + )); + } + let ident: Ident = input.parse()?; + input.parse::()?; + let start_span = input.span(); + let ty: Type = input.parse()?; + let end_span = quote!(#ty).into_iter().last().unwrap().span(); + input.parse::()?; + let mut expr_semi = Vec::from_iter(input.parse::()?); + if let Some(tail) = expr_semi.pop() { + syn::parse2::(TokenStream::from(tail))?; + } + let expr = TokenStream::from_iter(expr_semi); + Ok(Item { + attrs, + vis, + ident, + ty, + expr, + orig_item: None, + start_span, + end_span, + }) + } +} + +pub fn expand(path: Path, input: Item) -> TokenStream { + let mut attrs = input.attrs; + let vis = input.vis; + let ident = input.ident; + let mut ty = input.ty; + let expr = input.expr; + let orig_item = input.orig_item; + + ty::populate_static_lifetimes(&mut ty); + + let linkme_path = match attr::linkme_path(&mut attrs) { + Ok(path) => path, + Err(err) => return err.to_compile_error(), + }; + + let factory = quote_spanned!(input.start_span=> __new); + let get = quote_spanned!(input.end_span=> #factory()); + + quote! { + #path ! { + #(#attrs)* + #vis static #ident : #ty = { + #[allow(clippy::no_effect_underscore_binding)] + unsafe fn __typecheck(_: #linkme_path::__private::Void) { + let #factory = || -> fn() -> &'static #ty { || &#ident }; + unsafe { + #linkme_path::DisjointedStatic::private_typecheck(#path, #get); + } + } + + #expr + }; + } + + #orig_item + } +} diff --git a/impl/src/singleton/mod.rs b/impl/src/singleton/mod.rs new file mode 100644 index 0000000..b54e5ca --- /dev/null +++ b/impl/src/singleton/mod.rs @@ -0,0 +1,2 @@ +pub mod declaration; +pub mod item; diff --git a/impl/src/declaration.rs b/impl/src/slice/declaration.rs similarity index 100% rename from impl/src/declaration.rs rename to impl/src/slice/declaration.rs diff --git a/impl/src/element.rs b/impl/src/slice/element.rs similarity index 100% rename from impl/src/element.rs rename to impl/src/slice/element.rs diff --git a/impl/src/slice/mod.rs b/impl/src/slice/mod.rs new file mode 100644 index 0000000..6afc4fc --- /dev/null +++ b/impl/src/slice/mod.rs @@ -0,0 +1,2 @@ +pub mod declaration; +pub mod element; diff --git a/src/disjointed_static.rs b/src/disjointed_static.rs new file mode 100644 index 0000000..3e9b9af --- /dev/null +++ b/src/disjointed_static.rs @@ -0,0 +1,227 @@ +use core::ops::Deref; + +use crate::ptr::StaticPtr; + +/// A static element which is disjointedly declared in one location and defined +/// in a different location to be linked together. +/// +/// The implementation is based on `link_name` and `export_name` attributes. +/// It does not involve life-before-main or any other runtime initialization on +/// any platform. This is a zero-cost safe abstraction that operates entirely +/// during compilation and linking. +/// +/// Like [`DistributedSlice`], platform-specific linker support is used to +/// detect duplicated declarations. Unlike `DistributedSlice`, duplicated +/// definition results in multiple definition of a linker symbol. The Rust +/// compiler can sometimes spot this and emit a nice error message, but more +/// often improper use of `DisjointedStatic` will result in a linker error. +/// +/// [`DistributedSlice`]: crate::DistributedSlice +/// +/// # Declaration +/// +/// A disjointed static may be declared by writing `#[disjointed_static]` on a +/// static item whose type is some type `T`. +/// +/// ```no_run +/// # #![cfg_attr(feature = "used_linker", feature(used_with_arg))] +/// # +/// # struct Config; +/// # +/// use linkme::disjointed_static; +/// +/// #[disjointed_static] +/// pub static DEFAULT_CONFIG: Config; +/// ``` +/// +/// The attribute rewrites the `T` type of the static into +/// `DisjointedStatic`, so the static in the example technically has type +/// `DisjointedStatic`. +/// +/// ## Definition +/// +/// The item definition for a disjointed static may be registered by a +/// `#[disjointed_static(...)]` attribute in which the path to the disjointed +/// static declaration is given in the parentheses. The initializer is required +/// to be a const expression. +/// +/// The item definition may be registered in the same crate that declares the +/// disjointed static, or in any downstream crate. The definition linked into +/// the final binary will be observed in the disjointed static at runtime. +/// +/// ``` +/// # #![cfg_attr(feature = "used_linker", feature(used_with_arg))] +/// # +/// # mod other_crate { +/// # use linkme::disjointed_static; +/// # +/// # pub struct Config; +/// # +/// # #[disjointed_static] +/// # pub static DEFAULT_CONFIG: Config; +/// # } +/// # +/// # use other_crate::Config; +/// # +/// use linkme::disjointed_static; +/// use other_crate::DEFAULT_CONFIG; +/// +/// #[disjointed_static(DEFAULT_CONFIG)] +/// static CONFIG: Config = Config { +/// /* ... */ +/// }; +/// ``` +/// +/// The compiler will require that the static item type matches with the type +/// of the disjointed static declaration. If the two do not match, the program +/// will not compile. +/// +/// ```compile_fail +/// # #![cfg_attr(feature = "used_linker", feature(used_with_arg))] +/// # +/// # mod other_crate { +/// # use linkme::disjointed_static; +/// # +/// # pub struct Config; +/// # +/// # #[disjointed_static] +/// # pub static DEFAULT_CONFIG: Config; +/// # } +/// # +/// # use linkme::disjointed_static; +/// # use other_crate::DEFAULT_CONFIG; +/// # +/// #[disjointed_static(DEFAULT_CONFIG)] +/// static CONFIG_WTF: usize = 999; +/// ``` +/// +/// ```text +/// error[E0308]: mismatched types +/// --> tests/ui/mismatched_types.rs +/// | +/// LL | #[disjointed_static(DEFAULT_CONFIG)] +/// | ------------------------------------ arguments to this function are incorrect +/// LL | static CONFIG_WTF: usize = 999; +/// | ^^^^^ expected `Config`, found `usize` +/// | +/// = note: expected fn pointer `fn() -> &'static Config` +/// found fn pointer `fn() -> &'static usize` +/// ``` +pub struct DisjointedStatic { + name: &'static str, + singleton: StaticPtr, + dupcheck_start: StaticPtr, + dupcheck_stop: StaticPtr, +} + +impl DisjointedStatic { + #[doc(hidden)] + #[cfg(any( + target_os = "none", + target_os = "linux", + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "android", + target_os = "fuchsia", + target_os = "illumos", + target_os = "freebsd", + target_os = "psp", + ))] + pub const unsafe fn private_new( + name: &'static str, + singleton: *const T, + dupcheck_start: *const usize, + dupcheck_stop: *const usize, + ) -> Self { + DisjointedStatic { + name, + singleton: StaticPtr { ptr: singleton }, + dupcheck_start: StaticPtr { + ptr: dupcheck_start, + }, + dupcheck_stop: StaticPtr { ptr: dupcheck_stop }, + } + } + + #[doc(hidden)] + #[cfg(target_os = "windows")] + pub const unsafe fn private_new( + name: &'static str, + singleton: *const T, + dupcheck_start: *const (), + dupcheck_stop: *const (), + ) -> Self { + DisjointedStatic { + name, + singleton: StaticPtr { ptr: singleton }, + dupcheck_start: StaticPtr { + ptr: dupcheck_start as *const usize, + }, + dupcheck_stop: StaticPtr { + ptr: dupcheck_stop as *const usize, + }, + } + } + + #[doc(hidden)] + #[inline] + pub const fn private_typecheck(self, get: fn() -> &'static T) { + let _ = get; + } +} + +impl DisjointedStatic { + /// Retrieve a static reference to the linked static item. + /// + /// **Note**: Ordinarily this method should not need to be called because + /// `DisjointedStatic` already behaves like `&'static T` in most ways + /// through the power of `Deref`. In particular, function and method calls + /// can all happen directly on the static without calling `static_item()`. + /// + /// ```no_run + /// # #![cfg_attr(feature = "used_linker", feature(used_with_arg))] + /// # + /// # trait Runtime: Sync { + /// # fn run(&self, f: Box); + /// # } + /// # + /// use linkme::disjointed_static; + /// + /// #[disjointed_static] + /// static RUNTIME: &dyn Runtime; + /// + /// #[disjointed_static] + /// static CALLBACK: fn(); + /// + /// fn main() { + /// // Invoke methods on the linked static item. + /// RUNTIME.run(Box::new(|| { /* ... */ })); + /// + /// // Directly call the linked function. + /// CALLBACK(); + /// } + /// ``` + pub fn static_item(self) -> &'static T { + if self.dupcheck_start.ptr.wrapping_add(1) < self.dupcheck_stop.ptr { + panic!("duplicate #[disjointed_static] with name \"{}\"", self.name); + } + + unsafe { &*self.singleton.ptr } + } +} + +impl Copy for DisjointedStatic {} + +impl Clone for DisjointedStatic { + fn clone(&self) -> Self { + *self + } +} + +impl Deref for DisjointedStatic { + type Target = T; + fn deref(&self) -> &'static Self::Target { + self.static_item() + } +} diff --git a/src/distributed_slice.rs b/src/distributed_slice.rs index 757daf9..5dbbc90 100644 --- a/src/distributed_slice.rs +++ b/src/distributed_slice.rs @@ -4,6 +4,7 @@ use core::ops::Deref; use core::slice; use crate::__private::Slice; +use crate::ptr::StaticPtr; /// Collection of static elements that are gathered into a contiguous section of /// the binary by the linker. @@ -74,6 +75,8 @@ use crate::__private::Slice; /// will not compile. /// /// ```compile_fail +/// # #![cfg_attr(feature = "used_linker", feature(used_with_arg))] +/// # /// # mod other_crate { /// # use linkme::distributed_slice; /// # @@ -92,13 +95,15 @@ use crate::__private::Slice; /// /// ```text /// error[E0308]: mismatched types -/// --> src/distributed_slice.rs:65:19 +/// --> tests/ui/mismatched_types.rs /// | -/// 17 | static BENCH_WTF: usize = 999; +/// LL | #[distributed_slice(BENCHMARKS)] +/// | -------------------------------- arguments to this function are incorrect +/// LL | static BENCH_WTF: usize = 999; /// | ^^^^^ expected fn pointer, found `usize` /// | -/// = note: expected fn pointer `fn(&mut other_crate::Bencher)` -/// found type `usize` +/// = note: expected fn pointer `fn() -> &'static for<'a> fn(&'a mut Bencher)` +/// found fn pointer `fn() -> &'static usize` /// ``` /// /// ## Function elements @@ -136,22 +141,6 @@ pub struct DistributedSlice { dupcheck_stop: StaticPtr, } -struct StaticPtr { - ptr: *const T, -} - -unsafe impl Send for StaticPtr {} - -unsafe impl Sync for StaticPtr {} - -impl Copy for StaticPtr {} - -impl Clone for StaticPtr { - fn clone(&self) -> Self { - *self - } -} - impl DistributedSlice<[T]> { #[doc(hidden)] #[cfg(any( diff --git a/src/lib.rs b/src/lib.rs index 9cd789c..0f131c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,7 +147,9 @@ clippy::unused_self )] +mod disjointed_static; mod distributed_slice; +mod ptr; // Not public API. #[doc(hidden)] @@ -156,4 +158,5 @@ pub mod __private; pub use linkme_impl::*; +pub use crate::disjointed_static::DisjointedStatic; pub use crate::distributed_slice::DistributedSlice; diff --git a/src/ptr.rs b/src/ptr.rs new file mode 100644 index 0000000..686bc98 --- /dev/null +++ b/src/ptr.rs @@ -0,0 +1,15 @@ +pub(crate) struct StaticPtr { + pub(crate) ptr: *const T, +} + +unsafe impl Send for StaticPtr {} + +unsafe impl Sync for StaticPtr {} + +impl Copy for StaticPtr {} + +impl Clone for StaticPtr { + fn clone(&self) -> Self { + *self + } +} diff --git a/tests/disjointed_static.rs b/tests/disjointed_static.rs new file mode 100644 index 0000000..90b0280 --- /dev/null +++ b/tests/disjointed_static.rs @@ -0,0 +1,65 @@ +#![cfg_attr(feature = "used_linker", feature(used_with_arg))] +#![deny(unsafe_op_in_unsafe_fn)] +#![allow(unknown_lints, non_local_definitions)] // FIXME + +use linkme::disjointed_static; +use once_cell::sync::Lazy; + +#[disjointed_static] +static SHENANIGANS: i32; + +#[disjointed_static(SHENANIGANS)] +static N: i32 = 9; + +#[test] +fn test() { + assert_eq!(*SHENANIGANS, 9); +} + +#[test] +fn test_non_copy() { + pub struct NonCopy(pub i32); + + #[disjointed_static] + static NONCOPY: NonCopy; + + #[disjointed_static(NONCOPY)] + static ELEMENT: NonCopy = NonCopy(9); + + assert_eq!(NONCOPY.0, 9); +} + +#[test] +fn test_interior_mutable() { + #[disjointed_static] + static MUTABLE: Lazy; + + #[disjointed_static(MUTABLE)] + static ELEMENT: Lazy = Lazy::new(|| -1); + + assert_eq!(*ELEMENT, -1); +} + +#[test] +fn test_elided_lifetime() { + #[disjointed_static] + pub static MYITEM: &str; + + #[disjointed_static(MYITEM)] + static ELEMENT: &str = "..."; + + assert_eq!(*MYITEM, "..."); +} + +#[test] +fn test_legacy_syntax() { + // Rustc older than 1.43 requires an initializer expression. + #[disjointed_static] + pub static LEGACY: &str = ..; + + // Note: unlike disjointed_static, distributed_slice always *requires* that + // a definition item exists when linking a binary, as otherwise the linker + // will complain about the missing symbol. + #[disjointed_static(LEGACY)] + pub static ELEMENT: &str = "..."; +} diff --git a/tests/ui/mismatched_types.rs b/tests/ui/mismatched_types.rs index 34bf059..5364a7c 100644 --- a/tests/ui/mismatched_types.rs +++ b/tests/ui/mismatched_types.rs @@ -1,15 +1,22 @@ #![cfg_attr(feature = "used_linker", feature(used_with_arg))] -use linkme::distributed_slice; +use linkme::{disjointed_static, distributed_slice}; pub struct Bencher; +pub struct Config; #[distributed_slice] pub static BENCHMARKS: [fn(&mut Bencher)]; +#[disjointed_static] +pub static DEFAULT_CONFIG: Config; + #[distributed_slice(BENCHMARKS)] static BENCH_WTF: usize = 999; +#[disjointed_static(DEFAULT_CONFIG)] +static CONFIG_WTF: usize = 999; + #[distributed_slice(BENCHMARKS)] fn wrong_bench_fn<'a>(_: &'a mut ()) {} diff --git a/tests/ui/mismatched_types.stderr b/tests/ui/mismatched_types.stderr index 3ce2fb0..9447862 100644 --- a/tests/ui/mismatched_types.stderr +++ b/tests/ui/mismatched_types.stderr @@ -1,9 +1,9 @@ error[E0308]: mismatched types - --> tests/ui/mismatched_types.rs:11:19 + --> tests/ui/mismatched_types.rs:15:19 | -10 | #[distributed_slice(BENCHMARKS)] +14 | #[distributed_slice(BENCHMARKS)] | -------------------------------- arguments to this function are incorrect -11 | static BENCH_WTF: usize = 999; +15 | static BENCH_WTF: usize = 999; | ^^^^^ expected fn pointer, found `usize` | = note: expected fn pointer `fn() -> &'static for<'a> fn(&'a mut Bencher)` @@ -15,11 +15,27 @@ note: method defined here | ^^^^^^^^^^^^^^^^^ error[E0308]: mismatched types - --> tests/ui/mismatched_types.rs:14:1 + --> tests/ui/mismatched_types.rs:18:20 | -13 | #[distributed_slice(BENCHMARKS)] +17 | #[disjointed_static(DEFAULT_CONFIG)] + | ------------------------------------ arguments to this function are incorrect +18 | static CONFIG_WTF: usize = 999; + | ^^^^^ expected `Config`, found `usize` + | + = note: expected fn pointer `fn() -> &'static Config` + found fn pointer `fn() -> &'static usize` +note: method defined here + --> src/disjointed_static.rs + | + | pub const fn private_typecheck(self, get: fn() -> &'static T) { + | ^^^^^^^^^^^^^^^^^ + +error[E0308]: mismatched types + --> tests/ui/mismatched_types.rs:21:1 + | +20 | #[distributed_slice(BENCHMARKS)] | -------------------------------- arguments to this function are incorrect -14 | fn wrong_bench_fn<'a>(_: &'a mut ()) {} +21 | fn wrong_bench_fn<'a>(_: &'a mut ()) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Bencher`, found `()` | = note: expected fn pointer `fn() -> &'static for<'a> fn(&'a mut Bencher)` diff --git a/tests/ui/single_crate_dupe.rs b/tests/ui/single_crate_dupe.rs new file mode 100644 index 0000000..260004a --- /dev/null +++ b/tests/ui/single_crate_dupe.rs @@ -0,0 +1,17 @@ +#![feature(used_with_arg)] + +mod slice { + use linkme::distributed_slice; + #[distributed_slice] + static STATIC: [u32]; +} + +mod singleton { + use linkme::disjointed_static; + #[disjointed_static] + static STATIC: u32; + #[disjointed_static(STATIC)] + static IMPL: u32 = 9; +} + +fn main() {} diff --git a/tests/ui/single_crate_dupe.stderr b/tests/ui/single_crate_dupe.stderr new file mode 100644 index 0000000..40b783b --- /dev/null +++ b/tests/ui/single_crate_dupe.stderr @@ -0,0 +1,11 @@ +error[E0428]: the name `_linkme_macro_STATIC` is defined multiple times + --> tests/ui/single_crate_dupe.rs:11:5 + | +5 | #[distributed_slice] + | -------------------- previous definition of the macro `_linkme_macro_STATIC` here +... +11 | #[disjointed_static] + | ^^^^^^^^^^^^^^^^^^^^ `_linkme_macro_STATIC` redefined here + | + = note: `_linkme_macro_STATIC` must be defined only once in the macro namespace of this module + = note: this error originates in the attribute macro `disjointed_static` (in Nightly builds, run with -Z macro-backtrace for more info)