use proc_macro2::{Span, TokenStream};
use proc_macro_error::emit_error;
use syn::{
parenthesized,
parse::{Parse, ParseStream},
parse2,
spanned::Spanned,
token::Paren,
Attribute, Path, Token,
};
use super::{parse_to_comma, Parenthesized, CRATE_NAME};
use crate::precondition::{CfgPrecondition, Precondition};
fn is_attr(attr_to_check: &str, path: &Path) -> bool {
if path.is_ident(attr_to_check) {
true
} else if path.segments.len() == 2 {
path.segments[0].ident == *CRATE_NAME && path.segments[1].ident == attr_to_check
} else {
false
}
}
struct PathAndParenthesized {
path: Path,
parentheses: Paren,
content: TokenStream,
}
impl Parse for PathAndParenthesized {
fn parse(input: ParseStream) -> syn::Result<Self> {
let path = input.parse()?;
let content;
let parentheses = parenthesized!(content in input);
Ok(PathAndParenthesized {
path,
parentheses,
content: content.parse()?,
})
}
}
pub(crate) enum Attr<Content> {
WithCfg {
_cfg_attr_keyword: Path,
_outer_parentheses: Paren,
cfg: TokenStream,
_comma: Token![,],
_path: Path,
_inner_parentheses: Paren,
content: Content,
span: Span,
},
WithParen {
_path: Path,
_parentheses: Paren,
content: Content,
span: Span,
},
Direct {
content: Content,
},
}
impl<Content: Parse + Spanned> Attr<Content> {
pub(crate) fn from_inner(target_attr: &str, attribute: &Attribute) -> Option<Attr<Content>> {
if is_attr(target_attr, &attribute.path) {
let Parenthesized {
parentheses,
content,
} = parse2(attribute.tokens.clone())
.map_err(|err| emit_error!(err))
.ok()?;
Some(Attr::WithParen {
_path: attribute.path.clone(),
_parentheses: parentheses,
content: parse2(content).map_err(|err| emit_error!(err)).ok()?,
span: attribute
.pound_token
.span
.join(attribute.bracket_token.span)
.unwrap_or(attribute.bracket_token.span),
})
} else if attribute.path.is_ident("cfg_attr") {
let Parenthesized {
parentheses: outer_parentheses,
content: mut cfg_attr_content,
} = parse2(attribute.tokens.clone())
.map_err(|err| emit_error!(err))
.ok()?;
let (cfg, comma) = parse_to_comma(&mut cfg_attr_content);
let comma = comma?;
let PathAndParenthesized {
path,
parentheses: inner_parentheses,
content,
} = parse2(cfg_attr_content)
.map_err(|err| emit_error!(err))
.ok()?;
if !is_attr(target_attr, &path) {
return None;
}
let span = path
.span()
.join(inner_parentheses.span)
.unwrap_or(inner_parentheses.span);
Some(Attr::WithCfg {
_cfg_attr_keyword: attribute.path.clone(),
_outer_parentheses: outer_parentheses,
cfg,
_comma: comma,
_path: path,
_inner_parentheses: inner_parentheses,
content: parse2(content).map_err(|err| emit_error!(err)).ok()?,
span,
})
} else {
None
}
}
pub(crate) fn content(&self) -> &Content {
match self {
Attr::WithCfg { content, .. } => content,
Attr::WithParen { content, .. } => content,
Attr::Direct { content } => content,
}
}
pub(crate) fn into_content(self) -> (Content, Option<TokenStream>, Span) {
match self {
Attr::WithCfg {
content, cfg, span, ..
} => (content, Some(cfg), span),
Attr::WithParen { content, span, .. } => (content, None, span),
Attr::Direct { content } => {
let span = content.span();
(content, None, span)
}
}
}
}
impl<Content: Spanned> Spanned for Attr<Content> {
fn span(&self) -> Span {
match self {
Attr::WithCfg { span, .. } => *span,
Attr::WithParen { span, .. } => *span,
Attr::Direct { content } => content.span(),
}
}
}
impl<Content> From<Content> for Attr<Content> {
fn from(content: Content) -> Self {
Attr::Direct { content }
}
}
impl<Content: Into<Precondition> + Spanned> From<Attr<Content>> for CfgPrecondition {
fn from(val: Attr<Content>) -> Self {
match val {
Attr::WithCfg {
content, span, cfg, ..
} => CfgPrecondition {
precondition: content.into(),
cfg: Some(cfg),
span,
},
Attr::WithParen { content, span, .. } => CfgPrecondition {
precondition: content.into(),
cfg: None,
span,
},
Attr::Direct { content } => {
let span = content.span();
CfgPrecondition {
precondition: content.into(),
cfg: None,
span,
}
}
}
}
}