stability-0.2.1/.cargo_vcs_info.json0000644000000001360000000000100130400ustar { "git": { "sha1": "241f78729fac623b1f0845784b12f86eb809c459" }, "path_in_vcs": "" }stability-0.2.1/.editorconfig000064400000000000000000000002251046102023000143040ustar 00000000000000[*] indent_style = space indent_size = 4 charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.{yaml,yml}] indent_size = 2 stability-0.2.1/.github/workflows/ci.yml000064400000000000000000000004271046102023000163460ustar 00000000000000name: ci on: [push, pull_request] jobs: test: strategy: matrix: os: - ubuntu-latest - macos-latest - windows-latest runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - run: cargo test --workspace stability-0.2.1/.gitignore000064400000000000000000000000241046102023000136140ustar 00000000000000/target /Cargo.lock stability-0.2.1/CODEOWNERS000064400000000000000000000000301046102023000132140ustar 00000000000000* me@stephencoakley.com stability-0.2.1/Cargo.toml0000644000000016630000000000100110440ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.60" name = "stability" version = "0.2.1" authors = ["Stephen M. Coakley "] description = "Rust API stability attributes for the rest of us." documentation = "https://docs.rs/stability/" readme = "README.md" license = "MIT" repository = "https://github.com/sagebind/stability" [lib] proc-macro = true [dependencies.quote] version = "1" [dependencies.syn] version = "2" features = [ "derive", "full", ] stability-0.2.1/Cargo.toml.orig000064400000000000000000000007601046102023000145220ustar 00000000000000[package] name = "stability" version = "0.2.1" description = "Rust API stability attributes for the rest of us." authors = ["Stephen M. Coakley "] license = "MIT" repository = "https://github.com/sagebind/stability" documentation = "https://docs.rs/stability/" readme = "README.md" edition = "2021" rust-version = "1.60" [dependencies] quote = "1" [dependencies.syn] version = "2" features = ["derive", "full"] [lib] proc-macro = true [workspace] members = ["example"] stability-0.2.1/LICENSE000064400000000000000000000020631046102023000126360ustar 00000000000000MIT License Copyright (c) 2020 Stephen M. Coakley Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. stability-0.2.1/README.md000064400000000000000000000024361046102023000131140ustar 00000000000000# Stability Rust API stability attributes for the rest of us. [![Crates.io](https://img.shields.io/crates/v/stability.svg)](https://crates.io/crates/stability) [![Documentation](https://docs.rs/stability/badge.svg)][documentation] [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) ![Minimum supported Rust version](https://img.shields.io/badge/rustc-1.60+-yellow.svg) [![Build](https://github.com/sagebind/stability/workflows/ci/badge.svg)](https://github.com/sagebind/stability/actions) ## Overview This crate provides attribute macros for specifying API stability of public API items of a crate. For a quick example: ```rust /// This function does something really risky! /// /// Don't use it yet! #[stability::unstable(feature = "risky-function")] pub fn risky_function() { unimplemented!() } ``` Please check out the [documentation] for detailed usage. ## Installation Install via Cargo by adding to your `Cargo.toml` file: ```toml [dependencies] stability = "0.2.1" ``` ### Supported Rust versions The current release is only guaranteed to work with the latest stable Rust compiler. ## License This project's source code and documentation are licensed under the MIT license. See the [LICENSE](LICENSE) file for details. [documentation]: https://docs.rs/stability stability-0.2.1/src/lib.rs000064400000000000000000000126511046102023000135400ustar 00000000000000//! This crate provides attribute macros for specifying API stability of public //! API items of a crate. //! //! The Rust standard library has a concept of [API //! stability](https://rustc-dev-guide.rust-lang.org/stability.html) and custom //! attributes for managing that on a per-item basis, but most of these //! attributes are not available for normal crates to use, with the exception of //! the //! [`#[deprecated]`](https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-deprecated-attribute) //! attribute. This crate seeks to provide similar attributes on stable Rust, //! though tuned more toward what the needs of normal crate authors. //! //! For complete examples of how to use this crate, check out the source code //! for the [`stability-example` //! crate](https://github.com/sagebind/stability/tree/master/example) included //! in the stability repository. //! //! Currently, only the [`#[unstable]`][macro@unstable] attribute is available. //! Please see the documentation of that macro for an explanation on what it //! does and how to use it. use proc_macro::TokenStream; use syn::{parse_macro_input, Item}; mod unstable; /// Mark an API as unstable. /// /// You can apply this attribute to an item in your public API that you would /// like to expose to users, but are not yet ready for general use. This is /// useful when you want to let users try out some new functionality for an API /// you haven't finished testing or designing, or for whatever reason do not /// want to commit any stability guarantees for. /// /// This attribute does the following things to annotated items: /// /// - Changes the visibility of the item from `pub` to `pub(crate)`, unless a /// certain crate feature is enabled. This ensures that internal code within /// the crate can always use the item, but downstream consumers cannot access /// it unless they opt-in to the unstable API. /// - Visibility of certain child items of the annotated item will also be /// changed to match the new item visibility, such as struct fields. Children /// that are not public will not be affected. /// - Child items of annotated modules will *not* have their visibility changed, /// as it might be desirable to be able to re-export them even if the module /// visibility is restricted. You should apply the attribute to each item /// within the module with the same feature name if you want to restrict the /// module's contents itself and not just the module namespace. /// - Appends an "Availability" section to the item's documentation that notes /// that the item is unstable, and indicates the name of the crate feature to /// enable it. /// /// Note that unlike the `#[unstable]` attribute used [in the standard /// library](https://rustc-dev-guide.rust-lang.org/stability.html), this /// attribute does not apply itself recursively to child items. /// /// Applying this attribute to non-`pub` items is pointless and does nothing. /// /// # Arguments /// /// The `unstable` attribute supports optional arguments that can be passed to /// control its behavior. /// /// - `feature`: Specify the name of the unstable feature that should control /// this item's availability. The crate feature will have the string /// `unstable-` prepended to it. If not specified, it will be guarded by a /// catch-all `unstable` feature. /// - `issue`: Provide a link or reference to a tracking issue for the unstable /// feature. This will be included in the item's documentation. /// /// # Examples /// /// We can apply the attribute to a public function like so: /// /// ``` /// /// This function does something really risky! /// /// /// /// Don't use it yet! /// #[stability::unstable(feature = "risky-function")] /// pub fn risky_function() { /// unimplemented!() /// } /// ``` /// /// This will essentially be expanded to the following: /// /// ``` /// /// This function does something really risky! /// /// /// /// Don't use it yet! /// /// /// /// # Availability /// /// /// /// **This API is marked as unstable** and is only available when the /// /// `unstable-risky-function` crate feature is enabled. This comes with no /// /// stability guarantees, and could be changed or removed at any time. /// #[cfg(feature = "unstable-risky-function")] /// pub fn risky_function() { /// unimplemented!() /// } /// /// /// This function does something really risky! /// /// /// /// Don't use it yet! /// #[cfg(not(feature = "unstable-risky-function"))] /// pub(crate) fn risky_function() { /// unimplemented!() /// } /// ``` #[proc_macro_attribute] pub fn unstable(args: TokenStream, input: TokenStream) -> TokenStream { let mut attributes = unstable::UnstableAttribute::default(); let attributes_parser = syn::meta::parser(|meta| attributes.parse(meta)); parse_macro_input!(args with attributes_parser); match parse_macro_input!(input as Item) { Item::Type(item_type) => attributes.expand(item_type), Item::Enum(item_enum) => attributes.expand(item_enum), Item::Struct(item_struct) => attributes.expand(item_struct), Item::Fn(item_fn) => attributes.expand(item_fn), Item::Mod(item_mod) => attributes.expand(item_mod), Item::Trait(item_trait) => attributes.expand(item_trait), Item::Const(item_const) => attributes.expand(item_const), Item::Static(item_static) => attributes.expand(item_static), Item::Use(item_use) => attributes.expand(item_use), _ => panic!("unsupported item type"), } } stability-0.2.1/src/unstable.rs000064400000000000000000000112241046102023000146020ustar 00000000000000use proc_macro::TokenStream; use quote::{quote, ToTokens}; use syn::meta::ParseNestedMeta; use syn::parse::Result; use syn::{parse_quote, Visibility}; #[derive(Debug, Default)] pub(crate) struct UnstableAttribute { feature: Option, issue: Option, } impl UnstableAttribute { pub(crate) fn parse(&mut self, meta: ParseNestedMeta) -> Result<()> { if meta.path.is_ident("feature") { match meta.value()?.parse()? { syn::Lit::Str(s) => self.feature = Some(s.value()), _ => panic!(), } } else if meta.path.is_ident("issue") { match meta.value()?.parse()? { syn::Lit::Str(s) => self.issue = Some(s.value()), _ => panic!(), } } Ok(()) } fn crate_feature_name(&self) -> String { if let Some(name) = self.feature.as_deref() { format!("unstable-{}", name) } else { String::from("unstable") } } pub(crate) fn expand(&self, mut item: impl ItemLike + ToTokens + Clone) -> TokenStream { // We only care about public items. if item.is_public() { let feature_name = self.crate_feature_name(); if let Some(issue) = &self.issue { let doc_addendum = format!( "\n\ # Availability\n\ \n\ **This API is marked as unstable** and is only available when \ the `{}` crate feature is enabled. This comes with no stability \ guarantees, and could be changed or removed at any time.\ \n\ The tracking issue is: `{}`\ ", feature_name, issue ); item.push_attr(parse_quote! { #[doc = #doc_addendum] }); } else { let doc_addendum = format!( "\n\ # Availability\n\ \n\ **This API is marked as unstable** and is only available when \ the `{}` crate feature is enabled. This comes with no stability \ guarantees, and could be changed or removed at any time.\ ", feature_name ); item.push_attr(parse_quote! { #[doc = #doc_addendum] }); } let mut hidden_item = item.clone(); hidden_item.set_visibility(parse_quote! { pub(crate) }); TokenStream::from(quote! { #[cfg(feature = #feature_name)] #item #[cfg(not(feature = #feature_name))] #[allow(dead_code)] #hidden_item }) } else { item.into_token_stream().into() } } } pub(crate) trait ItemLike { fn attrs(&self) -> &[syn::Attribute]; fn push_attr(&mut self, attr: syn::Attribute); fn visibility(&self) -> &Visibility; fn set_visibility(&mut self, visibility: Visibility); fn is_public(&self) -> bool { matches!(self.visibility(), Visibility::Public(_)) } } macro_rules! impl_has_visibility { ($($ty:ty),+ $(,)?) => { $( impl ItemLike for $ty { fn attrs(&self) -> &[syn::Attribute] { &self.attrs } fn push_attr(&mut self, attr: syn::Attribute) { self.attrs.push(attr); } fn visibility(&self) -> &Visibility { &self.vis } fn set_visibility(&mut self, visibility: Visibility) { self.vis = visibility; } } )* }; } impl_has_visibility!( syn::ItemType, syn::ItemEnum, syn::ItemFn, syn::ItemMod, syn::ItemTrait, syn::ItemConst, syn::ItemStatic, syn::ItemUse, ); impl ItemLike for syn::ItemStruct { fn attrs(&self) -> &[syn::Attribute] { &self.attrs } fn push_attr(&mut self, attr: syn::Attribute) { self.attrs.push(attr); } fn visibility(&self) -> &Visibility { &self.vis } fn set_visibility(&mut self, visibility: Visibility) { // Also constrain visibility of all fields to be at most the given // item visibility. self.fields .iter_mut() .filter(|field| matches!(&field.vis, Visibility::Public(_))) .for_each(|field| field.vis = visibility.clone()); self.vis = visibility; } }