From fb569d3246a679f5428fae3825865d1912b06c7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 6 May 2021 16:38:40 +0100 Subject: [PATCH 01/53] Rework the way sealed items are handled --- src/lib.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fa04aa8..0b4ce22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,9 +63,35 @@ //! ``` use proc_macro::TokenStream; -use quote::quote; +use quote::{format_ident, quote}; use syn::{parse_macro_input, parse_quote}; +macro_rules! SEAL_FORMAT { + () => { + "__seal_for_{}" + }; +} + +macro_rules! parse_quote {( + $($code:tt)* +) => ( + (|| { + fn type_of_some (_: Option) + -> &'static str + { + ::core::any::type_name::() + } + let target_ty = None; if false { return target_ty.unwrap(); } + eprintln!( + "[{}:{}:{}:parse_quote!]\n - ty: `{ty}`\n - code: `{code}`", + file!(), line!(), column!(), + code = ::quote::quote!( $($code)* ), + ty = type_of_some(target_ty), + ); + ::syn::parse_quote!( $($code)* ) + })() +)} + #[proc_macro_attribute] pub fn sealed(_args: TokenStream, input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as syn::Item); @@ -77,7 +103,8 @@ pub fn sealed(_args: TokenStream, input: TokenStream) -> TokenStream { fn parse_sealed(item: syn::Item) -> syn::Result { match item { - syn::Item::Struct(item_struct) => parse_sealed_struct(item_struct), + syn::Item::Impl(item_impl) => parse_sealed_impl(item_impl), + // syn::Item::Struct(item_struct) => parse_sealed_struct(item_struct), syn::Item::Trait(item_trait) => parse_sealed_trait(item_trait), _ => Err(syn::Error::new( proc_macro2::Span::call_site(), @@ -86,22 +113,49 @@ fn parse_sealed(item: syn::Item) -> syn::Result { } } -// Care for https://gist.github.com/Koxiaet/8c05ebd4e0e9347eb05f265dfb7252e1#procedural-macros-support-renaming-the-crate -fn parse_sealed_struct(strct: syn::ItemStruct) -> syn::Result { - let ident = &strct.ident; - Ok(quote!( - #strct - impl private::Sealed for #ident {} - )) -} +// // Care for https://gist.github.com/Koxiaet/8c05ebd4e0e9347eb05f265dfb7252e1#procedural-macros-support-renaming-the-crate +// fn parse_sealed_struct(strct: syn::ItemStruct) -> syn::Result { +// let ident = &strct.ident; +// Ok(quote!( +// #strct +// impl private::Sealed for #ident {} +// )) +// } // Care for https://gist.github.com/Koxiaet/8c05ebd4e0e9347eb05f265dfb7252e1#procedural-macros-support-renaming-the-crate -fn parse_sealed_trait(mut trt: syn::ItemTrait) -> syn::Result { - trt.supertraits.push(parse_quote!(private::Sealed)); +fn parse_sealed_trait(mut item_trait: syn::ItemTrait) -> syn::Result { + let trait_ident = &item_trait.ident; + let seal = format_ident!(SEAL_FORMAT!(), trait_ident); + item_trait.supertraits.push(parse_quote!(#seal::Sealed)); Ok(quote!( - #trt - mod private { + pub(crate) mod #seal { pub trait Sealed {} } + #item_trait )) } + +fn parse_sealed_impl(item_impl: syn::ItemImpl) -> syn::Result { + eprintln!("{:#?}", item_impl); + 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 trait_ident = sealed_path.pop().unwrap().into_value().ident; + + let seal = format_ident!(SEAL_FORMAT!(), trait_ident); + sealed_path.push(parse_quote!(#seal)); + sealed_path.push(parse_quote!(Sealed)); + + let self_type = &item_impl.self_ty; + Ok(quote! { + impl #sealed_path for #self_type {} + #item_impl + }) + } else { + Err(syn::Error::new_spanned( + item_impl, + "missing implentation trait", + )) + } +} From 689b6ccaffaa323a96f91c3258102040e7900d11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 6 May 2021 17:09:57 +0100 Subject: [PATCH 02/53] Implement snake case --- src/lib.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0b4ce22..670c622 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,12 +66,6 @@ use proc_macro::TokenStream; use quote::{format_ident, quote}; use syn::{parse_macro_input, parse_quote}; -macro_rules! SEAL_FORMAT { - () => { - "__seal_for_{}" - }; -} - macro_rules! parse_quote {( $($code:tt)* ) => ( @@ -92,6 +86,15 @@ macro_rules! parse_quote {( })() )} +macro_rules! build_seal { + ($seal:ident) => {{ + // TODO there has to be a better way + use heck::SnakeCase as __heck__SnakeCase; + let seal_ident = $seal.to_string().to_snake_case(); + format_ident!("__seal_for_{}", seal_ident) + }}; +} + #[proc_macro_attribute] pub fn sealed(_args: TokenStream, input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as syn::Item); @@ -125,7 +128,7 @@ fn parse_sealed(item: syn::Item) -> syn::Result { // Care for https://gist.github.com/Koxiaet/8c05ebd4e0e9347eb05f265dfb7252e1#procedural-macros-support-renaming-the-crate fn parse_sealed_trait(mut item_trait: syn::ItemTrait) -> syn::Result { let trait_ident = &item_trait.ident; - let seal = format_ident!(SEAL_FORMAT!(), trait_ident); + let seal = build_seal!(trait_ident); item_trait.supertraits.push(parse_quote!(#seal::Sealed)); Ok(quote!( pub(crate) mod #seal { @@ -142,12 +145,12 @@ fn parse_sealed_impl(item_impl: syn::ItemImpl) -> syn::Result Date: Thu, 6 May 2021 17:23:01 +0100 Subject: [PATCH 03/53] Add generic handling --- src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 670c622..7233710 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,15 +144,16 @@ fn parse_sealed_impl(item_impl: syn::ItemImpl) -> syn::Result Date: Thu, 6 May 2021 17:26:58 +0100 Subject: [PATCH 04/53] Update the dependencies --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 225a4a5..8145582 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,4 +29,5 @@ syn = { version = "1.0", features = ["extra-traits"] } [dependencies] syn = { version = "1.0", features = ["full"] } quote = "1.0" -proc-macro2 = "1.0" \ No newline at end of file +proc-macro2 = "1.0" +heck = "0.3" \ No newline at end of file From 4ea39fba7f7e530d54816ecd2351feeb542f7a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 6 May 2021 17:27:07 +0100 Subject: [PATCH 05/53] Update the demo --- demo/src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/src/main.rs b/demo/src/main.rs index 9fbbade..57e843f 100644 --- a/demo/src/main.rs +++ b/demo/src/main.rs @@ -12,16 +12,16 @@ where state: PhantomData, } -#[sealed] pub struct Idle; +#[sealed] impl DroneState for Idle {} -#[sealed] pub struct Hovering; +#[sealed] impl DroneState for Hovering {} -#[sealed] pub struct Flying; +#[sealed] impl DroneState for Flying {} impl Drone { From 8e20eed2b9e07e9642b78d7eeab564477de7583d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 6 May 2021 17:27:16 +0100 Subject: [PATCH 06/53] Update the examples --- examples/nesting.rs | 34 ++++++++++++++++++++++++++++++++++ examples/simple.rs | 19 +++++++++++++++++++ examples/syn.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 examples/nesting.rs create mode 100644 examples/simple.rs create mode 100644 examples/syn.rs diff --git a/examples/nesting.rs b/examples/nesting.rs new file mode 100644 index 0000000..2886d38 --- /dev/null +++ b/examples/nesting.rs @@ -0,0 +1,34 @@ +use sealed::sealed; + +mod lets { + pub mod attempt { + pub mod some { + pub mod nesting { + use sealed::sealed; + #[sealed] + pub trait LongerSnakeCaseType {} + } + } + } +} + +pub struct A; + +pub struct B { + field_1: i32, +} + +pub struct C; + +#[sealed] +impl lets::attempt::some::nesting::LongerSnakeCaseType for A {} + +#[sealed] +impl lets::attempt::some::nesting::LongerSnakeCaseType for B {} + +// fails to compile +impl lets::attempt::some::nesting::LongerSnakeCaseType for C {} + +fn main() { + return; +} diff --git a/examples/simple.rs b/examples/simple.rs new file mode 100644 index 0000000..abebb91 --- /dev/null +++ b/examples/simple.rs @@ -0,0 +1,19 @@ +use sealed::sealed; + +#[sealed] +pub trait T {} + +pub struct A; +pub struct B { + field_1: i32, +} + +#[sealed] +impl T for A {} +#[sealed] +impl T for B {} + + +fn main() { + return; +} diff --git a/examples/syn.rs b/examples/syn.rs new file mode 100644 index 0000000..d4d9eca --- /dev/null +++ b/examples/syn.rs @@ -0,0 +1,44 @@ +// Example provided in #4 +// https://github.com/jmg-duarte/sealed-rs/issues/4 + +use sealed::sealed; +use proc_macro2::*; +use syn::spanned::Spanned; + +#[sealed] +pub trait AsSpan { + fn as_span(&self) -> Span; +} +// expands to: +// pub trait AsSpan: private::Sealed { +// fn as_span(&self) -> Span; +// } +// mod private { +// pub trait Sealed {} +// } + +#[sealed] +impl AsSpan for Span { + fn as_span(&self) -> Self { + *self + } +} +// expands to: +// impl AsSpan for Span { // foreign type, cannot place #[sealed] +// fn as_span(&self) -> Self { *self } +// } +// impl private::Sealed for Span {} + +#[sealed] +impl AsSpan for &T { + fn as_span(&self) -> Span { + self.span() + } +} +// expands to: +// impl AsSpan for &T { +// fn as_span(&self) -> Span { self.span() } +// } +// impl private::Sealed for &T {} + +fn main() {} From 58229019376859d1b4e0614163135f3809608012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 6 May 2021 17:27:59 +0100 Subject: [PATCH 07/53] Remove debug info --- src/lib.rs | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7233710..9426e19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,26 +66,6 @@ use proc_macro::TokenStream; use quote::{format_ident, quote}; use syn::{parse_macro_input, parse_quote}; -macro_rules! parse_quote {( - $($code:tt)* -) => ( - (|| { - fn type_of_some (_: Option) - -> &'static str - { - ::core::any::type_name::() - } - let target_ty = None; if false { return target_ty.unwrap(); } - eprintln!( - "[{}:{}:{}:parse_quote!]\n - ty: `{ty}`\n - code: `{code}`", - file!(), line!(), column!(), - code = ::quote::quote!( $($code)* ), - ty = type_of_some(target_ty), - ); - ::syn::parse_quote!( $($code)* ) - })() -)} - macro_rules! build_seal { ($seal:ident) => {{ // TODO there has to be a better way @@ -139,7 +119,6 @@ fn parse_sealed_trait(mut item_trait: syn::ItemTrait) -> syn::Result syn::Result { - eprintln!("{:#?}", item_impl); 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 From 639f677af6e61a3c0af98d993df0b0216d8b1960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 6 May 2021 17:28:57 +0100 Subject: [PATCH 08/53] Update examples --- examples/nesting.rs | 9 +-------- examples/simple.rs | 5 +---- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/examples/nesting.rs b/examples/nesting.rs index 2886d38..202b282 100644 --- a/examples/nesting.rs +++ b/examples/nesting.rs @@ -14,11 +14,7 @@ mod lets { pub struct A; -pub struct B { - field_1: i32, -} - -pub struct C; +pub struct B(i32); #[sealed] impl lets::attempt::some::nesting::LongerSnakeCaseType for A {} @@ -26,9 +22,6 @@ impl lets::attempt::some::nesting::LongerSnakeCaseType for A {} #[sealed] impl lets::attempt::some::nesting::LongerSnakeCaseType for B {} -// fails to compile -impl lets::attempt::some::nesting::LongerSnakeCaseType for C {} - fn main() { return; } diff --git a/examples/simple.rs b/examples/simple.rs index abebb91..3c1407f 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -4,16 +4,13 @@ use sealed::sealed; pub trait T {} pub struct A; -pub struct B { - field_1: i32, -} +pub struct B(i32); #[sealed] impl T for A {} #[sealed] impl T for B {} - fn main() { return; } From 52cc65de696ee6895c6e6531827a29e330bcde18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 6 May 2021 17:29:36 +0100 Subject: [PATCH 09/53] Update tests --- tests/{compile/01.rs => fail/01-general.rs} | 8 +++-- .../01.stderr => fail/01-general.stderr} | 4 +-- tests/fail/02-nesting.rs | 30 +++++++++++++++++++ tests/fail/02-nesting.stderr | 10 +++++++ tests/pass/01-long-name.rs | 28 +++++++++++++++++ tests/pass/02-nesting.rs | 29 ++++++++++++++++++ tests/test.rs | 3 +- 7 files changed, 107 insertions(+), 5 deletions(-) rename tests/{compile/01.rs => fail/01-general.rs} (98%) rename tests/{compile/01.stderr => fail/01-general.stderr} (82%) create mode 100644 tests/fail/02-nesting.rs create mode 100644 tests/fail/02-nesting.stderr create mode 100644 tests/pass/01-long-name.rs create mode 100644 tests/pass/02-nesting.rs diff --git a/tests/compile/01.rs b/tests/fail/01-general.rs similarity index 98% rename from tests/compile/01.rs rename to tests/fail/01-general.rs index d9f368a..7e6ad4d 100644 --- a/tests/compile/01.rs +++ b/tests/fail/01-general.rs @@ -1,18 +1,22 @@ use sealed::sealed; -#[sealed] pub struct A; -#[sealed] + pub struct B { field_1: i32, } + pub struct C; #[sealed] trait T {} +#[sealed] impl T for A {} + +#[sealed] impl T for B {} + impl T for C {} fn main() { diff --git a/tests/compile/01.stderr b/tests/fail/01-general.stderr similarity index 82% rename from tests/compile/01.stderr rename to tests/fail/01-general.stderr index 5c38aa6..a8ca728 100644 --- a/tests/compile/01.stderr +++ b/tests/fail/01-general.stderr @@ -1,10 +1,10 @@ error[E0277]: the trait bound `C: Sealed` is not satisfied - --> $DIR/01.rs:16:6 + --> $DIR/01-general.rs:20:6 | 11 | #[sealed] | --------- required by this bound in `T` 12 | trait T {} | - required by a bound in this ... -16 | impl T for C {} +20 | impl T for C {} | ^ the trait `Sealed` is not implemented for `C` diff --git a/tests/fail/02-nesting.rs b/tests/fail/02-nesting.rs new file mode 100644 index 0000000..a5d72fc --- /dev/null +++ b/tests/fail/02-nesting.rs @@ -0,0 +1,30 @@ +use sealed::sealed; + +mod lets { + pub mod attempt { + pub mod some { + pub mod nesting { + use sealed::sealed; + #[sealed] + pub trait T {} + } + } + } +} + +pub struct A; +pub struct B { + field_1: i32, +} +pub struct C; + +#[sealed] +impl lets::attempt::some::nesting::T for A {} +#[sealed] +impl lets::attempt::some::nesting::T for B {} +// fails to compile +impl lets::attempt::some::nesting::T for C {} + +fn main() { + return; +} diff --git a/tests/fail/02-nesting.stderr b/tests/fail/02-nesting.stderr new file mode 100644 index 0000000..3097838 --- /dev/null +++ b/tests/fail/02-nesting.stderr @@ -0,0 +1,10 @@ +error[E0277]: the trait bound `C: Sealed` is not satisfied + --> $DIR/02-nesting.rs:26:6 + | +8 | #[sealed] + | --------- required by this bound in `T` +9 | pub trait T {} + | - required by a bound in this +... +26 | impl lets::attempt::some::nesting::T for C {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Sealed` is not implemented for `C` diff --git a/tests/pass/01-long-name.rs b/tests/pass/01-long-name.rs new file mode 100644 index 0000000..da3afa2 --- /dev/null +++ b/tests/pass/01-long-name.rs @@ -0,0 +1,28 @@ +use sealed::sealed; + +mod lets { + pub mod attempt { + pub mod some { + pub mod nesting { + use sealed::sealed; + #[sealed] + pub trait LongerSnakeCaseType{} + } + } + } +} + +pub struct A; + +pub struct B { + field_1: i32, +} + +#[sealed] +impl lets::attempt::some::nesting::LongerSnakeCaseType for A {} +#[sealed] +impl lets::attempt::some::nesting::LongerSnakeCaseType for B {} + +fn main() { + return; +} diff --git a/tests/pass/02-nesting.rs b/tests/pass/02-nesting.rs new file mode 100644 index 0000000..271c845 --- /dev/null +++ b/tests/pass/02-nesting.rs @@ -0,0 +1,29 @@ +use sealed::sealed; + +mod lets { + pub mod attempt { + pub mod some { + pub mod nesting { + use sealed::sealed; + #[sealed] + pub trait T {} + } + } + } +} + +pub struct A; + +pub struct B { + field_1: i32, +} + +#[sealed] +impl lets::attempt::some::nesting::T for A {} + +#[sealed] +impl lets::attempt::some::nesting::T for B {} + +fn main() { + return; +} diff --git a/tests/test.rs b/tests/test.rs index 02dd27c..e097918 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,5 +1,6 @@ #[test] fn compile_test() { let t = trybuild::TestCases::new(); - t.compile_fail("tests/compile/01.rs"); + t.pass("tests/pass/*.rs"); + t.compile_fail("tests/fail/*.rs"); } From ec7f5f4dff93f5be37d8b81ff63f8b9fe2569980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 6 May 2021 17:35:40 +0100 Subject: [PATCH 10/53] Add support for generics --- src/lib.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9426e19..7a97c9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,11 +108,12 @@ fn parse_sealed(item: syn::Item) -> syn::Result { // Care for https://gist.github.com/Koxiaet/8c05ebd4e0e9347eb05f265dfb7252e1#procedural-macros-support-renaming-the-crate fn parse_sealed_trait(mut item_trait: syn::ItemTrait) -> syn::Result { let trait_ident = &item_trait.ident; + let trait_generics = &item_trait.generics; let seal = build_seal!(trait_ident); - item_trait.supertraits.push(parse_quote!(#seal::Sealed)); + item_trait.supertraits.push(parse_quote!(#seal::Sealed #trait_generics)); Ok(quote!( pub(crate) mod #seal { - pub trait Sealed {} + pub trait Sealed #trait_generics {} } #item_trait )) @@ -123,7 +124,7 @@ fn parse_sealed_impl(item_impl: syn::ItemImpl) -> syn::Result syn::Result Date: Thu, 6 May 2021 17:35:50 +0100 Subject: [PATCH 11/53] Add tests and examples for generics --- examples/generics.rs | 16 ++++++++++++++++ tests/pass/03-generics.rs | 22 ++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 examples/generics.rs create mode 100644 tests/pass/03-generics.rs diff --git a/examples/generics.rs b/examples/generics.rs new file mode 100644 index 0000000..e10ae37 --- /dev/null +++ b/examples/generics.rs @@ -0,0 +1,16 @@ +// Test provided in #4 +// https://github.com/jmg-duarte/sealed-rs/issues/4 + +use sealed::sealed; + +#[sealed] +pub trait Set {} + +#[sealed] +impl Set> for T {} + +#[sealed] +impl Set> for Option {} + + +fn main() {} diff --git a/tests/pass/03-generics.rs b/tests/pass/03-generics.rs new file mode 100644 index 0000000..bec0b12 --- /dev/null +++ b/tests/pass/03-generics.rs @@ -0,0 +1,22 @@ +// Test provided in #4 +// https://github.com/jmg-duarte/sealed-rs/issues/4 + +use sealed::sealed; + +#[sealed] +pub trait Set {} +// pub trait Set: private::Sealed {} +// mod private { +// pub trait Sealed {} +// } + +#[sealed] +impl Set> for T {} +// impl private::Sealed> for T {} + +#[sealed] +impl Set> for Option {} +// impl private::Sealed> for Option {} + + +fn main() {} From e4560e3a7b7171024dcf1949db9e88d0b00c1209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 6 May 2021 17:37:08 +0100 Subject: [PATCH 12/53] Remove old comment --- src/lib.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7a97c9a..14e0ecc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,15 +96,6 @@ fn parse_sealed(item: syn::Item) -> syn::Result { } } -// // Care for https://gist.github.com/Koxiaet/8c05ebd4e0e9347eb05f265dfb7252e1#procedural-macros-support-renaming-the-crate -// fn parse_sealed_struct(strct: syn::ItemStruct) -> syn::Result { -// let ident = &strct.ident; -// Ok(quote!( -// #strct -// impl private::Sealed for #ident {} -// )) -// } - // Care for https://gist.github.com/Koxiaet/8c05ebd4e0e9347eb05f265dfb7252e1#procedural-macros-support-renaming-the-crate fn parse_sealed_trait(mut item_trait: syn::ItemTrait) -> syn::Result { let trait_ident = &item_trait.ident; From 9d3f2714139e717c56752b427a4ca76217bb827d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 6 May 2021 17:37:27 +0100 Subject: [PATCH 13/53] Bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8145582..fc406f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sealed" -version = "0.1.3" +version = "0.2.0" authors = ["José Duarte "] license = "MIT OR Apache-2.0" description = "Macro for sealing traits and structures" From 713517c586a7ad80ed5801501eb5622d04a66f6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 6 May 2021 17:41:03 +0100 Subject: [PATCH 14/53] Add tests for multiple traits --- tests/pass/04-multiple-traits.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/pass/04-multiple-traits.rs diff --git a/tests/pass/04-multiple-traits.rs b/tests/pass/04-multiple-traits.rs new file mode 100644 index 0000000..a2eb166 --- /dev/null +++ b/tests/pass/04-multiple-traits.rs @@ -0,0 +1,24 @@ +use sealed::sealed; + +#[sealed] +pub trait T {} + +#[sealed] +pub trait T1 {} + +pub struct A; +pub struct B(i32); + +#[sealed] +impl T for A {} +#[sealed] +impl T for B {} + +#[sealed] +impl T1 for A {} +#[sealed] +impl T1 for B {} + +fn main() { + return; +} From bb8969c54efb552d2133230e551cc36e9aafca4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 6 May 2021 17:47:12 +0100 Subject: [PATCH 15/53] Update README --- README.md | 66 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 96f7773..40ab2ab 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ as described in the Rust API Guidelines [[1](https://rust-lang.github.io/api-gui ```toml [dependencies] -sealed = "0.1" +sealed = "0.2" ``` ## Example @@ -18,7 +18,7 @@ sealed = "0.1" 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/). +Examples are available in [`examples/`](examples/), you can also see a demo in [`demo/`](demo/). ```rust use sealed::sealed; @@ -26,14 +26,14 @@ use sealed::sealed; #[sealed] trait T {} -#[sealed] pub struct A; +#[sealed] impl T for A {} -#[sealed] pub struct B; +#[sealed] impl T for B {} pub struct C; @@ -47,23 +47,57 @@ fn main() { ## 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. +The `#[sealed]` attribute can be attached to either a `trait` or an `impl`. +It supports: +- Several traits per module +- Generic parameters +- Foreign types +- Blanket `impl`s -### Expansion +## Expansion + +### Input ```rust -// #[sealed] -// trait T {} -trait T: private::Sealed {} -mod private { - trait Sealed {} +use sealed::sealed; + +#[sealed] +pub trait T {} + +pub struct A; +pub struct B(i32); + +#[sealed] +impl T for A {} +#[sealed] +impl T for B {} + +fn main() { + return; +} +``` + +### Expanded + +```rust +use sealed::sealed; + +pub(crate) mod __seal_for_t { + pub trait Sealed {} } +pub trait T: __seal_for_t::Sealed {} -// #[sealed] -// pub struct A; pub struct A; -impl private::Sealed for A {} +pub struct B(i32); + +impl __seal_for_t::Sealed for A {} +impl T for A {} + +impl __seal_for_t::Sealed for B {} +impl T for B {} + +fn main() { + return; +} ``` \ No newline at end of file From 5e1059686f09524d85c47fc524dcd20672e4e778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 6 May 2021 17:53:40 +0100 Subject: [PATCH 16/53] Format --- examples/generics.rs | 1 - examples/syn.rs | 2 +- src/lib.rs | 4 +++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/generics.rs b/examples/generics.rs index e10ae37..b858e32 100644 --- a/examples/generics.rs +++ b/examples/generics.rs @@ -12,5 +12,4 @@ impl Set> for T {} #[sealed] impl Set> for Option {} - fn main() {} diff --git a/examples/syn.rs b/examples/syn.rs index d4d9eca..85b4a33 100644 --- a/examples/syn.rs +++ b/examples/syn.rs @@ -1,8 +1,8 @@ // Example provided in #4 // https://github.com/jmg-duarte/sealed-rs/issues/4 -use sealed::sealed; use proc_macro2::*; +use sealed::sealed; use syn::spanned::Spanned; #[sealed] diff --git a/src/lib.rs b/src/lib.rs index 14e0ecc..7bf1a3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,7 +101,9 @@ fn parse_sealed_trait(mut item_trait: syn::ItemTrait) -> syn::Result Date: Thu, 6 May 2021 17:57:36 +0100 Subject: [PATCH 17/53] More format --- tests/pass/01-long-name.rs | 2 +- tests/pass/03-generics.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/pass/01-long-name.rs b/tests/pass/01-long-name.rs index da3afa2..2cb4ca7 100644 --- a/tests/pass/01-long-name.rs +++ b/tests/pass/01-long-name.rs @@ -6,7 +6,7 @@ mod lets { pub mod nesting { use sealed::sealed; #[sealed] - pub trait LongerSnakeCaseType{} + pub trait LongerSnakeCaseType {} } } } diff --git a/tests/pass/03-generics.rs b/tests/pass/03-generics.rs index bec0b12..2da3074 100644 --- a/tests/pass/03-generics.rs +++ b/tests/pass/03-generics.rs @@ -18,5 +18,4 @@ impl Set> for T {} impl Set> for Option {} // impl private::Sealed> for Option {} - fn main() {} From 0ec94e84177ddb3964625221bc38970fdc21581a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 6 May 2021 22:48:17 +0100 Subject: [PATCH 18/53] Fix version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fc406f5..d8b8c07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sealed" -version = "0.2.0" +version = "0.2.0-rc1" authors = ["José Duarte "] license = "MIT OR Apache-2.0" description = "Macro for sealing traits and structures" From 68e81079ee00c48d84ab7785027f91ac73c44894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 6 May 2021 23:01:17 +0100 Subject: [PATCH 19/53] Use split_for_impl to avoid problems with where clauses --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7bf1a3f..826a274 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,10 +123,10 @@ fn parse_sealed_impl(item_impl: syn::ItemImpl) -> syn::Result Date: Thu, 6 May 2021 23:05:49 +0100 Subject: [PATCH 20/53] Replace the macro with a function --- src/lib.rs | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 826a274..a58a890 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,19 +62,12 @@ //! impl private::Sealed for A {} //! ``` +use std::fmt::Display; + use proc_macro::TokenStream; -use quote::{format_ident, quote}; +use quote::quote; use syn::{parse_macro_input, parse_quote}; -macro_rules! build_seal { - ($seal:ident) => {{ - // TODO there has to be a better way - use heck::SnakeCase as __heck__SnakeCase; - let seal_ident = $seal.to_string().to_snake_case(); - format_ident!("__seal_for_{}", seal_ident) - }}; -} - #[proc_macro_attribute] pub fn sealed(_args: TokenStream, input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as syn::Item); @@ -84,6 +77,28 @@ pub fn sealed(_args: TokenStream, input: TokenStream) -> TokenStream { }) } +// generously offered by @danielhenrymantilla aka Yandros +fn to_snake_case(s: &'_ str) -> String { + let mut ret = String::with_capacity(s.len()); + let mut first = true; + s.bytes().for_each(|c| { + if c.is_ascii_uppercase() { + if !first { + ret.push('_'); + } + ret.push(c.to_ascii_lowercase() as char); + } else { + ret.push(c as char); + } + first = false; + }); + ret +} + +fn seal_name(seal: D) -> syn::Ident { + ::quote::format_ident!("__seal_for_{}", to_snake_case(&seal.to_string()),) +} + fn parse_sealed(item: syn::Item) -> syn::Result { match item { syn::Item::Impl(item_impl) => parse_sealed_impl(item_impl), @@ -100,7 +115,7 @@ fn parse_sealed(item: syn::Item) -> syn::Result { fn parse_sealed_trait(mut item_trait: syn::ItemTrait) -> syn::Result { let trait_ident = &item_trait.ident; let trait_generics = &item_trait.generics; - let seal = build_seal!(trait_ident); + let seal = seal_name(trait_ident); item_trait .supertraits .push(parse_quote!(#seal::Sealed #trait_generics)); @@ -118,7 +133,7 @@ fn parse_sealed_impl(item_impl: syn::ItemImpl) -> syn::Result Date: Thu, 6 May 2021 23:07:39 +0100 Subject: [PATCH 21/53] Remove the heck dep --- Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d8b8c07..8cb5d02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,5 +29,4 @@ syn = { version = "1.0", features = ["extra-traits"] } [dependencies] syn = { version = "1.0", features = ["full"] } quote = "1.0" -proc-macro2 = "1.0" -heck = "0.3" \ No newline at end of file +proc-macro2 = "1.0" \ No newline at end of file From 5c00a613c72bcb25db9b4e3893ebd5e609d39d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 14:17:10 +0100 Subject: [PATCH 22/53] Remove comment --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index a58a890..b94633a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,7 +102,6 @@ fn seal_name(seal: D) -> syn::Ident { fn parse_sealed(item: syn::Item) -> syn::Result { match item { syn::Item::Impl(item_impl) => parse_sealed_impl(item_impl), - // syn::Item::Struct(item_struct) => parse_sealed_struct(item_struct), syn::Item::Trait(item_trait) => parse_sealed_trait(item_trait), _ => Err(syn::Error::new( proc_macro2::Span::call_site(), From 6c41596fdbc3f2f4f2505aa9ec7b44c3cd7ea6f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 14:17:44 +0100 Subject: [PATCH 23/53] Replace Display with fully qualified name --- src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b94633a..9621ad1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,8 +62,6 @@ //! impl private::Sealed for A {} //! ``` -use std::fmt::Display; - use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, parse_quote}; @@ -95,7 +93,7 @@ fn to_snake_case(s: &'_ str) -> String { ret } -fn seal_name(seal: D) -> syn::Ident { +fn seal_name(seal: D) -> syn::Ident { ::quote::format_ident!("__seal_for_{}", to_snake_case(&seal.to_string()),) } From 46e9472db802eaad8e16aae21652a1b9f6c87170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 14:18:11 +0100 Subject: [PATCH 24/53] Replace proc_macro2::TokenStream with TokenStream2 --- src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9621ad1..cdd7612 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,6 +63,7 @@ //! ``` use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::{parse_macro_input, parse_quote}; @@ -97,7 +98,7 @@ fn seal_name(seal: D) -> syn::Ident { ::quote::format_ident!("__seal_for_{}", to_snake_case(&seal.to_string()),) } -fn parse_sealed(item: syn::Item) -> syn::Result { +fn parse_sealed(item: syn::Item) -> syn::Result { match item { syn::Item::Impl(item_impl) => parse_sealed_impl(item_impl), syn::Item::Trait(item_trait) => parse_sealed_trait(item_trait), @@ -109,7 +110,7 @@ fn parse_sealed(item: syn::Item) -> syn::Result { } // Care for https://gist.github.com/Koxiaet/8c05ebd4e0e9347eb05f265dfb7252e1#procedural-macros-support-renaming-the-crate -fn parse_sealed_trait(mut item_trait: syn::ItemTrait) -> syn::Result { +fn parse_sealed_trait(mut item_trait: syn::ItemTrait) -> syn::Result { let trait_ident = &item_trait.ident; let trait_generics = &item_trait.generics; let seal = seal_name(trait_ident); @@ -124,7 +125,7 @@ fn parse_sealed_trait(mut item_trait: syn::ItemTrait) -> syn::Result syn::Result { +fn parse_sealed_impl(item_impl: syn::ItemImpl) -> syn::Result { 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 From 55690921cbe044686b1715215494755873c3f433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 14:19:32 +0100 Subject: [PATCH 25/53] Bump versions --- Cargo.toml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8cb5d02..a749711 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sealed" -version = "0.2.0-rc1" +version = "0.2.0-rc2" authors = ["José Duarte "] license = "MIT OR Apache-2.0" description = "Macro for sealing traits and structures" diff --git a/README.md b/README.md index 40ab2ab..b05b774 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ as described in the Rust API Guidelines [[1](https://rust-lang.github.io/api-gui ```toml [dependencies] -sealed = "0.2" +sealed = "0.2.0-rc2" ``` ## Example From f336c997c7a4d6bada3de714fc5860c9c745d092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 14:22:31 +0100 Subject: [PATCH 26/53] Revert back to heck --- Cargo.toml | 3 ++- src/lib.rs | 21 ++------------------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a749711..74f9c4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,4 +29,5 @@ syn = { version = "1.0", features = ["extra-traits"] } [dependencies] syn = { version = "1.0", features = ["full"] } quote = "1.0" -proc-macro2 = "1.0" \ No newline at end of file +proc-macro2 = "1.0" +heck = "0.3" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index cdd7612..6b7057e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,6 +62,7 @@ //! impl private::Sealed for A {} //! ``` +use heck::SnakeCase; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; @@ -76,26 +77,8 @@ pub fn sealed(_args: TokenStream, input: TokenStream) -> TokenStream { }) } -// generously offered by @danielhenrymantilla aka Yandros -fn to_snake_case(s: &'_ str) -> String { - let mut ret = String::with_capacity(s.len()); - let mut first = true; - s.bytes().for_each(|c| { - if c.is_ascii_uppercase() { - if !first { - ret.push('_'); - } - ret.push(c.to_ascii_lowercase() as char); - } else { - ret.push(c as char); - } - first = false; - }); - ret -} - fn seal_name(seal: D) -> syn::Ident { - ::quote::format_ident!("__seal_for_{}", to_snake_case(&seal.to_string()),) + ::quote::format_ident!("__seal_for_{}", &seal.to_string().to_snake_case()) } fn parse_sealed(item: syn::Item) -> syn::Result { From 170df326e4414d2fb8b703e04db9598930585092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 14:31:14 +0100 Subject: [PATCH 27/53] Address raw idents --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6b7057e..ae11ce7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,7 +66,7 @@ use heck::SnakeCase; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use syn::{parse_macro_input, parse_quote}; +use syn::{ext::IdentExt, parse_macro_input, parse_quote}; #[proc_macro_attribute] pub fn sealed(_args: TokenStream, input: TokenStream) -> TokenStream { @@ -94,7 +94,7 @@ fn parse_sealed(item: syn::Item) -> syn::Result { // Care for https://gist.github.com/Koxiaet/8c05ebd4e0e9347eb05f265dfb7252e1#procedural-macros-support-renaming-the-crate fn parse_sealed_trait(mut item_trait: syn::ItemTrait) -> syn::Result { - let trait_ident = &item_trait.ident; + let trait_ident = &item_trait.ident.unraw(); let trait_generics = &item_trait.generics; let seal = seal_name(trait_ident); item_trait From 7e51a1c6ca6e3f83dbbee630132f767e542aa5ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 14:34:10 +0100 Subject: [PATCH 28/53] Format Cargo.toml --- Cargo.toml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 74f9c4b..85a891d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,14 +10,10 @@ categories = ["development-tools", "rust-patterns"] keywords = ["proc_macro", "sealed", "future-proofing"] readme = "README.md" edition = "2018" -exclude = [ - "images/*" -] +exclude = ["images/*"] [workspace] -members = [ - "demo" -] +members = ["demo"] [lib] proc-macro = true @@ -30,4 +26,4 @@ syn = { version = "1.0", features = ["extra-traits"] } syn = { version = "1.0", features = ["full"] } quote = "1.0" proc-macro2 = "1.0" -heck = "0.3" \ No newline at end of file +heck = "0.3" From ec2516520246c567267d1420bbe176cd37c422cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 14:35:57 +0100 Subject: [PATCH 29/53] Address clippy::unnecessary-wraps --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ae11ce7..e7863b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,7 +84,7 @@ fn seal_name(seal: D) -> syn::Ident { fn parse_sealed(item: syn::Item) -> syn::Result { match item { syn::Item::Impl(item_impl) => parse_sealed_impl(item_impl), - syn::Item::Trait(item_trait) => parse_sealed_trait(item_trait), + syn::Item::Trait(item_trait) => Ok(parse_sealed_trait(item_trait)), _ => Err(syn::Error::new( proc_macro2::Span::call_site(), "expected struct or trait", @@ -93,19 +93,19 @@ fn parse_sealed(item: syn::Item) -> syn::Result { } // Care for https://gist.github.com/Koxiaet/8c05ebd4e0e9347eb05f265dfb7252e1#procedural-macros-support-renaming-the-crate -fn parse_sealed_trait(mut item_trait: syn::ItemTrait) -> syn::Result { +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)); - Ok(quote!( + quote!( pub(crate) mod #seal { pub trait Sealed #trait_generics {} } #item_trait - )) + ) } fn parse_sealed_impl(item_impl: syn::ItemImpl) -> syn::Result { From 99c51418391f9da0fea4574f22773e92a2aa5c25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 14:38:48 +0100 Subject: [PATCH 30/53] Update examples comments --- examples/syn.rs | 8 ++++---- tests/pass/03-generics.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/syn.rs b/examples/syn.rs index 85b4a33..efaf86d 100644 --- a/examples/syn.rs +++ b/examples/syn.rs @@ -10,10 +10,10 @@ pub trait AsSpan { fn as_span(&self) -> Span; } // expands to: -// pub trait AsSpan: private::Sealed { +// pub trait AsSpan: __seal_for_as_span::Sealed { // fn as_span(&self) -> Span; // } -// mod private { +// mod __seal_for_as_span { // pub trait Sealed {} // } @@ -27,7 +27,7 @@ impl AsSpan for Span { // impl AsSpan for Span { // foreign type, cannot place #[sealed] // fn as_span(&self) -> Self { *self } // } -// impl private::Sealed for Span {} +// impl __seal_for_as_span::Sealed for Span {} #[sealed] impl AsSpan for &T { @@ -39,6 +39,6 @@ impl AsSpan for &T { // impl AsSpan for &T { // fn as_span(&self) -> Span { self.span() } // } -// impl private::Sealed for &T {} +// impl __seal_for_as_span::Sealed for &T {} fn main() {} diff --git a/tests/pass/03-generics.rs b/tests/pass/03-generics.rs index 2da3074..be63385 100644 --- a/tests/pass/03-generics.rs +++ b/tests/pass/03-generics.rs @@ -5,17 +5,17 @@ use sealed::sealed; #[sealed] pub trait Set {} -// pub trait Set: private::Sealed {} -// mod private { +// pub trait Set: __seal_for_set::Sealed {} +// mod __seal_for_set { // pub trait Sealed {} // } #[sealed] impl Set> for T {} -// impl private::Sealed> for T {} +// impl __seal_for_set::Sealed> for T {} #[sealed] impl Set> for Option {} -// impl private::Sealed> for Option {} +// impl __seal_for_set::Sealed> for Option {} fn main() {} From 07900bd2fabf6e50ca9cdf6972ebffe7b54e30dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 18:14:51 +0100 Subject: [PATCH 31/53] Remove bound usage since it can break #seal --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e7863b9..b1afb95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,10 +119,10 @@ fn parse_sealed_impl(item_impl: syn::ItemImpl) -> syn::Result { sealed_path.push(parse_quote!(Sealed)); let self_type = &item_impl.self_ty; - let (trait_generics, _, where_clauses) = &item_impl.generics.split_for_impl(); + let trait_generics = &item_impl.generics.split_for_impl().1; Ok(quote! { - impl #trait_generics #sealed_path #arguments for #self_type #where_clauses {} + impl #trait_generics #sealed_path #arguments for #self_type {} #item_impl }) } else { From 1c787dd84d97588e8f224bb4d8874b97e24b1916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 18:15:44 +0100 Subject: [PATCH 32/53] Add automatically_derived attribute --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index b1afb95..e663742 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,6 +101,7 @@ fn parse_sealed_trait(mut item_trait: syn::ItemTrait) -> TokenStream2 { .supertraits .push(parse_quote!(#seal::Sealed #trait_generics)); quote!( + #[automatically_derived] pub(crate) mod #seal { pub trait Sealed #trait_generics {} } @@ -111,6 +112,7 @@ fn parse_sealed_trait(mut item_trait: syn::ItemTrait) -> TokenStream2 { fn parse_sealed_impl(item_impl: syn::ItemImpl) -> syn::Result { 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(); @@ -119,9 +121,13 @@ fn parse_sealed_impl(item_impl: syn::ItemImpl) -> syn::Result { sealed_path.push(parse_quote!(Sealed)); let self_type = &item_impl.self_ty; + + // Only keep the introduced params (no bounds), since + // the bounds may break in the `#seal` submodule. let trait_generics = &item_impl.generics.split_for_impl().1; Ok(quote! { + #[automatically_derive] impl #trait_generics #sealed_path #arguments for #self_type {} #item_impl }) From 18f88cca704ddac75352d432667f1d096bb5239c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 18:17:41 +0100 Subject: [PATCH 33/53] Typo fix --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index e663742..3498c72 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,7 +127,7 @@ fn parse_sealed_impl(item_impl: syn::ItemImpl) -> syn::Result { let trait_generics = &item_impl.generics.split_for_impl().1; Ok(quote! { - #[automatically_derive] + #[automatically_derived] impl #trait_generics #sealed_path #arguments for #self_type {} #item_impl }) From 9b53b0dc1bce56bad6dd539c63fb197a6321ee19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 18:17:48 +0100 Subject: [PATCH 34/53] Change resolver to v2 --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 85a891d..d29992b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ keywords = ["proc_macro", "sealed", "future-proofing"] readme = "README.md" edition = "2018" exclude = ["images/*"] +resolver = "2" [workspace] members = ["demo"] From a158e387cdbb10fc32106b9eca80713ce8d64f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 18:21:49 +0100 Subject: [PATCH 35/53] Address raw idents --- examples/raw-ident.rs | 12 ++++++++++++ src/lib.rs | 2 +- tests/pass/05-raw-ident.rs | 12 ++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 examples/raw-ident.rs create mode 100644 tests/pass/05-raw-ident.rs diff --git a/examples/raw-ident.rs b/examples/raw-ident.rs new file mode 100644 index 0000000..ac0c87c --- /dev/null +++ b/examples/raw-ident.rs @@ -0,0 +1,12 @@ +use sealed::sealed; + +#[sealed] +pub trait r#Pub {} + +#[sealed] +impl r#Pub> for T {} + +#[sealed] +impl r#Pub> for Option {} + +fn main() {} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 3498c72..3c901e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,7 +116,7 @@ fn parse_sealed_impl(item_impl: syn::ItemImpl) -> syn::Result { // 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); + let seal = seal_name(ident.unraw()); sealed_path.push(parse_quote!(#seal)); sealed_path.push(parse_quote!(Sealed)); diff --git a/tests/pass/05-raw-ident.rs b/tests/pass/05-raw-ident.rs new file mode 100644 index 0000000..ac0c87c --- /dev/null +++ b/tests/pass/05-raw-ident.rs @@ -0,0 +1,12 @@ +use sealed::sealed; + +#[sealed] +pub trait r#Pub {} + +#[sealed] +impl r#Pub> for T {} + +#[sealed] +impl r#Pub> for Option {} + +fn main() {} \ No newline at end of file From a9076a4f194554d21fa6e22f3b948214b45253cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 18:23:45 +0100 Subject: [PATCH 36/53] Make parse_sealed_impl return early --- src/lib.rs | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3c901e1..395884c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,31 +110,29 @@ fn parse_sealed_trait(mut item_trait: syn::ItemTrait) -> TokenStream2 { } fn parse_sealed_impl(item_impl: syn::ItemImpl) -> syn::Result { - if let Some(impl_trait) = &item_impl.trait_ { - let mut sealed_path = impl_trait.1.segments.clone(); + let impl_trait = item_impl + .trait_ + .as_ref() + .ok_or_else(|| syn::Error::new_spanned(&item_impl, "missing implentation trait"))?; - // 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.unraw()); - sealed_path.push(parse_quote!(#seal)); - sealed_path.push(parse_quote!(Sealed)); + let mut sealed_path = impl_trait.1.segments.clone(); - let self_type = &item_impl.self_ty; + // 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.unraw()); + sealed_path.push(parse_quote!(#seal)); + sealed_path.push(parse_quote!(Sealed)); - // Only keep the introduced params (no bounds), since - // the bounds may break in the `#seal` submodule. - let trait_generics = &item_impl.generics.split_for_impl().1; + let self_type = &item_impl.self_ty; - Ok(quote! { - #[automatically_derived] - impl #trait_generics #sealed_path #arguments for #self_type {} - #item_impl - }) - } else { - Err(syn::Error::new_spanned( - item_impl, - "missing implentation trait", - )) - } + // Only keep the introduced params (no bounds), since + // the bounds may break in the `#seal` submodule. + let trait_generics = &item_impl.generics.split_for_impl().1; + + Ok(quote! { + #[automatically_derived] + impl #trait_generics #sealed_path #arguments for #self_type {} + #item_impl + }) } From d8c1cd0d886df572e2798eff81c55bd286e047c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 18:24:43 +0100 Subject: [PATCH 37/53] Shorten the seal --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 395884c..d18be6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,7 +78,7 @@ pub fn sealed(_args: TokenStream, input: TokenStream) -> TokenStream { } fn seal_name(seal: D) -> syn::Ident { - ::quote::format_ident!("__seal_for_{}", &seal.to_string().to_snake_case()) + ::quote::format_ident!("__seal_{}", &seal.to_string().to_snake_case()) } fn parse_sealed(item: syn::Item) -> syn::Result { From 71f715c70570561ea58bc1f984dc61194d641056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 18:27:29 +0100 Subject: [PATCH 38/53] Format --- examples/raw-ident.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/raw-ident.rs b/examples/raw-ident.rs index ac0c87c..7e7f880 100644 --- a/examples/raw-ident.rs +++ b/examples/raw-ident.rs @@ -9,4 +9,4 @@ impl r#Pub> for T {} #[sealed] impl r#Pub> for Option {} -fn main() {} \ No newline at end of file +fn main() {} From 6330912ac9df6bcc782fd660f1a733163c6881b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 18:28:19 +0100 Subject: [PATCH 39/53] Update mini-docs --- README.md | 8 ++++---- examples/syn.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b05b774..9c0e036 100644 --- a/README.md +++ b/README.md @@ -83,18 +83,18 @@ fn main() { ```rust use sealed::sealed; -pub(crate) mod __seal_for_t { +pub(crate) mod __seal_t { pub trait Sealed {} } -pub trait T: __seal_for_t::Sealed {} +pub trait T: __seal_t::Sealed {} pub struct A; pub struct B(i32); -impl __seal_for_t::Sealed for A {} +impl __seal_t::Sealed for A {} impl T for A {} -impl __seal_for_t::Sealed for B {} +impl __seal_t::Sealed for B {} impl T for B {} fn main() { diff --git a/examples/syn.rs b/examples/syn.rs index efaf86d..51e5637 100644 --- a/examples/syn.rs +++ b/examples/syn.rs @@ -10,10 +10,10 @@ pub trait AsSpan { fn as_span(&self) -> Span; } // expands to: -// pub trait AsSpan: __seal_for_as_span::Sealed { +// pub trait AsSpan: __seal_as_span::Sealed { // fn as_span(&self) -> Span; // } -// mod __seal_for_as_span { +// mod __seal_as_span { // pub trait Sealed {} // } @@ -27,7 +27,7 @@ impl AsSpan for Span { // impl AsSpan for Span { // foreign type, cannot place #[sealed] // fn as_span(&self) -> Self { *self } // } -// impl __seal_for_as_span::Sealed for Span {} +// impl __seal_as_span::Sealed for Span {} #[sealed] impl AsSpan for &T { @@ -39,6 +39,6 @@ impl AsSpan for &T { // impl AsSpan for &T { // fn as_span(&self) -> Span { self.span() } // } -// impl __seal_for_as_span::Sealed for &T {} +// impl __seal_as_span::Sealed for &T {} fn main() {} From 61c1af10db37730825ac04ef95f68d6447f33bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 18:30:20 +0100 Subject: [PATCH 40/53] Apply clippy pedantic suggestions --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d18be6e..c68eedc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,7 +83,7 @@ fn seal_name(seal: D) -> syn::Ident { fn parse_sealed(item: syn::Item) -> syn::Result { match item { - syn::Item::Impl(item_impl) => parse_sealed_impl(item_impl), + 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(), @@ -109,11 +109,11 @@ fn parse_sealed_trait(mut item_trait: syn::ItemTrait) -> TokenStream2 { ) } -fn parse_sealed_impl(item_impl: syn::ItemImpl) -> syn::Result { +fn parse_sealed_impl(item_impl: &syn::ItemImpl) -> syn::Result { let impl_trait = item_impl .trait_ .as_ref() - .ok_or_else(|| syn::Error::new_spanned(&item_impl, "missing implentation trait"))?; + .ok_or_else(|| syn::Error::new_spanned(item_impl, "missing implentation trait"))?; let mut sealed_path = impl_trait.1.segments.clone(); @@ -128,7 +128,7 @@ fn parse_sealed_impl(item_impl: syn::ItemImpl) -> syn::Result { // Only keep the introduced params (no bounds), since // the bounds may break in the `#seal` submodule. - let trait_generics = &item_impl.generics.split_for_impl().1; + let trait_generics = item_impl.generics.split_for_impl().1; Ok(quote! { #[automatically_derived] From ee0e1964e65eed836085874ea1f81d9ed66eb046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 7 May 2021 18:33:45 +0100 Subject: [PATCH 41/53] Format tests --- tests/pass/05-raw-ident.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pass/05-raw-ident.rs b/tests/pass/05-raw-ident.rs index ac0c87c..7e7f880 100644 --- a/tests/pass/05-raw-ident.rs +++ b/tests/pass/05-raw-ident.rs @@ -9,4 +9,4 @@ impl r#Pub> for T {} #[sealed] impl r#Pub> for Option {} -fn main() {} \ No newline at end of file +fn main() {} From ed37d2ad4be966c013b3edff9ced5dea4b16f259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Mon, 10 May 2021 11:45:36 +0100 Subject: [PATCH 42/53] Bring back the where clauses --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c68eedc..48be558 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,11 +128,11 @@ fn parse_sealed_impl(item_impl: &syn::ItemImpl) -> syn::Result { // Only keep the introduced params (no bounds), since // the bounds may break in the `#seal` submodule. - let trait_generics = item_impl.generics.split_for_impl().1; + let (trait_generics, _, where_clauses) = item_impl.generics.split_for_impl(); Ok(quote! { #[automatically_derived] - impl #trait_generics #sealed_path #arguments for #self_type {} + impl #trait_generics #sealed_path #arguments for #self_type #where_clauses {} #item_impl }) } From f8e73fccf7ad74f895baa4d51b386dfb20724a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Tue, 11 May 2021 20:43:11 +0100 Subject: [PATCH 43/53] Add example --- examples/breakage.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 examples/breakage.rs diff --git a/examples/breakage.rs b/examples/breakage.rs new file mode 100644 index 0000000..e271d24 --- /dev/null +++ b/examples/breakage.rs @@ -0,0 +1,26 @@ +#![feature(associated_type_bounds)] + +use sealed::sealed; + +// trait Foo {} + +// #[sealed] +// trait Trait {} + +// trait Unrelated {} + +// // #[sealed] +// impl Unrelated for () {} + +fn main() { + trait Foo {} + trait Bar {} + + #[sealed(erased)] + trait Trait where T: ?Sized + Foo{} + + struct Implementor {} + + #[sealed(erased)] + impl Trait for Implementor where T: Foo + Bar {} +} From cb3eec6f3d1c83100986d230fe82a442cf97427a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Tue, 11 May 2021 20:43:32 +0100 Subject: [PATCH 44/53] Update example --- examples/breakage.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/breakage.rs b/examples/breakage.rs index e271d24..7f8eebc 100644 --- a/examples/breakage.rs +++ b/examples/breakage.rs @@ -16,11 +16,11 @@ fn main() { trait Foo {} trait Bar {} - #[sealed(erased)] + #[sealed(erase)] trait Trait where T: ?Sized + Foo{} struct Implementor {} - #[sealed(erased)] + #[sealed(erase)] impl Trait for Implementor where T: Foo + Bar {} } From b329e32c1e4eaf5d3b6c3f16bd94ca976458ee46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Tue, 11 May 2021 20:43:53 +0100 Subject: [PATCH 45/53] WIP - start implementing the erase arg --- src/lib.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 15 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 48be558..0993d7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,23 +68,45 @@ use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::{ext::IdentExt, parse_macro_input, parse_quote}; +const TRAIT_ERASURE_ARG_IDENT: &str = "erase"; + #[proc_macro_attribute] -pub fn sealed(_args: TokenStream, input: TokenStream) -> TokenStream { +pub fn sealed(args: TokenStream, input: TokenStream) -> TokenStream { + let erased = parse_macro_input!(args as Option); let input = parse_macro_input!(input as syn::Item); - TokenStream::from(match parse_sealed(input) { - Ok(ts) => ts, - Err(err) => err.to_compile_error(), - }) + if let Some(erased) = erased { + if erased != TRAIT_ERASURE_ARG_IDENT { + syn::Error::new_spanned( + erased, + format!( + "The only accepted argument is `{}`.", + TRAIT_ERASURE_ARG_IDENT + ), + ) + .to_compile_error() + } else { + match parse_sealed(input, true) { + Ok(ts) => ts, + Err(err) => err.to_compile_error(), + } + } + } else { + match parse_sealed(input, false) { + Ok(ts) => ts, + Err(err) => err.to_compile_error(), + } + } + .into() } fn seal_name(seal: D) -> syn::Ident { ::quote::format_ident!("__seal_{}", &seal.to_string().to_snake_case()) } -fn parse_sealed(item: syn::Item) -> syn::Result { +fn parse_sealed(item: syn::Item, erase: bool) -> syn::Result { match item { syn::Item::Impl(item_impl) => parse_sealed_impl(&item_impl), - syn::Item::Trait(item_trait) => Ok(parse_sealed_trait(item_trait)), + syn::Item::Trait(item_trait) => Ok(parse_sealed_trait(item_trait, erase)), _ => Err(syn::Error::new( proc_macro2::Span::call_site(), "expected struct or trait", @@ -93,20 +115,48 @@ fn parse_sealed(item: syn::Item) -> syn::Result { } // Care for https://gist.github.com/Koxiaet/8c05ebd4e0e9347eb05f265dfb7252e1#procedural-macros-support-renaming-the-crate -fn parse_sealed_trait(mut item_trait: syn::ItemTrait) -> TokenStream2 { +fn parse_sealed_trait(mut item_trait: syn::ItemTrait, erase: bool) -> TokenStream2 { let trait_ident = &item_trait.ident.unraw(); let trait_generics = &item_trait.generics; + eprintln!("{:#?}", trait_generics); let seal = seal_name(trait_ident); item_trait .supertraits .push(parse_quote!(#seal::Sealed #trait_generics)); - quote!( - #[automatically_derived] - pub(crate) mod #seal { - pub trait Sealed #trait_generics {} - } - #item_trait - ) + + if erase { + let lifetimes = trait_generics.lifetimes(); + let const_params = trait_generics.const_params(); + let type_params = + trait_generics + .type_params() + .map(|syn::TypeParam { ident, .. }| -> syn::TypeParam { + parse_quote!( #ident : ?Sized ) + }); + let type_params2 = trait_generics + .type_params() + .map(|syn::TypeParam { ident, .. }| ident) + .collect::>(); + eprintln!("{:#?}", type_params2); + quote!( + #[automatically_derived] + pub(crate) mod #seal { + pub trait Sealed< #(#lifetimes ,)* #(#type_params ,)* #(#const_params ,)* > { + // #(type #type_params2;)* + } + } + #item_trait + ) + } else { + quote!( + #[automatically_derived] + pub(crate) mod #seal { + use super::*; + pub trait Sealed #trait_generics {} + } + #item_trait + ) + } } fn parse_sealed_impl(item_impl: &syn::ItemImpl) -> syn::Result { From b82aa9a56232121184a1cb554d871b75f886a71c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Wed, 12 May 2021 11:30:27 +0100 Subject: [PATCH 46/53] Fix bug in erase --- src/lib.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0993d7a..f9e442c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -120,9 +120,13 @@ fn parse_sealed_trait(mut item_trait: syn::ItemTrait, erase: bool) -> TokenStrea let trait_generics = &item_trait.generics; eprintln!("{:#?}", trait_generics); let seal = seal_name(trait_ident); + + let type_params = trait_generics + .type_params() + .map(|syn::TypeParam { ident, .. }| ident); item_trait .supertraits - .push(parse_quote!(#seal::Sealed #trait_generics)); + .push(parse_quote!(#seal::Sealed< #(#type_params, )*>)); if erase { let lifetimes = trait_generics.lifetimes(); @@ -133,17 +137,11 @@ fn parse_sealed_trait(mut item_trait: syn::ItemTrait, erase: bool) -> TokenStrea .map(|syn::TypeParam { ident, .. }| -> syn::TypeParam { parse_quote!( #ident : ?Sized ) }); - let type_params2 = trait_generics - .type_params() - .map(|syn::TypeParam { ident, .. }| ident) - .collect::>(); - eprintln!("{:#?}", type_params2); + quote!( #[automatically_derived] pub(crate) mod #seal { - pub trait Sealed< #(#lifetimes ,)* #(#type_params ,)* #(#const_params ,)* > { - // #(type #type_params2;)* - } + pub trait Sealed< #(#lifetimes ,)* #(#type_params ,)* #(#const_params ,)* > {} } #item_trait ) From 02dc84caaab96fd2e1359d730039f18a1fcb2e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Wed, 12 May 2021 12:10:45 +0100 Subject: [PATCH 47/53] Update examples --- examples/{breakage.rs => bound-erasure-fn.rs} | 12 +----------- examples/bound-erasure.rs | 5 +++++ examples/super.rs | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 11 deletions(-) rename examples/{breakage.rs => bound-erasure-fn.rs} (57%) create mode 100644 examples/bound-erasure.rs create mode 100644 examples/super.rs diff --git a/examples/breakage.rs b/examples/bound-erasure-fn.rs similarity index 57% rename from examples/breakage.rs rename to examples/bound-erasure-fn.rs index 7f8eebc..3e77347 100644 --- a/examples/breakage.rs +++ b/examples/bound-erasure-fn.rs @@ -2,22 +2,12 @@ use sealed::sealed; -// trait Foo {} - -// #[sealed] -// trait Trait {} - -// trait Unrelated {} - -// // #[sealed] -// impl Unrelated for () {} - fn main() { trait Foo {} trait Bar {} #[sealed(erase)] - trait Trait where T: ?Sized + Foo{} + trait Trait {} struct Implementor {} diff --git a/examples/bound-erasure.rs b/examples/bound-erasure.rs new file mode 100644 index 0000000..572d151 --- /dev/null +++ b/examples/bound-erasure.rs @@ -0,0 +1,5 @@ +use sealed::sealed; +trait Foo {} +#[sealed(erase)] +trait Trait {} +fn main() {} diff --git a/examples/super.rs b/examples/super.rs new file mode 100644 index 0000000..b880ec8 --- /dev/null +++ b/examples/super.rs @@ -0,0 +1,16 @@ +#![feature(associated_type_bounds)] + +use sealed::sealed; + +pub trait Foo {} +pub trait Bar {} + +#[sealed] +trait Trait {} + +struct Implementor {} + +#[sealed] +impl Trait for Implementor where T: Foo + Bar {} + +fn main() {} From 81b845c8f6027796c86e619f29aafeafe158f513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Wed, 12 May 2021 12:10:51 +0100 Subject: [PATCH 48/53] Add tests --- tests/pass/06-bounds.rs | 5 +++++ tests/pass/07-bound-erasure.rs | 18 ++++++++++++++++++ tests/pass/08-bound-erasure-fn.rs | 18 ++++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 tests/pass/06-bounds.rs create mode 100644 tests/pass/07-bound-erasure.rs create mode 100644 tests/pass/08-bound-erasure-fn.rs diff --git a/tests/pass/06-bounds.rs b/tests/pass/06-bounds.rs new file mode 100644 index 0000000..572d151 --- /dev/null +++ b/tests/pass/06-bounds.rs @@ -0,0 +1,5 @@ +use sealed::sealed; +trait Foo {} +#[sealed(erase)] +trait Trait {} +fn main() {} diff --git a/tests/pass/07-bound-erasure.rs b/tests/pass/07-bound-erasure.rs new file mode 100644 index 0000000..166a0b5 --- /dev/null +++ b/tests/pass/07-bound-erasure.rs @@ -0,0 +1,18 @@ +use sealed::sealed; + +trait Foo {} +trait Bar {} + +#[sealed(erase)] +trait Trait +where + T: ?Sized + Foo, +{ +} + +struct Implementor {} + +#[sealed(erase)] +impl Trait for Implementor where T: Foo + Bar {} + +fn main() {} diff --git a/tests/pass/08-bound-erasure-fn.rs b/tests/pass/08-bound-erasure-fn.rs new file mode 100644 index 0000000..9be502e --- /dev/null +++ b/tests/pass/08-bound-erasure-fn.rs @@ -0,0 +1,18 @@ +use sealed::sealed; + +fn main() { + trait Foo {} + trait Bar {} + + #[sealed(erase)] + trait Trait + where + T: ?Sized + Foo, + { + } + + struct Implementor {} + + #[sealed(erase)] + impl Trait for Implementor where T: Foo + Bar {} +} From 234d89389c9987f5579fa5d43024e9c29574ce32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Wed, 12 May 2021 12:11:02 +0100 Subject: [PATCH 49/53] Update library --- src/lib.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f9e442c..9fc04db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,7 +75,12 @@ pub fn sealed(args: TokenStream, input: TokenStream) -> TokenStream { let erased = parse_macro_input!(args as Option); let input = parse_macro_input!(input as syn::Item); if let Some(erased) = erased { - if erased != TRAIT_ERASURE_ARG_IDENT { + if erased == TRAIT_ERASURE_ARG_IDENT { + match parse_sealed(input, true) { + Ok(ts) => ts, + Err(err) => err.to_compile_error(), + } + } else { syn::Error::new_spanned( erased, format!( @@ -84,11 +89,6 @@ pub fn sealed(args: TokenStream, input: TokenStream) -> TokenStream { ), ) .to_compile_error() - } else { - match parse_sealed(input, true) { - Ok(ts) => ts, - Err(err) => err.to_compile_error(), - } } } else { match parse_sealed(input, false) { @@ -118,19 +118,20 @@ fn parse_sealed(item: syn::Item, erase: bool) -> syn::Result { fn parse_sealed_trait(mut item_trait: syn::ItemTrait, erase: bool) -> TokenStream2 { let trait_ident = &item_trait.ident.unraw(); let trait_generics = &item_trait.generics; - eprintln!("{:#?}", trait_generics); let seal = seal_name(trait_ident); let type_params = trait_generics .type_params() - .map(|syn::TypeParam { ident, .. }| ident); + .map(|syn::TypeParam { ident, .. }| -> syn::TypeParam { parse_quote!( #ident ) }); + item_trait .supertraits - .push(parse_quote!(#seal::Sealed< #(#type_params, )*>)); + .push(parse_quote!(#seal::Sealed <#(#type_params, )*>)); if erase { let lifetimes = trait_generics.lifetimes(); let const_params = trait_generics.const_params(); + let type_params = trait_generics .type_params() From 7924217e00c9f0181cc63a17fb172b0f43a2f7f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Wed, 12 May 2021 12:16:31 +0100 Subject: [PATCH 50/53] Update README --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 9c0e036..d5723f0 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,13 @@ fn main() { } ``` +## Attributes + +This is the list of attributes that can be used along `#[sealed]`: +- `#[sealed]`: the main attribute macro, without attribute parameters. +- `#[sealed(erase)]`: this option turns on bound erasure. This is useful when using the `#[sealed]` macro inside a function. +For an example, see [`bound-erasure-fn`](tests/pass/08-bound-erasure-fn.rs). + ## Details The `#[sealed]` attribute can be attached to either a `trait` or an `impl`. From d563403ec3eafcd57538c719f14a5bc00ce0ea09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Wed, 12 May 2021 12:16:48 +0100 Subject: [PATCH 51/53] Bump version --- Cargo.toml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d29992b..7ed3f6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sealed" -version = "0.2.0-rc2" +version = "0.2.0-rc3" authors = ["José Duarte "] license = "MIT OR Apache-2.0" description = "Macro for sealing traits and structures" diff --git a/README.md b/README.md index d5723f0..e418651 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ as described in the Rust API Guidelines [[1](https://rust-lang.github.io/api-gui ```toml [dependencies] -sealed = "0.2.0-rc2" +sealed = "0.2.0-rc3" ``` ## Example From 0609d80f0a8c938e52aa78fd3233c8a0f1837e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Wed, 12 May 2021 12:20:25 +0100 Subject: [PATCH 52/53] Remove example with nightly features --- examples/super.rs | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 examples/super.rs diff --git a/examples/super.rs b/examples/super.rs deleted file mode 100644 index b880ec8..0000000 --- a/examples/super.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![feature(associated_type_bounds)] - -use sealed::sealed; - -pub trait Foo {} -pub trait Bar {} - -#[sealed] -trait Trait {} - -struct Implementor {} - -#[sealed] -impl Trait for Implementor where T: Foo + Bar {} - -fn main() {} From 32a150798892cbb85804a237f106918bf6230b2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 14 May 2021 11:59:10 +0100 Subject: [PATCH 53/53] Remove feature --- examples/bound-erasure-fn.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/bound-erasure-fn.rs b/examples/bound-erasure-fn.rs index 3e77347..a749a59 100644 --- a/examples/bound-erasure-fn.rs +++ b/examples/bound-erasure-fn.rs @@ -1,5 +1,3 @@ -#![feature(associated_type_bounds)] - use sealed::sealed; fn main() {