Skip to content

Commit

Permalink
Allow path as value in name-value attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
dtolnay committed Nov 3, 2020
1 parent 4c0c5e0 commit 0357257
Show file tree
Hide file tree
Showing 13 changed files with 114 additions and 14 deletions.
8 changes: 6 additions & 2 deletions compiler/rustc_parse/src/parser/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ impl<'a> Parser<'a> {
/// PATH `{` TOKEN_STREAM `}`
/// PATH
/// PATH `=` UNSUFFIXED_LIT
/// PATH `=` PATH
/// The delimiters or `=` are still put into the resulting token stream.
pub fn parse_attr_item(&mut self, capture_tokens: bool) -> PResult<'a, ast::AttrItem> {
let item = match self.token.kind {
Expand Down Expand Up @@ -230,6 +231,11 @@ impl<'a> Parser<'a> {

crate fn parse_unsuffixed_lit(&mut self) -> PResult<'a, ast::Lit> {
let lit = self.parse_lit()?;
self.require_unsuffixed(&lit);
Ok(lit)
}

crate fn require_unsuffixed(&self, lit: &ast::Lit) {
debug!("checking if {:?} is unusuffixed", lit);

if !lit.kind.is_unsuffixed() {
Expand All @@ -240,8 +246,6 @@ impl<'a> Parser<'a> {
)
.emit();
}

Ok(lit)
}

/// Parses `cfg_attr(pred, attr_item_list)` where `attr_item_list` is comma-delimited.
Expand Down
22 changes: 18 additions & 4 deletions compiler/rustc_parse/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -934,16 +934,22 @@ impl<'a> Parser<'a> {
is_interpolated_expr = true;
}
}
let token_tree = if is_interpolated_expr {
let token_stream: TokenStream = if is_interpolated_expr {
// We need to accept arbitrary interpolated expressions to continue
// supporting things like `doc = $expr` that work on stable.
// Non-literal interpolated expressions are rejected after expansion.
self.parse_token_tree()
self.parse_token_tree().into()
} else if let Some(lit) = self.parse_opt_lit() {
self.require_unsuffixed(&lit);
lit.token_tree().into()
} else if self.check(&token::ModSep) || self.token.ident().is_some() {
self.collect_tokens_only(|this| this.parse_path(PathStyle::Mod))?
} else {
self.parse_unsuffixed_lit()?.token_tree()
let msg = "expected a literal or ::-separated path";
return Err(self.struct_span_err(self.token.span, msg));
};

MacArgs::Eq(eq_span, token_tree.into())
MacArgs::Eq(eq_span, token_stream)
} else {
MacArgs::Empty
}
Expand Down Expand Up @@ -1254,6 +1260,14 @@ impl<'a> Parser<'a> {
Ok((ret, Some(LazyTokenStream::new(lazy_impl))))
}

fn collect_tokens_only<R>(
&mut self,
f: impl FnOnce(&mut Self) -> PResult<'a, R>,
) -> PResult<'a, TokenStream> {
let (_ignored, tokens) = self.collect_tokens(f)?;
Ok(tokens)
}

