syntax_ext: Reuse built-in attribute template checking for macro attributes

This commit is contained in:
Vadim Petrochenkov 2019-07-19 02:10:36 +03:00
parent 433024147a
commit 76b1ffaf6c
9 changed files with 132 additions and 113 deletions

View file

@ -36,10 +36,10 @@ use syntax::tokenstream::{TokenTree, TokenStream};
use syntax::ast;
use syntax::ptr::P;
use syntax::ast::Expr;
use syntax::attr::{self, HasAttrs};
use syntax::attr::{self, HasAttrs, AttributeTemplate};
use syntax::source_map::Spanned;
use syntax::edition::Edition;
use syntax::feature_gate::{AttributeGate, AttributeTemplate, AttributeType};
use syntax::feature_gate::{AttributeGate, AttributeType};
use syntax::feature_gate::{Stability, deprecated_attributes};
use syntax_pos::{BytePos, Span, SyntaxContext};
use syntax::symbol::{Symbol, kw, sym};

View file

@ -1,6 +1,9 @@
//! Parsing and validation of builtin attributes
use crate::ast::{self, Attribute, MetaItem, NestedMetaItem};
use crate::early_buffered_lints::BufferedEarlyLintId;
use crate::ext::base::ExtCtxt;
use crate::ext::build::AstBuilder;
use crate::feature_gate::{Features, GatedCfg};
use crate::parse::ParseSess;
@ -19,6 +22,27 @@ enum AttrError {
UnsupportedLiteral(&'static str, /* is_bytestr */ bool),
}
/// A template that the attribute input must match.
/// Only top-level shape (`#[attr]` vs `#[attr(...)]` vs `#[attr = ...]`) is considered now.
#[derive(Clone, Copy)]
pub struct AttributeTemplate {
crate word: bool,
crate list: Option<&'static str>,
crate name_value_str: Option<&'static str>,
}
impl AttributeTemplate {
/// Checks that the given meta-item is compatible with this template.
fn compatible(&self, meta_item_kind: &ast::MetaItemKind) -> bool {
match meta_item_kind {
ast::MetaItemKind::Word => self.word,
ast::MetaItemKind::List(..) => self.list.is_some(),
ast::MetaItemKind::NameValue(lit) if lit.node.is_str() => self.name_value_str.is_some(),
ast::MetaItemKind::NameValue(..) => false,
}
}
}
fn handle_errors(sess: &ParseSess, span: Span, error: AttrError) {
let diag = &sess.span_diagnostic;
match error {
@ -901,3 +925,75 @@ pub fn find_transparency(
let fallback = if is_legacy { Transparency::SemiTransparent } else { Transparency::Opaque };
(transparency.map_or(fallback, |t| t.0), error)
}
pub fn check_builtin_macro_attribute(ecx: &ExtCtxt<'_>, meta_item: &MetaItem, name: Symbol) {
// All the built-in macro attributes are "words" at the moment.
let template = AttributeTemplate { word: true, list: None, name_value_str: None };
let attr = ecx.attribute(meta_item.span, meta_item.clone());
check_builtin_attribute(ecx.parse_sess, &attr, name, template);
}
crate fn check_builtin_attribute(
sess: &ParseSess, attr: &ast::Attribute, name: Symbol, template: AttributeTemplate
) {
// Some special attributes like `cfg` must be checked
// before the generic check, so we skip them here.
let should_skip = |name| name == sym::cfg;
// Some of previously accepted forms were used in practice,
// report them as warnings for now.
let should_warn = |name| name == sym::doc || name == sym::ignore ||
name == sym::inline || name == sym::link;
match attr.parse_meta(sess) {
Ok(meta) => if !should_skip(name) && !template.compatible(&meta.node) {
let error_msg = format!("malformed `{}` attribute input", name);
let mut msg = "attribute must be of the form ".to_owned();
let mut suggestions = vec![];
let mut first = true;
if template.word {
first = false;
let code = format!("#[{}]", name);
msg.push_str(&format!("`{}`", &code));
suggestions.push(code);
}
if let Some(descr) = template.list {
if !first {
msg.push_str(" or ");
}
first = false;
let code = format!("#[{}({})]", name, descr);
msg.push_str(&format!("`{}`", &code));
suggestions.push(code);
}
if let Some(descr) = template.name_value_str {
if !first {
msg.push_str(" or ");
}
let code = format!("#[{} = \"{}\"]", name, descr);
msg.push_str(&format!("`{}`", &code));
suggestions.push(code);
}
if should_warn(name) {
sess.buffer_lint(
BufferedEarlyLintId::IllFormedAttributeInput,
meta.span,
ast::CRATE_NODE_ID,
&msg,
);
} else {
sess.span_diagnostic.struct_span_err(meta.span, &error_msg)
.span_suggestions(
meta.span,
if suggestions.len() == 1 {
"must be of the form"
} else {
"the following are the possible correct uses"
},
suggestions.into_iter(),
Applicability::HasPlaceholders,
).emit();
}
}
Err(mut err) => err.emit(),
}
}

View file

@ -19,8 +19,7 @@ use crate::ast::{
self, AssocTyConstraint, AssocTyConstraintKind, NodeId, GenericParam, GenericParamKind,
PatKind, RangeEnd,
};
use crate::attr;
use crate::early_buffered_lints::BufferedEarlyLintId;
use crate::attr::{self, check_builtin_attribute, AttributeTemplate};
use crate::source_map::Spanned;
use crate::edition::{ALL_EDITIONS, Edition};
use crate::visit::{self, FnKind, Visitor};
@ -906,27 +905,6 @@ pub enum AttributeGate {
Ungated,
}
/// A template that the attribute input must match.
/// Only top-level shape (`#[attr]` vs `#[attr(...)]` vs `#[attr = ...]`) is considered now.
#[derive(Clone, Copy)]
pub struct AttributeTemplate {
word: bool,
list: Option<&'static str>,
name_value_str: Option<&'static str>,
}
impl AttributeTemplate {
/// Checks that the given meta-item is compatible with this template.
fn compatible(&self, meta_item_kind: &ast::MetaItemKind) -> bool {
match meta_item_kind {
ast::MetaItemKind::Word => self.word,
ast::MetaItemKind::List(..) => self.list.is_some(),
ast::MetaItemKind::NameValue(lit) if lit.node.is_str() => self.name_value_str.is_some(),
ast::MetaItemKind::NameValue(..) => false,
}
}
}
/// A convenience macro for constructing attribute templates.
/// E.g., `template!(Word, List: "description")` means that the attribute
/// supports forms `#[attr]` and `#[attr(description)]`.
@ -1901,70 +1879,6 @@ impl<'a> PostExpansionVisitor<'a> {
Abi::System => {}
}
}
fn check_builtin_attribute(&mut self, attr: &ast::Attribute, name: Symbol,
template: AttributeTemplate) {
// Some special attributes like `cfg` must be checked
// before the generic check, so we skip them here.
let should_skip = |name| name == sym::cfg;
// Some of previously accepted forms were used in practice,
// report them as warnings for now.
let should_warn = |name| name == sym::doc || name == sym::ignore ||
name == sym::inline || name == sym::link;
match attr.parse_meta(self.context.parse_sess) {
Ok(meta) => if !should_skip(name) && !template.compatible(&meta.node) {
let error_msg = format!("malformed `{}` attribute input", name);
let mut msg = "attribute must be of the form ".to_owned();
let mut suggestions = vec![];
let mut first = true;
if template.word {
first = false;
let code = format!("#[{}]", name);
msg.push_str(&format!("`{}`", &code));
suggestions.push(code);
}
if let Some(descr) = template.list {
if !first {
msg.push_str(" or ");
}
first = false;
let code = format!("#[{}({})]", name, descr);
msg.push_str(&format!("`{}`", &code));
suggestions.push(code);
}
if let Some(descr) = template.name_value_str {
if !first {
msg.push_str(" or ");
}
let code = format!("#[{} = \"{}\"]", name, descr);
msg.push_str(&format!("`{}`", &code));
suggestions.push(code);
}
if should_warn(name) {
self.context.parse_sess.buffer_lint(
BufferedEarlyLintId::IllFormedAttributeInput,
meta.span,
ast::CRATE_NODE_ID,
&msg,
);
} else {
self.context.parse_sess.span_diagnostic.struct_span_err(meta.span, &error_msg)
.span_suggestions(
meta.span,
if suggestions.len() == 1 {
"must be of the form"
} else {
"the following are the possible correct uses"
},
suggestions.into_iter(),
Applicability::HasPlaceholders,
).emit();
}
}
Err(mut err) => err.emit(),
}
}
}
impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
@ -2005,7 +1919,7 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
match attr_info {
// `rustc_dummy` doesn't have any restrictions specific to built-in attributes.
Some(&(name, _, template, _)) if name != sym::rustc_dummy =>
self.check_builtin_attribute(attr, name, template),
check_builtin_attribute(self.context.parse_sess, attr, name, template),
_ => if let Some(TokenTree::Token(token)) = attr.tokens.trees().next() {
if token == token::Eq {
// All key-value attributes are restricted to meta-item syntax.

View file

@ -1,31 +1,22 @@
use errors::Applicability;
use syntax::ast::{self, Arg, Attribute, Expr, FnHeader, Generics, Ident, Item};
use syntax::ast::{ItemKind, Mutability, Ty, TyKind, Unsafety, VisibilityKind};
use syntax::source_map::respan;
use syntax::ast::{self, Arg, Attribute, Expr, FnHeader, Generics, Ident, Item};
use syntax::attr::check_builtin_macro_attribute;
use syntax::ext::allocator::{AllocatorKind, AllocatorMethod, AllocatorTy, ALLOCATOR_METHODS};
use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::ext::build::AstBuilder;
use syntax::ext::hygiene::SyntaxContext;
use syntax::ptr::P;
use syntax::source_map::respan;
use syntax::symbol::{kw, sym};
use syntax_pos::Span;
pub fn expand(
ecx: &mut ExtCtxt<'_>,
span: Span,
_span: Span,
meta_item: &ast::MetaItem,
item: Annotatable,
) -> Vec<Annotatable> {
if !meta_item.is_word() {
let msg = format!("malformed `{}` attribute input", meta_item.path);
ecx.parse_sess.span_diagnostic.struct_span_err(span, &msg)
.span_suggestion(
span,
"must be of the form",
format!("`#[{}]`", meta_item.path),
Applicability::MachineApplicable
).emit();
}
check_builtin_macro_attribute(ecx, meta_item, sym::global_allocator);
let not_static = |item: Annotatable| {
ecx.parse_sess.span_diagnostic.span_err(item.span(), "allocators must be statics");

View file

@ -1,31 +1,34 @@
/// The expansion from a test function to the appropriate test struct for libtest
/// Ideally, this code would be in libtest but for efficiency and error messages it lives here.
use syntax::ast;
use syntax::attr::{self, check_builtin_macro_attribute};
use syntax::ext::base::*;
use syntax::ext::build::AstBuilder;
use syntax::ext::hygiene::SyntaxContext;
use syntax::attr;
use syntax::ast;
use syntax::print::pprust;
use syntax::symbol::{Symbol, sym};
use syntax_pos::Span;
use std::iter;
pub fn expand_test(
cx: &mut ExtCtxt<'_>,
attr_sp: Span,
_meta_item: &ast::MetaItem,
meta_item: &ast::MetaItem,
item: Annotatable,
) -> Vec<Annotatable> {
check_builtin_macro_attribute(cx, meta_item, sym::test);
expand_test_or_bench(cx, attr_sp, item, false)
}
pub fn expand_bench(
cx: &mut ExtCtxt<'_>,
attr_sp: Span,
_meta_item: &ast::MetaItem,
meta_item: &ast::MetaItem,
item: Annotatable,
) -> Vec<Annotatable> {
check_builtin_macro_attribute(cx, meta_item, sym::bench);
expand_test_or_bench(cx, attr_sp, item, true)
}

View file

@ -9,10 +9,11 @@
// We mark item with an inert attribute "rustc_test_marker" which the test generation
// logic will pick up on.
use syntax::ast;
use syntax::attr::check_builtin_macro_attribute;
use syntax::ext::base::*;
use syntax::ext::build::AstBuilder;
use syntax::ext::hygiene::SyntaxContext;
use syntax::ast;
use syntax::source_map::respan;
use syntax::symbol::sym;
use syntax_pos::Span;
@ -20,9 +21,11 @@ use syntax_pos::Span;
pub fn expand(
ecx: &mut ExtCtxt<'_>,
attr_sp: Span,
_meta_item: &ast::MetaItem,
meta_item: &ast::MetaItem,
anno_item: Annotatable
) -> Vec<Annotatable> {
check_builtin_macro_attribute(ecx, meta_item, sym::test_case);
if !ecx.ecfg.should_test { return vec![]; }
let sp = attr_sp.with_ctxt(SyntaxContext::empty().apply_mark(ecx.current_expansion.id));

View file

@ -2,7 +2,7 @@ error: malformed `global_allocator` attribute input
--> $DIR/allocator-args.rs:10:1
|
LL | #[global_allocator(malloc)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: ``#[global_allocator]``
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[global_allocator]`
error: aborting due to previous error

View file

@ -1,7 +1,13 @@
error: malformed `bench` attribute input
--> $DIR/issue-43106-gating-of-bench.rs:15:1
|
LL | #![bench = "4100"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[bench]`
error[E0601]: `main` function not found in crate `issue_43106_gating_of_bench`
|
= note: consider adding a `main` function to `$DIR/issue-43106-gating-of-bench.rs`
error: aborting due to previous error
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0601`.

View file

@ -1,7 +1,13 @@
error: malformed `test` attribute input
--> $DIR/issue-43106-gating-of-test.rs:10:1
|
LL | #![test = "4200"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[test]`
error[E0601]: `main` function not found in crate `issue_43106_gating_of_test`
|
= note: consider adding a `main` function to `$DIR/issue-43106-gating-of-test.rs`
error: aborting due to previous error
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0601`.