Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ditch parse_in_attr #67052

Merged
merged 3 commits into from
Dec 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 62 additions & 44 deletions src/librustc_parse/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,19 @@
//!
//! [#64197]: https://github.com/rust-lang/rust/issues/64197

use crate::validate_attr;
use crate::{parse_in, validate_attr};
use rustc_feature::Features;
use rustc_errors::Applicability;
use syntax::attr::HasAttrs;
use syntax::feature_gate::{feature_err, get_features};
use syntax::attr;
use syntax::ast;
use syntax::ast::{self, Attribute, AttrItem, MetaItem};
use syntax::edition::Edition;
use syntax::mut_visit::*;
use syntax::ptr::P;
use syntax::sess::ParseSess;
use syntax::util::map_in_place::MapInPlace;
use syntax_pos::Span;
use syntax_pos::symbol::sym;

use smallvec::SmallVec;
Expand Down Expand Up @@ -72,6 +73,11 @@ macro_rules! configure {
}
}

const CFG_ATTR_GRAMMAR_HELP: &str = "#[cfg_attr(condition, attribute, other_attribute, ...)]";
const CFG_ATTR_NOTE_REF: &str = "for more information, visit \
<https://doc.rust-lang.org/reference/conditional-compilation.html\
#the-cfg_attr-attribute>";

