From 0357257b72b9019b1c24ffd37ebc163c3d1bc6cb Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Mon, 14 Sep 2020 18:59:41 -0700 Subject: [PATCH] Allow path as value in name-value attribute --- compiler/rustc_parse/src/parser/attr.rs | 8 ++- compiler/rustc_parse/src/parser/mod.rs | 22 +++++-- src/test/ui/attr-eq-token-tree.rs | 2 +- src/test/ui/attr-eq-token-tree.stderr | 2 +- src/test/ui/attributes/path-eq-path.rs | 11 ++++ src/test/ui/attributes/path-eq-path.stderr | 14 +++++ src/test/ui/macros/macro-attribute.rs | 2 +- src/test/ui/macros/macro-attribute.stderr | 2 +- src/test/ui/parser/attr-bad-meta-2.rs | 2 +- src/test/ui/parser/attr-bad-meta-2.stderr | 2 +- src/test/ui/proc-macro/attributes-included.rs | 1 + .../ui/proc-macro/attributes-included.stderr | 2 +- .../auxiliary/attributes-included.rs | 58 ++++++++++++++++++- 13 files changed, 114 insertions(+), 14 deletions(-) create mode 100644 src/test/ui/attributes/path-eq-path.rs create mode 100644 src/test/ui/attributes/path-eq-path.stderr diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs index 053b7e0b75fe4..e46c39bef04db 100644 --- a/compiler/rustc_parse/src/parser/attr.rs +++ b/compiler/rustc_parse/src/parser/attr.rs @@ -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 { @@ -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() { @@ -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. diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index da1c54e88b5e2..f9cb42d56767c 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -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 } @@ -1254,6 +1260,14 @@ impl<'a> Parser<'a> { Ok((ret, Some(LazyTokenStream::new(lazy_impl)))) } + fn collect_tokens_only( + &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) diff --git a/src/test/ui/attr-eq-token-tree.rs b/src/test/ui/attr-eq-token-tree.rs index c301492b9e21a..d9359da76ec30 100644 --- a/src/test/ui/attr-eq-token-tree.rs +++ b/src/test/ui/attr-eq-token-tree.rs @@ -1,2 +1,2 @@ -#[my_attr = !] //~ ERROR unexpected token: `!` +#[my_attr = !] //~ ERROR expected a literal or ::-separated path fn main() {} diff --git a/src/test/ui/attr-eq-token-tree.stderr b/src/test/ui/attr-eq-token-tree.stderr index bb37c2e0cc473..f4ad7f5a3944f 100644 --- a/src/test/ui/attr-eq-token-tree.stderr +++ b/src/test/ui/attr-eq-token-tree.stderr @@ -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 = !] diff --git a/src/test/ui/attributes/path-eq-path.rs b/src/test/ui/attributes/path-eq-path.rs new file mode 100644 index 0000000000000..b189eb249ee68 --- /dev/null +++ b/src/test/ui/attributes/path-eq-path.rs @@ -0,0 +1,11 @@ +#[cfg(any())] +extern "C++" { + #[namespace = std::experimental] + type any; + + #[rust = std::option::Option] + //~^ ERROR expected one of `::` or `]`, found `<` + type optional; +} + +fn main() {} diff --git a/src/test/ui/attributes/path-eq-path.stderr b/src/test/ui/attributes/path-eq-path.stderr new file mode 100644 index 0000000000000..82603ff27e17c --- /dev/null +++ b/src/test/ui/attributes/path-eq-path.stderr @@ -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] + | ^ expected one of `::` or `]` +... +LL | } + | - the item list ends here + +error: aborting due to previous error + diff --git a/src/test/ui/macros/macro-attribute.rs b/src/test/ui/macros/macro-attribute.rs index f580dfa8e34ed..c6656a975d68d 100644 --- a/src/test/ui/macros/macro-attribute.rs +++ b/src/test/ui/macros/macro-attribute.rs @@ -1,2 +1,2 @@ -#[doc = $not_there] //~ ERROR unexpected token: `$` +#[doc = $not_there] //~ ERROR expected a literal or ::-separated path fn main() { } diff --git a/src/test/ui/macros/macro-attribute.stderr b/src/test/ui/macros/macro-attribute.stderr index d28ce25341d3a..3a8a3abf2b883 100644 --- a/src/test/ui/macros/macro-attribute.stderr +++ b/src/test/ui/macros/macro-attribute.stderr @@ -1,4 +1,4 @@ -error: unexpected token: `$` +error: expected a literal or ::-separated path --> $DIR/macro-attribute.rs:1:9 | LL | #[doc = $not_there] diff --git a/src/test/ui/parser/attr-bad-meta-2.rs b/src/test/ui/parser/attr-bad-meta-2.rs index cefd3369742be..a4232e8924cc5 100644 --- a/src/test/ui/parser/attr-bad-meta-2.rs +++ b/src/test/ui/parser/attr-bad-meta-2.rs @@ -1,2 +1,2 @@ -#[path =] //~ ERROR unexpected token: `]` +#[path =] //~ ERROR expected a literal or ::-separated path mod m {} diff --git a/src/test/ui/parser/attr-bad-meta-2.stderr b/src/test/ui/parser/attr-bad-meta-2.stderr index 2d772dae69125..9fb8f8a12289f 100644 --- a/src/test/ui/parser/attr-bad-meta-2.stderr +++ b/src/test/ui/parser/attr-bad-meta-2.stderr @@ -1,4 +1,4 @@ -error: unexpected token: `]` +error: expected a literal or ::-separated path --> $DIR/attr-bad-meta-2.rs:1:9 | LL | #[path =] diff --git a/src/test/ui/proc-macro/attributes-included.rs b/src/test/ui/proc-macro/attributes-included.rs index 95e8e10a3ece9..fa9ead029c273 100644 --- a/src/test/ui/proc-macro/attributes-included.rs +++ b/src/test/ui/proc-macro/attributes-included.rs @@ -13,6 +13,7 @@ use attributes_included::*; #[foo] #[inline] /// doc +#[namespace = std::experimental] fn foo() { let a: i32 = "foo"; //~ WARN: unused variable } diff --git a/src/test/ui/proc-macro/attributes-included.stderr b/src/test/ui/proc-macro/attributes-included.stderr index 72c88d5d8b752..1ce12e9f688ea 100644 --- a/src/test/ui/proc-macro/attributes-included.stderr +++ b/src/test/ui/proc-macro/attributes-included.stderr @@ -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` diff --git a/src/test/ui/proc-macro/auxiliary/attributes-included.rs b/src/test/ui/proc-macro/auxiliary/attributes-included.rs index a5eb40b28dca4..05942d305c47a 100644 --- a/src/test/ui/proc-macro/auxiliary/attributes-included.rs +++ b/src/test/ui/proc-macro/auxiliary/attributes-included.rs @@ -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::>(); + let mut input = input.into_iter().collect::>(); + 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()) } @@ -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()); } @@ -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::>(), + _ => 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(), '#'),