1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! # `#[sealed]`
//!
//! [<img alt="" src="https://img.shields.io/badge/docs.rs-sealed-success?style=flat-square">](https://docs.rs/sealed)
//! [<img alt="" src="https://img.shields.io/crates/v/sealed?style=flat-square">](https://crates.io/crates/sealed)
//!
//! This crate provides a convenient and simple way to implement the sealed trait pattern,
//! as described in the Rust API Guidelines [[1](https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed)].
//!
//! ```toml
//! [dependencies]
//! sealed = "0.1"
//! ```
//!
//! ## Example
//!
//! In the following code structs `A` and `B` implement the sealed trait `T`,
//! the `C` struct, which is not sealed, will error during compilation.
//!
//! You can see a demo in [`demo/`](demo/).
//!
//! ```rust,compile_fail
//! use sealed::sealed;
//!
//! #[sealed]
//! trait T {}
//!
//! #[sealed]
//! pub struct A;
//!
//! impl T for A {}
//!
//! #[sealed]
//! pub struct B;
//!
//! impl T for B {}
//!
//! pub struct C;
//!
//! impl T for C {} // compile error
//! ```
//!
//! ## Details
//!
//! The macro generates a `private` module when attached to a `trait`
//! (this raises the limitation that the `#[sealed]` macro can only be added to a single trait per module),
//! when attached to a `struct` the generated code simply implements the sealed trait for the respective structure.
//!
//!
//! ### Expansion
//!
//! ```rust
//! // #[sealed]
//! // trait T {}
//! trait T: private::Sealed {}
//! mod private {
//!     pub trait Sealed {}
//! }
//!
//! // #[sealed]
//! // pub struct A;
//! pub struct A;
//! impl private::Sealed for A {}
//! ```

use heck::SnakeCase;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{ext::IdentExt, parse_macro_input, parse_quote};

#[proc_macro_attribute]
pub fn sealed(_args: TokenStream, input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as syn::Item);
    TokenStream::from(match parse_sealed(input) {
        Ok(ts) => ts,
        Err(err) => err.to_compile_error(),
    })
}

fn seal_name<D: ::std::fmt::Display>(seal: D) -> syn::Ident {
    ::quote::format_ident!("__seal_for_{}", &seal.to_string().to_snake_case())
}

fn parse_sealed(item: syn::Item) -> syn::Result<TokenStream2> {
    match item {
        syn::Item::Impl(item_impl) => parse_sealed_impl(item_impl),
        syn::Item::Trait(item_trait) => Ok(parse_sealed_trait(item_trait)),
        _ => Err(syn::Error::new(
            proc_macro2::Span::call_site(),
            "expected struct or trait",
        )),
    }
}

// Care for https://gist.github.com/Koxiaet/8c05ebd4e0e9347eb05f265dfb7252e1#procedural-macros-support-renaming-the-crate
fn parse_sealed_trait(mut item_trait: syn::ItemTrait) -> TokenStream2 {
    let trait_ident = &item_trait.ident.unraw();
    let trait_generics = &item_trait.generics;
    let seal = seal_name(trait_ident);
    item_trait
        .supertraits
        .push(parse_quote!(#seal::Sealed #trait_generics));
    quote!(
        pub(crate) mod #seal {
            pub trait Sealed #trait_generics {}
        }
        #item_trait
    )
}

fn parse_sealed_impl(item_impl: syn::ItemImpl) -> syn::Result<TokenStream2> {
    if let Some(impl_trait) = &item_impl.trait_ {
        let mut sealed_path = impl_trait.1.segments.clone();
        // since `impl for ...` is not allowed, this path will *always* have at least length 1
        // thus both `first` and `last` are safe to unwrap
        let syn::PathSegment { ident, arguments } = sealed_path.pop().unwrap().into_value();
        let seal = seal_name(ident);
        sealed_path.push(parse_quote!(#seal));
        sealed_path.push(parse_quote!(Sealed));

        let self_type = &item_impl.self_ty;
        let (trait_generics, _, where_clauses) = &item_impl.generics.split_for_impl();

        Ok(quote! {
            impl #trait_generics #sealed_path #arguments for #self_type #where_clauses {}
            #item_impl
        })
    } else {
        Err(syn::Error::new_spanned(
            item_impl,
            "missing implentation trait",
        ))
    }
}