impl<'a> StripUnconfigured<'a> {
pub fn configure<T: HasAttrs>(&mut self, mut node: T) -> Option<T> {
self.process_cfg_attrs(&mut node);
Expand All @@ -97,34 +103,14 @@ impl<'a> StripUnconfigured<'a> {
/// Gives a compiler warning when the `cfg_attr` contains no attributes and
/// is in the original source file. Gives a compiler error if the syntax of
/// the attribute is incorrect.
fn process_cfg_attr(&mut self, attr: ast::Attribute) -> Vec<ast::Attribute> {
fn process_cfg_attr(&mut self, attr: Attribute) -> Vec<Attribute> {
if !attr.has_name(sym::cfg_attr) {
return vec![attr];
}
if let ast::MacArgs::Empty = attr.get_normal_item().args {
self.sess.span_diagnostic
.struct_span_err(
attr.span,
"malformed `cfg_attr` attribute input",
).span_suggestion(
attr.span,
"missing condition and attribute",
"#[cfg_attr(condition, attribute, other_attribute, ...)]".to_owned(),
Applicability::HasPlaceholders,
).note("for more information, visit \
<https://doc.rust-lang.org/reference/conditional-compilation.html\
#the-cfg_attr-attribute>")
.emit();
return vec![];
}

let res = crate::parse_in_attr(self.sess, &attr, |p| p.parse_cfg_attr());
let (cfg_predicate, expanded_attrs) = match res {
Ok(result) => result,
Err(mut e) => {
e.emit();
return vec![];
}
let (cfg_predicate, expanded_attrs) = match self.parse_cfg_attr(&attr) {
None => return vec![],
Some(r) => r,
};

// Lint on zero attributes in source.
Expand All @@ -135,24 +121,56 @@ impl<'a> StripUnconfigured<'a> {
// At this point we know the attribute is considered used.
attr::mark_used(&attr);

if attr::cfg_matches(&cfg_predicate, self.sess, self.features) {
// We call `process_cfg_attr` recursively in case there's a
// `cfg_attr` inside of another `cfg_attr`. E.g.
// `#[cfg_attr(false, cfg_attr(true, some_attr))]`.
expanded_attrs.into_iter()
.flat_map(|(item, span)| self.process_cfg_attr(attr::mk_attr_from_item(
attr.style,
item,
span,
)))
if !attr::cfg_matches(&cfg_predicate, self.sess, self.features) {
return vec![];
}

// We call `process_cfg_attr` recursively in case there's a
// `cfg_attr` inside of another `cfg_attr`. E.g.
// `#[cfg_attr(false, cfg_attr(true, some_attr))]`.
expanded_attrs
.into_iter()
.flat_map(|(item, span)| {
let attr = attr::mk_attr_from_item(attr.style, item, span);
self.process_cfg_attr(attr)
})
.collect()
} else {
vec![]
}

fn parse_cfg_attr(&self, attr: &Attribute) -> Option<(MetaItem, Vec<(AttrItem, Span)>)> {
match attr.get_normal_item().args {
ast::MacArgs::Delimited(dspan, delim, ref tts) if !tts.is_empty() => {
let msg = "wrong `cfg_attr` delimiters";
validate_attr::check_meta_bad_delim(self.sess, dspan, delim, msg);
match parse_in(self.sess, tts.clone(), "`cfg_attr` input", |p| p.parse_cfg_attr()) {
Ok(r) => return Some(r),
Err(mut e) => e
.help(&format!("the valid syntax is `{}`", CFG_ATTR_GRAMMAR_HELP))
.note(CFG_ATTR_NOTE_REF)
.emit(),
}
}
_ => self.error_malformed_cfg_attr_missing(attr.span),
}
None
}

fn error_malformed_cfg_attr_missing(&self, span: Span) {
self.sess
.span_diagnostic
.struct_span_err(span, "malformed `cfg_attr` attribute input")
.span_suggestion(
span,
"missing condition and attribute",
CFG_ATTR_GRAMMAR_HELP.to_string(),
Applicability::HasPlaceholders,
)
.note(CFG_ATTR_NOTE_REF)
.emit();
}

/// Determines if a node with the given attributes should be included in this configuration.
pub fn in_cfg(&self, attrs: &[ast::Attribute]) -> bool {
pub fn in_cfg(&self, attrs: &[Attribute]) -> bool {
attrs.iter().all(|attr| {
if !is_cfg(attr) {
return true;
Expand Down Expand Up @@ -199,15 +217,15 @@ impl<'a> StripUnconfigured<'a> {
}

/// Visit attributes on expression and statements (but not attributes on items in blocks).
fn visit_expr_attrs(&mut self, attrs: &[ast::Attribute]) {
fn visit_expr_attrs(&mut self, attrs: &[Attribute]) {
// flag the offending attributes
for attr in attrs.iter() {
self.maybe_emit_expr_attr_err(attr);
}
}

/// If attributes are not allowed on expressions, emit an error for `attr`
pub fn maybe_emit_expr_attr_err(&self, attr: &ast::Attribute) {
pub fn maybe_emit_expr_attr_err(&self, attr: &Attribute) {
if !self.features.map(|features| features.stmt_expr_attributes).unwrap_or(true) {
let mut err = feature_err(self.sess,
sym::stmt_expr_attributes,
Expand Down Expand Up @@ -350,7 +368,7 @@ impl<'a> MutVisitor for StripUnconfigured<'a> {
}
}

fn is_cfg(attr: &ast::Attribute) -> bool {
fn is_cfg(attr: &Attribute) -> bool {
attr.check_name(sym::cfg)
}

Expand All @@ -359,8 +377,8 @@ fn is_cfg(attr: &ast::Attribute) -> bool {
pub fn process_configure_mod(
sess: &ParseSess,
cfg_mods: bool,
attrs: &[ast::Attribute],
) -> (bool, Vec<ast::Attribute>) {
attrs: &[Attribute],
) -> (bool, Vec<Attribute>) {
// Don't perform gated feature checking.
let mut strip_unconfigured = StripUnconfigured { sess, features: None };
let mut attrs = attrs.to_owned();
Expand Down
16 changes: 4 additions & 12 deletions src/librustc_parse/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,21 +270,13 @@ pub fn stream_to_parser_with_base_dir<'a>(
}

/// Runs the given subparser `f` on the tokens of the given `attr`'s item.
pub fn parse_in_attr<'a, T>(
pub fn parse_in<'a, T>(
sess: &'a ParseSess,
attr: &ast::Attribute,
tts: TokenStream,
name: &'static str,
mut f: impl FnMut(&mut Parser<'a>) -> PResult<'a, T>,
) -> PResult<'a, T> {
let mut parser = Parser::new(
sess,
// FIXME(#66940, Centril | petrochenkov): refactor this function so it doesn't
// require reconstructing and immediately re-parsing delimiters.
attr.get_normal_item().args.outer_tokens(),
None,
false,
false,
Some("attribute"),
);
let mut parser = Parser::new(sess, tts, None, false, false, Some(name));
let result = f(&mut parser)?;
if parser.token != token::Eof {
parser.unexpected()?;
Expand Down
29 changes: 20 additions & 9 deletions src/librustc_parse/parser/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ impl<'a> Parser<'a> {
Ok(attrs)
}

pub(super) fn parse_unsuffixed_lit(&mut self) -> PResult<'a, ast::Lit> {
crate fn parse_unsuffixed_lit(&mut self) -> PResult<'a, ast::Lit> {
let lit = self.parse_lit()?;
debug!("checking if {:?} is unusuffixed", lit);

Expand All @@ -238,25 +238,36 @@ impl<'a> Parser<'a> {

/// Parses `cfg_attr(pred, attr_item_list)` where `attr_item_list` is comma-delimited.
pub fn parse_cfg_attr(&mut self) -> PResult<'a, (ast::MetaItem, Vec<(ast::AttrItem, Span)>)> {
self.expect(&token::OpenDelim(token::Paren))?;

let cfg_predicate = self.parse_meta_item()?;
self.expect(&token::Comma)?;

// Presumably, the majority of the time there will only be one attr.
let mut expanded_attrs = Vec::with_capacity(1);

while !self.check(&token::CloseDelim(token::Paren)) {
let lo = self.token.span.lo();
while self.token.kind != token::Eof {
let lo = self.token.span;
let item = self.parse_attr_item()?;
expanded_attrs.push((item, self.prev_span.with_lo(lo)));
self.expect_one_of(&[token::Comma], &[token::CloseDelim(token::Paren)])?;
expanded_attrs.push((item, lo.to(self.prev_span)));
if !self.eat(&token::Comma) {
break;
}
}

self.expect(&token::CloseDelim(token::Paren))?;
Ok((cfg_predicate, expanded_attrs))
}

/// Matches `COMMASEP(meta_item_inner)`.
crate fn parse_meta_seq_top(&mut self) -> PResult<'a, Vec<ast::NestedMetaItem>> {
// Presumably, the majority of the time there will only be one attr.
let mut nmis = Vec::with_capacity(1);
while self.token.kind != token::Eof {
nmis.push(self.parse_meta_item_inner()?);
if !self.eat(&token::Comma) {
break;
}
}
Ok(nmis)
}

/// Matches the following grammar (per RFC 1559).
///
/// meta_item : PATH ( '=' UNSUFFIXED_LIT | '(' meta_item_inner? ')' )? ;
Expand Down
37 changes: 0 additions & 37 deletions src/librustc_parse/parser/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use crate::maybe_whole;
use rustc_errors::{PResult, Applicability, pluralize};
use syntax::ast::{self, QSelf, Path, PathSegment, Ident, ParenthesizedArgs, AngleBracketedArgs};
use syntax::ast::{AnonConst, GenericArg, AssocTyConstraint, AssocTyConstraintKind, BlockCheckMode};
use syntax::ast::MacArgs;
use syntax::ThinVec;
use syntax::token::{self, Token};
use syntax_pos::source_map::{Span, BytePos};
Expand Down Expand Up @@ -109,42 +108,6 @@ impl<'a> Parser<'a> {
Ok(Path { segments, span: lo.to(self.prev_span) })
}

/// Like `parse_path`, but also supports parsing `Word` meta items into paths for
/// backwards-compatibility. This is used when parsing derive macro paths in `#[derive]`
/// attributes.
fn parse_path_allowing_meta(&mut self, style: PathStyle) -> PResult<'a, Path> {
let meta_ident = match self.token.kind {
token::Interpolated(ref nt) => match **nt {
token::NtMeta(ref item) => match item.args {
MacArgs::Empty => Some(item.path.clone()),
_ => None,
},
_ => None,
},
_ => None,
};
if let Some(path) = meta_ident {
self.bump();
return Ok(path);
}
self.parse_path(style)
}

/// Parse a list of paths inside `#[derive(path_0, ..., path_n)]`.
pub fn parse_derive_paths(&mut self) -> PResult<'a, Vec<Path>> {
self.expect(&token::OpenDelim(token::Paren))?;
let mut list = Vec::new();
while !self.eat(&token::CloseDelim(token::Paren)) {
let path = self.parse_path_allowing_meta(PathStyle::Mod)?;
list.push(path);
if !self.eat(&token::Comma) {
self.expect(&token::CloseDelim(token::Paren))?;
break
}
}
Ok(list)
}

pub(super) fn parse_path_segments(
&mut self,
segments: &mut Vec<PathSegment>,
Expand Down
38 changes: 35 additions & 3 deletions src/librustc_parse/validate_attr.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
//! Meta-syntax validation logic of attributes for post-expansion.

use crate::parse_in;

use rustc_errors::{PResult, Applicability};
use rustc_feature::{AttributeTemplate, BUILTIN_ATTRIBUTE_MAP};
use syntax::ast::{self, Attribute, AttrKind, Ident, MacArgs, MetaItem, MetaItemKind};
use syntax::ast::{self, Attribute, AttrKind, Ident, MacArgs, MacDelimiter, MetaItem, MetaItemKind};
use syntax::attr::mk_name_value_item_str;
use syntax::early_buffered_lints::ILL_FORMED_ATTRIBUTE_INPUT;
use syntax::tokenstream::DelimSpan;
use syntax::sess::ParseSess;
use syntax_pos::{Symbol, sym};

Expand All @@ -27,16 +30,45 @@ pub fn check_meta(sess: &ParseSess, attr: &Attribute) {
pub fn parse_meta<'a>(sess: &'a ParseSess, attr: &Attribute) -> PResult<'a, MetaItem> {
Ok(match attr.kind {
AttrKind::Normal(ref item) => MetaItem {
path: item.path.clone(),
kind: super::parse_in_attr(sess, attr, |p| p.parse_meta_item_kind())?,
span: attr.span,
path: item.path.clone(),
kind: match &attr.get_normal_item().args {
MacArgs::Empty => MetaItemKind::Word,
MacArgs::Eq(_, t) => {
let v = parse_in(sess, t.clone(), "name value", |p| p.parse_unsuffixed_lit())?;
MetaItemKind::NameValue(v)
}
MacArgs::Delimited(dspan, delim, t) => {
check_meta_bad_delim(sess, *dspan, *delim, "wrong meta list delimiters");
let nmis = parse_in(sess, t.clone(), "meta list", |p| p.parse_meta_seq_top())?;
MetaItemKind::List(nmis)
}
}
},
AttrKind::DocComment(comment) => {
mk_name_value_item_str(Ident::new(sym::doc, attr.span), comment, attr.span)
}
})
}

crate fn check_meta_bad_delim(sess: &ParseSess, span: DelimSpan, delim: MacDelimiter, msg: &str) {
if let ast::MacDelimiter::Parenthesis = delim {
return;
}

sess.span_diagnostic
.struct_span_err(span.entire(), msg)
.multipart_suggestion(
"the delimiters should be `(` and `)`",
vec![
(span.open, "(".to_string()),
(span.close, ")".to_string()),
],
Applicability::MachineApplicable,
)
.emit();
}

/// Checks that the given meta-item is compatible with this `AttributeTemplate`.
fn is_attr_template_compatible(template: &AttributeTemplate, meta: &ast::MetaItemKind) -> bool {
match meta {
Expand Down
Loading