/// `::{` or `::*`
fn is_import_coupler(&mut self) -> bool {
self.check(&token::ModSep)
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/attr-eq-token-tree.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#[my_attr = !] //~ ERROR unexpected token: `!`
#[my_attr = !] //~ ERROR expected a literal or ::-separated path
fn main() {}
2 changes: 1 addition & 1 deletion src/test/ui/attr-eq-token-tree.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: unexpected token: `!`
error: expected a literal or ::-separated path
--> $DIR/attr-eq-token-tree.rs:1:13
|
LL | #[my_attr = !]
Expand Down
11 changes: 11 additions & 0 deletions src/test/ui/attributes/path-eq-path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#[cfg(any())]
extern "C++" {
#[namespace = std::experimental]
type any;

#[rust = std::option::Option<T>]
//~^ ERROR expected one of `::` or `]`, found `<`
type optional;
}

fn main() {}
14 changes: 14 additions & 0 deletions src/test/ui/attributes/path-eq-path.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: expected one of `::` or `]`, found `<`
--> $DIR/path-eq-path.rs:6:33
|
LL | extern "C++" {
| - while parsing this item list starting here
...
LL | #[rust = std::option::Option<T>]
| ^ expected one of `::` or `]`
...
LL | }
| - the item list ends here

error: aborting due to previous error

2 changes: 1 addition & 1 deletion src/test/ui/macros/macro-attribute.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#[doc = $not_there] //~ ERROR unexpected token: `$`
#[doc = $not_there] //~ ERROR expected a literal or ::-separated path
fn main() { }
2 changes: 1 addition & 1 deletion src/test/ui/macros/macro-attribute.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: unexpected token: `$`
error: expected a literal or ::-separated path
--> $DIR/macro-attribute.rs:1:9
|
LL | #[doc = $not_there]
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/parser/attr-bad-meta-2.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#[path =] //~ ERROR unexpected token: `]`
#[path =] //~ ERROR expected a literal or ::-separated path
mod m {}
2 changes: 1 addition & 1 deletion src/test/ui/parser/attr-bad-meta-2.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: unexpected token: `]`
error: expected a literal or ::-separated path
--> $DIR/attr-bad-meta-2.rs:1:9
|
LL | #[path =]
Expand Down
1 change: 1 addition & 0 deletions src/test/ui/proc-macro/attributes-included.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use attributes_included::*;
#[foo]
#[inline]
/// doc
#[namespace = std::experimental]
fn foo() {
let a: i32 = "foo"; //~ WARN: unused variable
}
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/proc-macro/attributes-included.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
warning: unused variable: `a`
--> $DIR/attributes-included.rs:17:9
--> $DIR/attributes-included.rs:18:9
|
LL | let a: i32 = "foo";
| ^ help: if this is intentional, prefix it with an underscore: `_a`
Expand Down
58 changes: 57 additions & 1 deletion src/test/ui/proc-macro/auxiliary/attributes-included.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,30 @@
extern crate proc_macro;

use proc_macro::{TokenStream, TokenTree, Delimiter, Literal, Spacing, Group};
use std::iter;

#[proc_macro_attribute]
pub fn foo(attr: TokenStream, input: TokenStream) -> TokenStream {
assert!(attr.is_empty());
let input = input.into_iter().collect::<Vec<_>>();
let mut input = input.into_iter().collect::<Vec<_>>();
let (namespace_start, namespace_end);
{
let mut cursor = &input[..];
assert_inline(&mut cursor);
assert_doc(&mut cursor);
assert_inline(&mut cursor);
assert_doc(&mut cursor);

// Splice out the #[namespace = ...] attribute as it is an inert
// attribute, not a proc macro.
namespace_start = input.len() - cursor.len();
assert_namespace(&mut cursor);
namespace_end = input.len() - cursor.len();

assert_foo(&mut cursor);
assert!(cursor.is_empty());
}
input.splice(namespace_start..namespace_end, iter::empty());
fold_stream(input.into_iter().collect())
}

Expand All @@ -34,6 +44,7 @@ pub fn bar(attr: TokenStream, input: TokenStream) -> TokenStream {
assert_invoc(&mut cursor);
assert_inline(&mut cursor);
assert_doc(&mut cursor);
assert_namespace(&mut cursor);
assert_foo(&mut cursor);
assert!(cursor.is_empty());
}
Expand Down Expand Up @@ -93,6 +104,51 @@ fn assert_doc(slice: &mut &[TokenTree]) {
*slice = &slice[2..];
}

fn assert_namespace(slice: &mut &[TokenTree]) {
match &slice[0] {
TokenTree::Punct(tt) => assert_eq!(tt.as_char(), '#'),
_ => panic!("expected `#`"),
}
let inner = match &slice[1] {
TokenTree::Group(tt) => tt.stream().into_iter().collect::<Vec<_>>(),
_ => panic!("expected brackets"),
};
if inner.len() != 6 {
panic!("expected 6 tokens in namespace attr")
}
match &inner[0] {
TokenTree::Ident(tt) => assert_eq!("namespace", tt.to_string()),
_ => panic!("expected `namespace`"),
}
match &inner[1] {
TokenTree::Punct(tt) => assert_eq!(tt.as_char(), '='),
_ => panic!("expected `=`"),
}
match &inner[2] {
TokenTree::Ident(tt) => assert_eq!("std", tt.to_string()),
_ => panic!("expected `std`"),
}
match &inner[3] {
TokenTree::Punct(tt) => {
assert_eq!(tt.as_char(), ':');
assert_eq!(tt.spacing(), Spacing::Joint);
}
_ => panic!("expected `:`"),
}
match &inner[4] {
TokenTree::Punct(tt) => {
assert_eq!(tt.as_char(), ':');
assert_eq!(tt.spacing(), Spacing::Alone);
}
_ => panic!("expected `:`"),
}
match &inner[5] {
TokenTree::Ident(tt) => assert_eq!("experimental", tt.to_string()),
_ => panic!("expected `experimental`"),
}
*slice = &slice[2..];
}

fn assert_invoc(slice: &mut &[TokenTree]) {
match &slice[0] {
TokenTree::Punct(tt) => assert_eq!(tt.as_char(), '#'),
Expand Down

0 comments on commit 0357257

Please sign in to comment.