diff --git a/src/ast.rs b/src/ast.rs index 0b37665..50a6d9e 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -140,11 +140,9 @@ pub(crate) struct Attribute { #[derive(Clone, Copy, PartialEq)] pub(crate) enum AttributeKind { - // #[doc ...] - Doc, - // #[inline ...] - Inline, - Other, + TraitOnly, + ImplOnly, + TraitAndImpl, } impl Attribute { @@ -152,7 +150,7 @@ impl Attribute { Self { pound_token: Punct::new('#', Spacing::Alone), tokens: Group::new(Delimiter::Bracket, tokens.into_iter().collect()), - kind: AttributeKind::Other, + kind: AttributeKind::TraitAndImpl, } } } @@ -311,7 +309,7 @@ pub(crate) struct TraitItemType { pub(crate) mod parsing { use std::iter::FromIterator; - use proc_macro::{Delimiter, Punct, Spacing, TokenStream, TokenTree}; + use proc_macro::{Delimiter, Group, Punct, Spacing, TokenStream, TokenTree}; use super::{ Attribute, AttributeKind, BoundLifetimes, ConstParam, FnArg, GenericParam, Generics, @@ -319,7 +317,11 @@ pub(crate) mod parsing { PredicateLifetime, PredicateType, Signature, TypeParam, TypeParamBound, Visibility, WhereClause, WherePredicate, }; - use crate::{error::Result, iter::TokenIter, to_tokens::ToTokens}; + use crate::{ + error::{Error, Result}, + iter::TokenIter, + to_tokens::ToTokens, + }; fn parse_until_punct(input: &mut TokenIter, ch: char) -> Result<(Vec, Punct)> { let mut buf = vec![]; @@ -388,27 +390,68 @@ pub(crate) mod parsing { fn parse_attrs(input: &mut TokenIter) -> Result> { let mut attrs = vec![]; + let mut prev_kind_override: Option<(AttributeKind, Group)> = None; while input.peek_t(&'#') { let pound_token = input.parse_punct('#')?; let tokens = input.parse_group(Delimiter::Bracket)?; - let mut kind = AttributeKind::Other; + let mut kind = AttributeKind::TraitAndImpl; + let mut cur_kind_override = None; let mut iter = TokenIter::new(tokens.stream()); if let Some(TokenTree::Ident(i)) = iter.next() { match iter.next() { // ignore #[path ...] Some(TokenTree::Punct(ref p)) if p.as_char() == ':' => {} - _ => match &*i.to_string() { - "doc" => kind = AttributeKind::Doc, - "inline" => kind = AttributeKind::Inline, + next => match &*i.to_string() { + "doc" => kind = AttributeKind::TraitOnly, + "inline" => kind = AttributeKind::ImplOnly, + "ext_attr" => { + if let Some(next) = next { + match &*next.to_string() { + "(impl_only)" => { + cur_kind_override = Some(AttributeKind::ImplOnly); + } + "(trait_only)" => { + cur_kind_override = Some(AttributeKind::TraitOnly); + } + _ => { + return Err(Error::new( + &tokens, + "invalid attribute tag".into(), + )) + } + } + } else { + return Err(Error::new(&tokens, "missing attribute tag".into())); + } + } _ => {} }, } } + match (prev_kind_override.take(), cur_kind_override) { + (Some((prev_kind_override, _)), None) => { + kind = prev_kind_override; + } + (None, Some(cur_kind_override)) => { + prev_kind_override = Some((cur_kind_override, tokens)); + continue; + } + (Some(_), Some(_)) => { + return Err(Error::new(&tokens, "repeated attribute tag".into())) + } + (None, None) => {} + } + let attr = Attribute { pound_token, tokens, kind }; attrs.push(attr); } - Ok(attrs) + + if let Some((_, tokens)) = prev_kind_override { + Err(Error::new(&tokens, "unused attribute tag".into())) + } else { + Ok(attrs) + } } fn parse_generics(input: &mut TokenIter) -> Result { diff --git a/src/lib.rs b/src/lib.rs index d340a26..e15bbdc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -457,7 +457,8 @@ fn trait_from_impl(item: &mut ItemImpl, trait_name: Ident) -> Result })?; let mut attrs = item.attrs.clone(); - find_remove(&mut item.attrs, AttributeKind::Doc); // https://github.com/taiki-e/easy-ext/issues/20 + find_remove(&mut item.attrs, AttributeKind::TraitOnly); + find_remove(&mut attrs, AttributeKind::ImplOnly); attrs.push(Attribute::new(vec![ TokenTree::Ident(Ident::new("allow", Span::call_site())), TokenTree::Group(Group::new( @@ -522,8 +523,9 @@ fn trait_item_from_impl_item( let vis = mem::replace(&mut impl_const.vis, Visibility::Inherited); check_visibility(vis, prev_vis, impl_vis, &impl_const.ident)?; - let attrs = impl_const.attrs.clone(); - find_remove(&mut impl_const.attrs, AttributeKind::Doc); // https://github.com/taiki-e/easy-ext/issues/20 + let mut attrs = impl_const.attrs.clone(); + find_remove(&mut impl_const.attrs, AttributeKind::TraitOnly); + find_remove(&mut attrs, AttributeKind::ImplOnly); Ok(TraitItem::Const(TraitItemConst { attrs, const_token: impl_const.const_token.clone(), @@ -537,8 +539,9 @@ fn trait_item_from_impl_item( let vis = mem::replace(&mut impl_type.vis, Visibility::Inherited); check_visibility(vis, prev_vis, impl_vis, &impl_type.ident)?; - let attrs = impl_type.attrs.clone(); - find_remove(&mut impl_type.attrs, AttributeKind::Doc); // https://github.com/taiki-e/easy-ext/issues/20 + let mut attrs = impl_type.attrs.clone(); + find_remove(&mut impl_type.attrs, AttributeKind::TraitOnly); + find_remove(&mut attrs, AttributeKind::ImplOnly); Ok(TraitItem::Type(TraitItemType { attrs, type_token: impl_type.type_token.clone(), @@ -552,8 +555,8 @@ fn trait_item_from_impl_item( check_visibility(vis, prev_vis, impl_vis, &impl_method.sig.ident)?; let mut attrs = impl_method.attrs.clone(); - find_remove(&mut impl_method.attrs, AttributeKind::Doc); // https://github.com/taiki-e/easy-ext/issues/20 - find_remove(&mut attrs, AttributeKind::Inline); // `#[inline]` is ignored on function prototypes + find_remove(&mut impl_method.attrs, AttributeKind::TraitOnly); + find_remove(&mut attrs, AttributeKind::ImplOnly); Ok(TraitItem::Method(TraitItemMethod { attrs, sig: { diff --git a/tests/test.rs b/tests/test.rs index a25480e..542aaf9 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -666,3 +666,65 @@ fn arbitrary_self_types() { Rc::new(String::default()).recv_rc_mut(); Box::pin(String::default()).recv_pin_box(); } + +#[test] +fn impl_only_attrs() { + #![deny(unused_variables)] + #![allow(non_camel_case_types)] + + #[ext(E1)] + #[ext_attr(impl_only)] + #[allow(unused_variables)] + impl str { + fn foo(&self) { + let x = 0; + } + } + + #[ext(E2)] + impl str { + #[ext_attr(impl_only)] + #[allow(unused_variables)] + fn foo(&self) { + let x = 0; + } + } + + #[ext(not_camel_case)] + #[ext_attr(impl_only)] + #[deny(non_camel_case_types)] + impl str { + fn foo(&self) {} + } +} + +#[test] +fn trait_only_attrs() { + #![allow(unused_variables)] + #![deny(non_camel_case_types)] + + #[ext(E1)] + #[ext_attr(trait_only)] + #[deny(unused_variables)] + impl str { + fn foo(&self) { + let x = 0; + } + } + + #[ext(E2)] + impl str { + #[ext_attr(trait_only)] + #[deny(unused_variables)] + fn foo(&self) { + let x = 0; + } + } + + #[ext(not_camel_case)] + #[ext_attr(trait_only)] + #[allow(non_camel_case_types)] + impl str { + fn foo(&self) {} + } +}