Skip to content

Commit

Permalink
derive: avoid parse_in_attr
Browse files Browse the repository at this point in the history
  • Loading branch information
Centril committed Dec 6, 2019
1 parent bbcda98 commit cbc9f68
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 80 deletions.
2 changes: 1 addition & 1 deletion src/librustc_parse/lib.rs
Expand Up @@ -285,7 +285,7 @@ pub fn parse_in<'a, T>(
}

/// Runs the given subparser `f` on the tokens of the given `attr`'s item.
pub fn parse_in_attr<'a, T>(
fn parse_in_attr<'a, T>(
sess: &'a ParseSess,
attr: &ast::Attribute,
f: impl FnMut(&mut Parser<'a>) -> PResult<'a, T>,
Expand Down
37 changes: 0 additions & 37 deletions src/librustc_parse/parser/path.rs
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
87 changes: 62 additions & 25 deletions src/libsyntax_expand/proc_macro.rs
@@ -1,7 +1,7 @@
use crate::base::{self, *};
use crate::proc_macro_server;

use syntax::ast::{self, ItemKind, MacArgs};
use syntax::ast::{self, ItemKind, MetaItemKind, NestedMetaItem};
use syntax::errors::{Applicability, FatalError};
use syntax::symbol::sym;
use syntax::token;
Expand Down Expand Up @@ -171,34 +171,71 @@ crate fn collect_derives(cx: &mut ExtCtxt<'_>, attrs: &mut Vec<ast::Attribute>)
if !attr.has_name(sym::derive) {
return true;
}
if !attr.is_meta_item_list() {
cx.struct_span_err(attr.span, "malformed `derive` attribute input")
.span_suggestion(
attr.span,
"missing traits to be derived",
"#[derive(Trait1, Trait2, ...)]".to_owned(),
Applicability::HasPlaceholders,
).emit();
return false;
}

let parse_derive_paths = |attr: &ast::Attribute| {
if let MacArgs::Empty = attr.get_normal_item().args {
return Ok(Vec::new());
// 1) First let's ensure that it's a meta item.
let nmis = match attr.meta_item_list() {
None => {
cx.struct_span_err(attr.span, "malformed `derive` attribute input")
.span_suggestion(
attr.span,
"missing traits to be derived",
"#[derive(Trait1, Trait2, ...)]".to_owned(),
Applicability::HasPlaceholders,
)
.emit();
return false;
}
rustc_parse::parse_in_attr(cx.parse_sess, attr, |p| p.parse_derive_paths())
Some(x) => x,
};

match parse_derive_paths(attr) {
Ok(traits) => {
result.extend(traits);
true
}
Err(mut e) => {
e.emit();
false
}
}
let mut retain_in_fm = true;
let mut retain_in_map = true;
let traits = nmis
.into_iter()
// 2) Moreover, let's ensure we have a path and not `#[derive("foo")]`.
.filter_map(|nmi| match nmi {
NestedMetaItem::Literal(lit) => {
retain_in_fm = false;
cx.struct_span_err(lit.span, "expected path to a trait, found literal")
.help("for example, write `#[derive(Debug)]` for `Debug`")
.emit();
None
}
NestedMetaItem::MetaItem(mi) => Some(mi),
})
// 3) Finally, we only accept `#[derive($path_0, $path_1, ..)]`
// but not e.g. `#[derive($path_0 = "value", $path_1(abc))]`.
// In this case we can still at least determine that the user
// wanted this trait to be derived, so let's keep it.
.map(|mi| {
let mut traits_dont_accept = |title, action| {
retain_in_map = false;
let sp = mi.span.with_lo(mi.path.span.hi());
cx.struct_span_err(sp, title)
.span_suggestion(
sp,
action,
String::new(),
Applicability::MachineApplicable,
)
.emit();
};
match &mi.kind {
MetaItemKind::List(..) => traits_dont_accept(
"traits in `#[derive(...)]` don't accept arguments",
"remove the arguments",
),
MetaItemKind::NameValue(..) => traits_dont_accept(
"traits in `#[derive(...)]` don't accept values",
"remove the value",
),
MetaItemKind::Word => {}
}
mi.path
});

result.extend(traits);
retain_in_fm && retain_in_map
});
result
}
8 changes: 6 additions & 2 deletions src/test/ui/malformed/malformed-derive-entry.rs
@@ -1,7 +1,11 @@
#[derive(Copy(Bad))] //~ ERROR expected one of `)`, `,`, or `::`, found `(`
#[derive(Copy(Bad))]
//~^ ERROR traits in `#[derive(...)]` don't accept arguments
//~| ERROR the trait bound
struct Test1;

#[derive(Copy="bad")] //~ ERROR expected one of `)`, `,`, or `::`, found `=`
#[derive(Copy="bad")]
//~^ ERROR traits in `#[derive(...)]` don't accept values
//~| ERROR the trait bound
struct Test2;

#[derive] //~ ERROR malformed `derive` attribute input
Expand Down
27 changes: 20 additions & 7 deletions src/test/ui/malformed/malformed-derive-entry.stderr
@@ -1,20 +1,33 @@
error: expected one of `)`, `,`, or `::`, found `(`
error: traits in `#[derive(...)]` don't accept arguments
--> $DIR/malformed-derive-entry.rs:1:14
|
LL | #[derive(Copy(Bad))]
| ^ expected one of `)`, `,`, or `::`
| ^^^^^ help: remove the arguments

error: expected one of `)`, `,`, or `::`, found `=`
--> $DIR/malformed-derive-entry.rs:4:14
error: traits in `#[derive(...)]` don't accept values
--> $DIR/malformed-derive-entry.rs:6:14
|
LL | #[derive(Copy="bad")]
| ^ expected one of `)`, `,`, or `::`
| ^^^^^^ help: remove the value

error: malformed `derive` attribute input
--> $DIR/malformed-derive-entry.rs:7:1
--> $DIR/malformed-derive-entry.rs:11:1
|
LL | #[derive]
| ^^^^^^^^^ help: missing traits to be derived: `#[derive(Trait1, Trait2, ...)]`

error: aborting due to 3 previous errors
error[E0277]: the trait bound `Test1: std::clone::Clone` is not satisfied
--> $DIR/malformed-derive-entry.rs:1:10
|
LL | #[derive(Copy(Bad))]
| ^^^^ the trait `std::clone::Clone` is not implemented for `Test1`

error[E0277]: the trait bound `Test2: std::clone::Clone` is not satisfied
--> $DIR/malformed-derive-entry.rs:6:10
|
LL | #[derive(Copy="bad")]
| ^^^^ the trait `std::clone::Clone` is not implemented for `Test2`

error: aborting due to 5 previous errors

For more information about this error, try `rustc --explain E0277`.
1 change: 0 additions & 1 deletion src/test/ui/span/macro-ty-params.rs
Expand Up @@ -10,5 +10,4 @@ fn main() {
foo::<T>!(); //~ ERROR generic arguments in macro path
foo::<>!(); //~ ERROR generic arguments in macro path
m!(Default<>); //~ ERROR generic arguments in macro path
//~^ ERROR unexpected generic arguments in path
}
8 changes: 1 addition & 7 deletions src/test/ui/span/macro-ty-params.stderr
Expand Up @@ -10,17 +10,11 @@ error: generic arguments in macro path
LL | foo::<>!();
| ^^

error: unexpected generic arguments in path
--> $DIR/macro-ty-params.rs:12:8
|
LL | m!(Default<>);
| ^^^^^^^^^

error: generic arguments in macro path
--> $DIR/macro-ty-params.rs:12:15
|
LL | m!(Default<>);
| ^^

error: aborting due to 4 previous errors
error: aborting due to 3 previous errors

0 comments on commit cbc9f68

Please sign in to comment.