Skip to content

Commit

Permalink
Initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
sagebind committed Oct 23, 2020
0 parents commit 6a6eade
Show file tree
Hide file tree
Showing 9 changed files with 360 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
51 changes: 51 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "stability"
version = "0.1.0"
description = "Rust API stability attributes for the rest of us."
authors = ["Stephen M. Coakley <[email protected]>"]
license = "MIT"
repository = "https://github.com/sagebind/stability"
documentation = "https://docs.rs/stability/"
readme = "README.md"
edition = "2018"

[dependencies]
quote = "1"

[dependencies.syn]
version = "1"
features = ["derive", "full"]

[lib]
proc-macro = true

[workspace]
members = ["example"]
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT 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.
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# 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)
[![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.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
13 changes: 13 additions & 0 deletions example/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "stability-example"
version = "0.0.0"
description = "Example crate demonstrating stablity usage."
authors = ["Stephen M. Coakley <[email protected]>"]
edition = "2018"
publish = false

[features]
unstable-risky-function = []

[dependencies.stability]
path = "../"
10 changes: 10 additions & 0 deletions example/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//! This is an example library demonstrating various attributes from the
//! stability crate.
/// This function does something really risky!
///
/// Don't use it yet!
#[stability::unstable(feature = "risky-function")]
pub fn risky_function() {
unimplemented!()
}
81 changes: 81 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//! 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.
use proc_macro::TokenStream;
use syn::{Item, parse_macro_input};

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.
/// - 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.
///
/// # Examples
///
/// ```
/// /// This function does something really risky!
/// ///
/// /// Don't use it yet!
/// #[stability::unstable(feature = "risky-function")]
/// pub fn risky_function() {
/// unimplemented!()
/// }
/// ```
#[proc_macro_attribute]
pub fn unstable(args: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs);
let attr = unstable::UnstableAttribute::from(args);

match parse_macro_input!(item as Item) {
Item::Enum(item_enum) => attr.expand(item_enum),
Item::Fn(item_fn) => attr.expand(item_fn),
Item::Mod(item_mod) => attr.expand(item_mod),
Item::Struct(item_struct) => attr.expand(item_struct),
Item::Trait(item_trait) => attr.expand(item_trait),
_ => panic!("unsupported item type"),
}
}
116 changes: 116 additions & 0 deletions src/unstable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse_quote, Visibility};

#[derive(Debug)]
pub(crate) struct UnstableAttribute {
feature: Option<String>,
}

impl UnstableAttribute {
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();

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.visibility_mut() = 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()
}
}
}

impl From<syn::AttributeArgs> for UnstableAttribute {
fn from(args: syn::AttributeArgs) -> Self {
let mut feature = None;

for arg in args {
match arg {
syn::NestedMeta::Meta(syn::Meta::NameValue(name_value)) => {
if name_value.path.is_ident("feature") {
match name_value.lit {
syn::Lit::Str(s) => feature = Some(s.value()),
_ => panic!(),
}
}
}
_ => {}
}
}

Self {
feature,
}
}
}

pub(crate) trait ItemLike {
fn attrs(&self) -> &[syn::Attribute];

fn push_attr(&mut self, attr: syn::Attribute);

fn visibility(&self) -> &Visibility;

fn visibility_mut(&mut self) -> &mut 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 visibility_mut(&mut self) -> &mut Visibility {
&mut self.vis
}
}
)*
};
}

impl_has_visibility!(syn::ItemEnum, syn::ItemFn, syn::ItemMod, syn::ItemStruct, syn::ItemTrait);

0 comments on commit 6a6eade

Please sign in to comment.