blob: 894b82b839a433879c4ef0aede207e939820f008 [file] [log] [blame]
use crate::syntax::cfg::CfgExpr;
use crate::syntax::namespace::Namespace;
use crate::syntax::report::Errors;
use crate::syntax::Atom::{self, *};
use crate::syntax::{cfg, Derive, Doc, ForeignName};
use proc_macro2::{Ident, TokenStream};
use quote::ToTokens;
use syn::parse::ParseStream;
use syn::{Attribute, Error, Expr, Lit, LitStr, Meta, Path, Result, Token};
// Intended usage:
//
// let mut doc = Doc::new();
// let mut cxx_name = None;
// let mut rust_name = None;
// /* ... */
// let attrs = attrs::parse(
// cx,
// item.attrs,
// attrs::Parser {
// doc: Some(&mut doc),
// cxx_name: Some(&mut cxx_name),
// rust_name: Some(&mut rust_name),
// /* ... */
// ..Default::default()
// },
// );
//
#[derive(Default)]
pub(crate) struct Parser<'a> {
pub cfg: Option<&'a mut CfgExpr>,
pub doc: Option<&'a mut Doc>,
pub derives: Option<&'a mut Vec<Derive>>,
pub repr: Option<&'a mut Option<Atom>>,
pub namespace: Option<&'a mut Namespace>,
pub cxx_name: Option<&'a mut Option<ForeignName>>,
pub rust_name: Option<&'a mut Option<Ident>>,
pub variants_from_header: Option<&'a mut Option<Attribute>>,
pub ignore_unrecognized: bool,
// Suppress clippy needless_update lint ("struct update has no effect, all
// the fields in the struct have already been specified") when preemptively
// writing `..Default::default()`.
pub(crate) _more: (),
}
pub(crate) fn parse(cx: &mut Errors, attrs: Vec<Attribute>, mut parser: Parser) -> OtherAttrs {
let mut passthrough_attrs = Vec::new();
for attr in attrs {
let attr_path = attr.path();
if attr_path.is_ident("doc") {
match parse_doc_attribute(&attr.meta) {
Ok(attr) => {
if let Some(doc) = &mut parser.doc {
match attr {
DocAttribute::Doc(lit) => doc.push(lit),
DocAttribute::Hidden => doc.hidden = true,
}
continue;
}
}
Err(err) => {
cx.push(err);
break;
}
}
} else if attr_path.is_ident("derive") {
match attr.parse_args_with(|attr: ParseStream| parse_derive_attribute(cx, attr)) {
Ok(attr) => {
if let Some(derives) = &mut parser.derives {
derives.extend(attr);
continue;
}
}
Err(err) => {
cx.push(err);
break;
}
}
} else if attr_path.is_ident("repr") {
match attr.parse_args_with(parse_repr_attribute) {
Ok(attr) => {
if let Some(repr) = &mut parser.repr {
**repr = Some(attr);
continue;
}
}
Err(err) => {
cx.push(err);
break;
}
}
} else if attr_path.is_ident("namespace") {
match Namespace::parse_meta(&attr.meta) {
Ok(attr) => {
if let Some(namespace) = &mut parser.namespace {
**namespace = attr;
continue;
}
}
Err(err) => {
cx.push(err);
break;
}
}
} else if attr_path.is_ident("cxx_name") {
match parse_cxx_name_attribute(&attr.meta) {
Ok(attr) => {
if let Some(cxx_name) = &mut parser.cxx_name {
**cxx_name = Some(attr);
continue;
}
}
Err(err) => {
cx.push(err);
break;
}
}
} else if attr_path.is_ident("rust_name") {
match parse_rust_name_attribute(&attr.meta) {
Ok(attr) => {
if let Some(rust_name) = &mut parser.rust_name {
**rust_name = Some(attr);
continue;
}
}
Err(err) => {
cx.push(err);
break;
}
}
} else if attr_path.is_ident("cfg") {
match cfg::parse_attribute(&attr) {
Ok(cfg_expr) => {
if let Some(cfg) = &mut parser.cfg {
cfg.merge(cfg_expr);
passthrough_attrs.push(attr);
continue;
}
}
Err(err) => {
cx.push(err);
break;
}
}
} else if attr_path.is_ident("variants_from_header")
&& cfg!(feature = "experimental-enum-variants-from-header")
{
if let Err(err) = attr.meta.require_path_only() {
cx.push(err);
}
if let Some(variants_from_header) = &mut parser.variants_from_header {
**variants_from_header = Some(attr);
continue;
}
} else if attr_path.is_ident("allow")
|| attr_path.is_ident("warn")
|| attr_path.is_ident("deny")
|| attr_path.is_ident("forbid")
|| attr_path.is_ident("deprecated")
|| attr_path.is_ident("must_use")
{
// https://doc.rust-lang.org/reference/attributes/diagnostics.html
passthrough_attrs.push(attr);
continue;
} else if attr_path.is_ident("serde") {
passthrough_attrs.push(attr);
continue;
} else if attr_path.segments.len() > 1 {
let tool = &attr_path.segments.first().unwrap().ident;
if tool == "rustfmt" {
// Skip, rustfmt only needs to find it in the pre-expansion source file.
continue;
} else if tool == "clippy" {
passthrough_attrs.push(attr);
continue;
}
}
if !parser.ignore_unrecognized {
cx.error(attr, "unsupported attribute");
break;
}
}
OtherAttrs(passthrough_attrs)
}
enum DocAttribute {
Doc(LitStr),
Hidden,
}
mod kw {
syn::custom_keyword!(hidden);
}
fn parse_doc_attribute(meta: &Meta) -> Result<DocAttribute> {
match meta {
Meta::NameValue(meta) => {
if let Expr::Lit(expr) = &meta.value {
if let Lit::Str(lit) = &expr.lit {
return Ok(DocAttribute::Doc(lit.clone()));
}
}
}
Meta::List(meta) => {
meta.parse_args::<kw::hidden>()?;
return Ok(DocAttribute::Hidden);
}
Meta::Path(_) => {}
}
Err(Error::new_spanned(meta, "unsupported doc attribute"))
}
fn parse_derive_attribute(cx: &mut Errors, input: ParseStream) -> Result<Vec<Derive>> {
let paths = input.parse_terminated(Path::parse_mod_style, Token![,])?;
let mut derives = Vec::new();
for path in paths {
if let Some(ident) = path.get_ident() {
if let Some(derive) = Derive::from(ident) {
derives.push(derive);
continue;
}
}
cx.error(path, "unsupported derive");
}
Ok(derives)
}
fn parse_repr_attribute(input: ParseStream) -> Result<Atom> {
let begin = input.cursor();
let ident: Ident = input.parse()?;
if let Some(atom) = Atom::from(&ident) {
match atom {
U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize if input.is_empty() => {
return Ok(atom);
}
_ => {}
}
}
Err(Error::new_spanned(
begin.token_stream(),
"unrecognized repr",
))
}
fn parse_cxx_name_attribute(meta: &Meta) -> Result<ForeignName> {
if let Meta::NameValue(meta) = meta {
match &meta.value {
Expr::Lit(expr) => {
if let Lit::Str(lit) = &expr.lit {
return ForeignName::parse(&lit.value(), lit.span());
}
}
Expr::Path(expr) => {
if let Some(ident) = expr.path.get_ident() {
return ForeignName::parse(&ident.to_string(), ident.span());
}
}
_ => {}
}
}
Err(Error::new_spanned(meta, "unsupported cxx_name attribute"))
}
fn parse_rust_name_attribute(meta: &Meta) -> Result<Ident> {
if let Meta::NameValue(meta) = meta {
match &meta.value {
Expr::Lit(expr) => {
if let Lit::Str(lit) = &expr.lit {
return lit.parse();
}
}
Expr::Path(expr) => {
if let Some(ident) = expr.path.get_ident() {
return Ok(ident.clone());
}
}
_ => {}
}
}
Err(Error::new_spanned(meta, "unsupported rust_name attribute"))
}
#[derive(Clone)]
pub(crate) struct OtherAttrs(Vec<Attribute>);
impl OtherAttrs {
pub(crate) fn none() -> Self {
OtherAttrs(Vec::new())
}
pub(crate) fn extend(&mut self, other: Self) {
self.0.extend(other.0);
}
}
impl ToTokens for OtherAttrs {
fn to_tokens(&self, tokens: &mut TokenStream) {
for attr in &self.0 {
let Attribute {
pound_token,
style,
bracket_token,
meta,
} = attr;
pound_token.to_tokens(tokens);
let _ = style; // ignore; render outer and inner attrs both as outer
bracket_token.surround(tokens, |tokens| meta.to_tokens(tokens));
}
}
}