From 8250a26b5bcea9190ac63e756c35d8a54bf9da0c Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Fri, 19 Aug 2016 18:58:14 -0700 Subject: [PATCH] Implement RFC#1559: allow all literals in attributes. --- src/librustc/hir/check_attr.rs | 19 +- src/librustc/hir/fold.rs | 23 +- src/librustc/lint/context.rs | 12 +- src/librustc_borrowck/borrowck/mir/mod.rs | 7 +- src/librustc_driver/lib.rs | 12 +- src/librustc_incremental/assert_dep_graph.rs | 39 ++- .../persist/dirty_clean.rs | 16 +- src/librustc_lint/builtin.rs | 6 +- src/librustc_lint/unused.rs | 7 + src/librustc_metadata/common.rs | 15 +- src/librustc_metadata/creader.rs | 3 +- src/librustc_metadata/decoder.rs | 51 +-- src/librustc_metadata/encoder.rs | 37 +- src/librustc_metadata/macro_import.rs | 11 +- src/librustc_plugin/load.rs | 20 +- src/librustc_plugin/registry.rs | 5 +- src/librustc_trans/assert_module_sources.rs | 2 +- src/librustdoc/clean/mod.rs | 56 ++- src/librustdoc/test.rs | 3 +- src/librustdoc/visit_ast.rs | 6 +- src/libsyntax/ast.rs | 58 ++- src/libsyntax/attr.rs | 330 ++++++++++++------ src/libsyntax/config.rs | 37 +- src/libsyntax/diagnostic_list.rs | 18 + src/libsyntax/ext/build.rs | 13 +- src/libsyntax/feature_gate.rs | 51 ++- src/libsyntax/fold.rs | 19 +- src/libsyntax/parse/attr.rs | 64 +++- src/libsyntax/print/pprust.rs | 19 +- src/libsyntax/test.rs | 9 +- src/libsyntax_ext/deriving/cmp/eq.rs | 2 +- src/libsyntax_ext/deriving/generic/mod.rs | 2 +- src/libsyntax_ext/deriving/mod.rs | 6 +- .../auxiliary/macro_crate_test.rs | 17 +- src/test/compile-fail/E0565-1.rs | 17 + src/test/compile-fail/E0565.rs | 17 + src/test/compile-fail/attr-literals.rs | 33 ++ src/test/compile-fail/gated-attr-literals.rs | 44 +++ src/test/parse-fail/suffixed-literal-meta.rs | 25 ++ .../attr-literals.rs} | 16 +- .../auxiliary/custom_derive_plugin.rs | 1 - .../auxiliary/custom_derive_plugin_attr.rs | 2 +- .../auxiliary/macro_crate_test.rs | 103 +++++- .../auxiliary/plugin_args.rs | 4 +- .../macro-crate-multi-decorator-literals.rs | 58 +++ 45 files changed, 942 insertions(+), 373 deletions(-) create mode 100644 src/test/compile-fail/E0565-1.rs create mode 100644 src/test/compile-fail/E0565.rs create mode 100644 src/test/compile-fail/attr-literals.rs create mode 100644 src/test/compile-fail/gated-attr-literals.rs create mode 100644 src/test/parse-fail/suffixed-literal-meta.rs rename src/test/{parse-fail/non-str-meta.rs => pretty/attr-literals.rs} (66%) create mode 100644 src/test/run-pass-fulldeps/macro-crate-multi-decorator-literals.rs diff --git a/src/librustc/hir/check_attr.rs b/src/librustc/hir/check_attr.rs index a1c04dfcab5e6..350b9fd88f6e7 100644 --- a/src/librustc/hir/check_attr.rs +++ b/src/librustc/hir/check_attr.rs @@ -11,7 +11,7 @@ use session::Session; use syntax::ast; -use syntax::attr::AttrMetaMethods; +use syntax::attr::{AttrNestedMetaItemMethods, AttrMetaMethods}; use syntax::visit; use syntax::visit::Visitor; @@ -52,18 +52,22 @@ impl<'a> CheckAttrVisitor<'a> { return; } }; + for word in words { - let word: &str = &word.name(); - let message = match word { + let name = match word.name() { + Some(word) => word, + None => continue, + }; + + let message = match &*name { "C" => { if target != Target::Struct && target != Target::Enum { - "attribute should be applied to struct or enum" + "attribute should be applied to struct or enum" } else { continue } } - "packed" | - "simd" => { + "packed" | "simd" => { if target != Target::Struct { "attribute should be applied to struct" } else { @@ -74,13 +78,14 @@ impl<'a> CheckAttrVisitor<'a> { "i32" | "u32" | "i64" | "u64" | "isize" | "usize" => { if target != Target::Enum { - "attribute should be applied to enum" + "attribute should be applied to enum" } else { continue } } _ => continue, }; + span_err!(self.sess, attr.span, E0517, "{}", message); } } diff --git a/src/librustc/hir/fold.rs b/src/librustc/hir/fold.rs index 0edfd16bdfd1b..99bd22cc87b19 100644 --- a/src/librustc/hir/fold.rs +++ b/src/librustc/hir/fold.rs @@ -12,8 +12,8 @@ //! and returns a piece of the same type. use hir::*; -use syntax::ast::{Name, NodeId, DUMMY_NODE_ID, Attribute, Attribute_, MetaItem}; -use syntax::ast::MetaItemKind; +use syntax::ast::{Name, NodeId, DUMMY_NODE_ID, Attribute, Attribute_}; +use syntax::ast::{NestedMetaItem, NestedMetaItemKind, MetaItem, MetaItemKind}; use hir; use syntax_pos::Span; use syntax::codemap::{respan, Spanned}; @@ -38,6 +38,10 @@ pub trait Folder : Sized { noop_fold_meta_items(meta_items, self) } + fn fold_meta_list_item(&mut self, list_item: NestedMetaItem) -> NestedMetaItem { + noop_fold_meta_list_item(list_item, self) + } + fn fold_meta_item(&mut self, meta_item: P) -> P { noop_fold_meta_item(meta_item, self) } @@ -486,13 +490,26 @@ pub fn noop_fold_attribute(at: Attribute, fld: &mut T) -> Option(li: NestedMetaItem, fld: &mut T) + -> NestedMetaItem { + Spanned { + node: match li.node { + NestedMetaItemKind::MetaItem(mi) => { + NestedMetaItemKind::MetaItem(fld.fold_meta_item(mi)) + }, + NestedMetaItemKind::Literal(lit) => NestedMetaItemKind::Literal(lit) + }, + span: fld.new_span(li.span) + } +} + pub fn noop_fold_meta_item(mi: P, fld: &mut T) -> P { mi.map(|Spanned { node, span }| { Spanned { node: match node { MetaItemKind::Word(id) => MetaItemKind::Word(id), MetaItemKind::List(id, mis) => { - MetaItemKind::List(id, mis.move_map(|e| fld.fold_meta_item(e))) + MetaItemKind::List(id, mis.move_map(|e| fld.fold_meta_list_item(e))) } MetaItemKind::NameValue(id, s) => MetaItemKind::NameValue(id, s), }, diff --git a/src/librustc/lint/context.rs b/src/librustc/lint/context.rs index 29bcc1257fd31..bccd217352b70 100644 --- a/src/librustc/lint/context.rs +++ b/src/librustc/lint/context.rs @@ -38,7 +38,7 @@ use util::nodemap::FnvHashMap; use std::cmp; use std::default::Default as StdDefault; use std::mem; -use syntax::attr::{self, AttrMetaMethods}; +use syntax::attr::{self, AttrMetaMethods, AttrNestedMetaItemMethods}; use syntax::parse::token::InternedString; use syntax::ast; use syntax_pos::Span; @@ -372,12 +372,10 @@ pub fn gather_attr(attr: &ast::Attribute) return out; }; - for meta in metas { - out.push(if meta.is_word() { - Ok((meta.name().clone(), level, meta.span)) - } else { - Err(meta.span) - }); + for li in metas { + out.push(li.word().map_or(Err(li.span), |word| { + Ok((word.name().clone(), level, word.span)) + })); } out diff --git a/src/librustc_borrowck/borrowck/mir/mod.rs b/src/librustc_borrowck/borrowck/mir/mod.rs index dbee0ea9b00e9..55c6a4de9df50 100644 --- a/src/librustc_borrowck/borrowck/mir/mod.rs +++ b/src/librustc_borrowck/borrowck/mir/mod.rs @@ -11,7 +11,7 @@ use borrowck::BorrowckCtxt; use syntax::ast::{self, MetaItem}; -use syntax::attr::AttrMetaMethods; +use syntax::attr::{AttrMetaMethods, AttrNestedMetaItemMethods}; use syntax::ptr::P; use syntax_pos::{Span, DUMMY_SP}; @@ -43,8 +43,9 @@ fn has_rustc_mir_with(attrs: &[ast::Attribute], name: &str) -> Option return Some(mi.clone()), + _ => continue } } } diff --git a/src/librustc_driver/lib.rs b/src/librustc_driver/lib.rs index 4e87c931cc19d..1f3f823d0b8ab 100644 --- a/src/librustc_driver/lib.rs +++ b/src/librustc_driver/lib.rs @@ -655,17 +655,19 @@ impl RustcDefaultCalls { if !allow_unstable_cfg && GatedCfg::gate(&*cfg).is_some() { continue; } + if cfg.is_word() { println!("{}", cfg.name()); - } else if cfg.is_value_str() { - if let Some(s) = cfg.value_str() { - println!("{}=\"{}\"", cfg.name(), s); - } + } else if let Some(s) = cfg.value_str() { + println!("{}=\"{}\"", cfg.name(), s); } else if cfg.is_meta_item_list() { // Right now there are not and should not be any // MetaItemKind::List items in the configuration returned by // `build_configuration`. - panic!("MetaItemKind::List encountered in default cfg") + panic!("Found an unexpected list in cfg attribute '{}'!", cfg.name()) + } else { + // There also shouldn't be literals. + panic!("Found an unexpected literal in cfg attribute '{}'!", cfg.name()) } } } diff --git a/src/librustc_incremental/assert_dep_graph.rs b/src/librustc_incremental/assert_dep_graph.rs index 420c88e89be0d..482b351481c4e 100644 --- a/src/librustc_incremental/assert_dep_graph.rs +++ b/src/librustc_incremental/assert_dep_graph.rs @@ -56,7 +56,7 @@ use std::env; use std::fs::File; use std::io::Write; use syntax::ast; -use syntax::attr::AttrMetaMethods; +use syntax::attr::{AttrNestedMetaItemMethods, AttrMetaMethods}; use syntax::parse::token::InternedString; use syntax_pos::Span; @@ -116,14 +116,18 @@ impl<'a, 'tcx> IfThisChanged<'a, 'tcx> { for attr in self.tcx.get_attrs(def_id).iter() { if attr.check_name(IF_THIS_CHANGED) { let mut id = None; - for meta_item in attr.meta_item_list().unwrap_or_default() { - if meta_item.is_word() && id.is_none() { - id = Some(meta_item.name().clone()); - } else { - // FIXME better-encapsulate meta_item (don't directly access `node`) - span_bug!(meta_item.span(), "unexpected meta-item {:?}", meta_item.node) + for list_item in attr.meta_item_list().unwrap_or_default() { + match list_item.word() { + Some(word) if id.is_none() => { + id = Some(word.name().clone()) + }, + _ => { + // FIXME better-encapsulate meta_item (don't directly access `node`) + span_bug!(list_item.span(), "unexpected list-item {:?}", list_item.node) + } } } + let id = id.unwrap_or(InternedString::new(ID)); self.if_this_changed.entry(id) .or_insert(FnvHashSet()) @@ -131,16 +135,21 @@ impl<'a, 'tcx> IfThisChanged<'a, 'tcx> { } else if attr.check_name(THEN_THIS_WOULD_NEED) { let mut dep_node_interned = None; let mut id = None; - for meta_item in attr.meta_item_list().unwrap_or_default() { - if meta_item.is_word() && dep_node_interned.is_none() { - dep_node_interned = Some(meta_item.name().clone()); - } else if meta_item.is_word() && id.is_none() { - id = Some(meta_item.name().clone()); - } else { - // FIXME better-encapsulate meta_item (don't directly access `node`) - span_bug!(meta_item.span(), "unexpected meta-item {:?}", meta_item.node) + for list_item in attr.meta_item_list().unwrap_or_default() { + match list_item.word() { + Some(word) if dep_node_interned.is_none() => { + dep_node_interned = Some(word.name().clone()); + }, + Some(word) if id.is_none() => { + id = Some(word.name().clone()) + }, + _ => { + // FIXME better-encapsulate meta_item (don't directly access `node`) + span_bug!(list_item.span(), "unexpected meta-item {:?}", list_item.node) + } } } + let dep_node = match dep_node_interned { Some(ref n) => { match DepNode::from_label_string(&n[..], def_id) { diff --git a/src/librustc_incremental/persist/dirty_clean.rs b/src/librustc_incremental/persist/dirty_clean.rs index 65da3a09ecca5..f0092ce04d1f6 100644 --- a/src/librustc_incremental/persist/dirty_clean.rs +++ b/src/librustc_incremental/persist/dirty_clean.rs @@ -31,8 +31,8 @@ use rustc::hir; use rustc::hir::def_id::DefId; use rustc::hir::intravisit::Visitor; use rustc_data_structures::fnv::FnvHashSet; -use syntax::ast::{self, Attribute, MetaItem}; -use syntax::attr::AttrMetaMethods; +use syntax::ast::{self, Attribute, NestedMetaItem}; +use syntax::attr::{AttrNestedMetaItemMethods, AttrMetaMethods}; use syntax::parse::token::InternedString; use rustc::ty::TyCtxt; @@ -71,13 +71,17 @@ pub struct DirtyCleanVisitor<'a, 'tcx:'a> { } impl<'a, 'tcx> DirtyCleanVisitor<'a, 'tcx> { - fn expect_associated_value(&self, item: &MetaItem) -> InternedString { + fn expect_associated_value(&self, item: &NestedMetaItem) -> InternedString { if let Some(value) = item.value_str() { value } else { - self.tcx.sess.span_fatal( - item.span, - &format!("associated value expected for `{}`", item.name())); + let msg = if let Some(name) = item.name() { + format!("associated value expected for `{}`", name) + } else { + "expected an associated value".to_string() + }; + + self.tcx.sess.span_fatal(item.span, &msg); } } diff --git a/src/librustc_lint/builtin.rs b/src/librustc_lint/builtin.rs index b4a2648b5dca7..9a4eec2d05b7a 100644 --- a/src/librustc_lint/builtin.rs +++ b/src/librustc_lint/builtin.rs @@ -44,8 +44,8 @@ use lint::{LintPass, LateLintPass}; use std::collections::HashSet; use syntax::{ast}; -use syntax::attr::{self, AttrMetaMethods, AttributeMethods}; -use syntax_pos::Span; +use syntax::attr::{self, AttrMetaMethods, AttrNestedMetaItemMethods, AttributeMethods}; +use syntax_pos::{Span}; use rustc::hir::{self, PatKind}; use rustc::hir::intravisit::FnKind; @@ -317,7 +317,7 @@ impl LateLintPass for MissingDoc { let doc_hidden = self.doc_hidden() || attrs.iter().any(|attr| { attr.check_name("doc") && match attr.meta_item_list() { None => false, - Some(l) => attr::contains_name(&l[..], "hidden"), + Some(l) => attr::list_contains_name(&l[..], "hidden"), } }); self.doc_hidden_stack.push(doc_hidden); diff --git a/src/librustc_lint/unused.rs b/src/librustc_lint/unused.rs index 57705301aab4e..44c2ffe45ccb4 100644 --- a/src/librustc_lint/unused.rs +++ b/src/librustc_lint/unused.rs @@ -234,10 +234,13 @@ impl LintPass for UnusedAttributes { impl LateLintPass for UnusedAttributes { fn check_attribute(&mut self, cx: &LateContext, attr: &ast::Attribute) { + debug!("checking attribute: {:?}", attr); + // Note that check_name() marks the attribute as used if it matches. for &(ref name, ty, _) in KNOWN_ATTRIBUTES { match ty { AttributeType::Whitelisted if attr.check_name(name) => { + debug!("{:?} is Whitelisted", name); break; }, _ => () @@ -247,11 +250,13 @@ impl LateLintPass for UnusedAttributes { let plugin_attributes = cx.sess().plugin_attributes.borrow_mut(); for &(ref name, ty) in plugin_attributes.iter() { if ty == AttributeType::Whitelisted && attr.check_name(&name) { + debug!("{:?} (plugin attr) is whitelisted with ty {:?}", name, ty); break; } } if !attr::is_used(attr) { + debug!("Emitting warning for: {:?}", attr); cx.span_lint(UNUSED_ATTRIBUTES, attr.span, "unused attribute"); // Is it a builtin attribute that must be used at the crate level? let known_crate = KNOWN_ATTRIBUTES.iter().find(|&&(name, ty, _)| { @@ -275,6 +280,8 @@ impl LateLintPass for UnusedAttributes { }; cx.span_lint(UNUSED_ATTRIBUTES, attr.span, msg); } + } else { + debug!("Attr was used: {:?}", attr); } } } diff --git a/src/librustc_metadata/common.rs b/src/librustc_metadata/common.rs index 99a3f3b00c8b0..85cf41e42a273 100644 --- a/src/librustc_metadata/common.rs +++ b/src/librustc_metadata/common.rs @@ -45,26 +45,13 @@ pub const tag_items_closure_kind: usize = 0x2a; pub const tag_items_closure_ty: usize = 0x2b; pub const tag_def_key: usize = 0x2c; -// GAP 0x2d 0x2e +// GAP 0x2d 0x34 pub const tag_index: usize = 0x110; // top-level only pub const tag_xref_index: usize = 0x111; // top-level only pub const tag_xref_data: usize = 0x112; // top-level only - -pub const tag_meta_item_name_value: usize = 0x2f; - -pub const tag_meta_item_name: usize = 0x30; - -pub const tag_meta_item_value: usize = 0x31; - pub const tag_attributes: usize = 0x101; // top-level only -pub const tag_attribute: usize = 0x32; - -pub const tag_meta_item_word: usize = 0x33; - -pub const tag_meta_item_list: usize = 0x34; - // The list of crates that this crate depends on pub const tag_crate_deps: usize = 0x102; // top-level only diff --git a/src/librustc_metadata/creader.rs b/src/librustc_metadata/creader.rs index 46469efea6bc8..4dc3d04c4a23b 100644 --- a/src/librustc_metadata/creader.rs +++ b/src/librustc_metadata/creader.rs @@ -35,8 +35,7 @@ use syntax::ast; use syntax::abi::Abi; use syntax::codemap; use syntax::parse; -use syntax::attr; -use syntax::attr::AttrMetaMethods; +use syntax::attr::{self, AttrMetaMethods, AttrNestedMetaItemMethods}; use syntax::parse::token::InternedString; use syntax::visit; use syntax_pos::{self, Span, mk_sp, Pos}; diff --git a/src/librustc_metadata/decoder.rs b/src/librustc_metadata/decoder.rs index b0335258b4041..b8ed1f7bae63b 100644 --- a/src/librustc_metadata/decoder.rs +++ b/src/librustc_metadata/decoder.rs @@ -56,7 +56,6 @@ use syntax::parse::token; use syntax::ast; use syntax::codemap; use syntax::print::pprust; -use syntax::ptr::P; use syntax_pos::{self, Span, BytePos, NO_EXPANSION}; pub type Cmd<'a> = &'a CrateMetadata; @@ -1121,44 +1120,20 @@ pub fn get_struct_field_names(cdata: Cmd, id: DefIndex) -> Vec { })).collect() } -fn get_meta_items(md: rbml::Doc) -> Vec> { - reader::tagged_docs(md, tag_meta_item_word).map(|meta_item_doc| { - let nd = reader::get_doc(meta_item_doc, tag_meta_item_name); - let n = token::intern_and_get_ident(nd.as_str()); - attr::mk_word_item(n) - }).chain(reader::tagged_docs(md, tag_meta_item_name_value).map(|meta_item_doc| { - let nd = reader::get_doc(meta_item_doc, tag_meta_item_name); - let vd = reader::get_doc(meta_item_doc, tag_meta_item_value); - let n = token::intern_and_get_ident(nd.as_str()); - let v = token::intern_and_get_ident(vd.as_str()); - // FIXME (#623): Should be able to decode MetaItemKind::NameValue variants, - // but currently the encoder just drops them - attr::mk_name_value_item_str(n, v) - })).chain(reader::tagged_docs(md, tag_meta_item_list).map(|meta_item_doc| { - let nd = reader::get_doc(meta_item_doc, tag_meta_item_name); - let n = token::intern_and_get_ident(nd.as_str()); - let subitems = get_meta_items(meta_item_doc); - attr::mk_list_item(n, subitems) - })).collect() -} - fn get_attributes(md: rbml::Doc) -> Vec { - match reader::maybe_get_doc(md, tag_attributes) { - Some(attrs_d) => { - reader::tagged_docs(attrs_d, tag_attribute).map(|attr_doc| { - let is_sugared_doc = reader::doc_as_u8( - reader::get_doc(attr_doc, tag_attribute_is_sugared_doc) - ) == 1; - let meta_items = get_meta_items(attr_doc); - // Currently it's only possible to have a single meta item on - // an attribute - assert_eq!(meta_items.len(), 1); - let meta_item = meta_items.into_iter().nth(0).unwrap(); - attr::mk_doc_attr_outer(attr::mk_attr_id(), meta_item, is_sugared_doc) - }).collect() - }, - None => vec![], - } + reader::maybe_get_doc(md, tag_attributes).map_or(vec![], |attrs_doc| { + let mut decoder = reader::Decoder::new(attrs_doc); + let mut attrs: Vec = decoder.read_opaque(|opaque_decoder, _| { + Decodable::decode(opaque_decoder) + }).unwrap(); + + // Need new unique IDs: old thread-local IDs won't map to new threads. + for attr in attrs.iter_mut() { + attr.node.id = attr::mk_attr_id(); + } + + attrs + }) } fn list_crate_attributes(md: rbml::Doc, hash: &Svh, diff --git a/src/librustc_metadata/encoder.rs b/src/librustc_metadata/encoder.rs index 9a668b69b2eeb..ef8253713f5a1 100644 --- a/src/librustc_metadata/encoder.rs +++ b/src/librustc_metadata/encoder.rs @@ -41,7 +41,7 @@ use std::io::{Cursor, SeekFrom}; use std::rc::Rc; use std::u32; use syntax::ast::{self, NodeId, Name, CRATE_NODE_ID, CrateNum}; -use syntax::attr::{self,AttrMetaMethods,AttributeMethods}; +use syntax::attr; use errors::Handler; use syntax; use syntax_pos::BytePos; @@ -1412,40 +1412,11 @@ fn encode_item_index(rbml_w: &mut Encoder, index: IndexData) { rbml_w.end_tag(); } -fn encode_meta_item(rbml_w: &mut Encoder, mi: &ast::MetaItem) { - if mi.is_word() { - let name = mi.name(); - rbml_w.start_tag(tag_meta_item_word); - rbml_w.wr_tagged_str(tag_meta_item_name, &name); - rbml_w.end_tag(); - } else if mi.is_value_str() { - let name = mi.name(); - /* FIXME (#623): support other literal kinds */ - let value = mi.value_str().unwrap(); - rbml_w.start_tag(tag_meta_item_name_value); - rbml_w.wr_tagged_str(tag_meta_item_name, &name); - rbml_w.wr_tagged_str(tag_meta_item_value, &value); - rbml_w.end_tag(); - } else { // it must be a list - let name = mi.name(); - let items = mi.meta_item_list().unwrap(); - rbml_w.start_tag(tag_meta_item_list); - rbml_w.wr_tagged_str(tag_meta_item_name, &name); - for inner_item in items { - encode_meta_item(rbml_w, &inner_item); - } - rbml_w.end_tag(); - } -} - fn encode_attributes(rbml_w: &mut Encoder, attrs: &[ast::Attribute]) { rbml_w.start_tag(tag_attributes); - for attr in attrs { - rbml_w.start_tag(tag_attribute); - rbml_w.wr_tagged_u8(tag_attribute_is_sugared_doc, attr.node.is_sugared_doc as u8); - encode_meta_item(rbml_w, attr.meta()); - rbml_w.end_tag(); - } + rbml_w.emit_opaque(|opaque_encoder| { + attrs.encode(opaque_encoder) + }).unwrap(); rbml_w.end_tag(); } diff --git a/src/librustc_metadata/macro_import.rs b/src/librustc_metadata/macro_import.rs index b2a2dcf90fa4b..ec0c9f455cd0c 100644 --- a/src/librustc_metadata/macro_import.rs +++ b/src/librustc_metadata/macro_import.rs @@ -18,8 +18,7 @@ use rustc::session::Session; use std::collections::{HashSet, HashMap}; use syntax::parse::token; use syntax::ast; -use syntax::attr; -use syntax::attr::AttrMetaMethods; +use syntax::attr::{self, AttrNestedMetaItemMethods, AttrMetaMethods}; use syntax::ext; use syntax_pos::Span; @@ -64,8 +63,8 @@ impl<'a> ext::base::MacroLoader for MacroLoader<'a> { } if let (Some(sel), Some(names)) = (import.as_mut(), names) { for attr in names { - if attr.is_word() { - sel.insert(attr.name().clone(), attr.span()); + if let Some(word) = attr.word() { + sel.insert(word.name().clone(), attr.span()); } else { span_err!(self.sess, attr.span(), E0466, "bad macro import"); } @@ -82,8 +81,8 @@ impl<'a> ext::base::MacroLoader for MacroLoader<'a> { }; for attr in names { - if attr.is_word() { - reexport.insert(attr.name().clone(), attr.span()); + if let Some(word) = attr.word() { + reexport.insert(word.name().clone(), attr.span()); } else { call_bad_macro_reexport(self.sess, attr.span()); } diff --git a/src/librustc_plugin/load.rs b/src/librustc_plugin/load.rs index fb68eae96476f..5a8d2e58c558b 100644 --- a/src/librustc_plugin/load.rs +++ b/src/librustc_plugin/load.rs @@ -20,8 +20,7 @@ use std::env; use std::mem; use std::path::PathBuf; use syntax::ast; -use syntax::ptr::P; -use syntax::attr::AttrMetaMethods; +use syntax::attr::{AttrMetaMethods, AttrNestedMetaItemMethods}; use syntax_pos::{Span, COMMAND_LINE_SP}; /// Pointer to a registrar function. @@ -30,7 +29,7 @@ pub type PluginRegistrarFun = pub struct PluginRegistrar { pub fun: PluginRegistrarFun, - pub args: Vec>, + pub args: Vec, } struct PluginLoader<'a> { @@ -69,13 +68,14 @@ pub fn load_plugins(sess: &Session, }; for plugin in plugins { - if plugin.value_str().is_some() { - call_malformed_plugin_attribute(sess, attr.span); - continue; + // plugins must have a name and can't be key = value + match plugin.name() { + Some(ref name) if !plugin.is_value_str() => { + let args = plugin.meta_item_list().map(ToOwned::to_owned); + loader.load_plugin(plugin.span, name, args.unwrap_or_default()); + }, + _ => call_malformed_plugin_attribute(sess, attr.span), } - - let args = plugin.meta_item_list().map(ToOwned::to_owned).unwrap_or_default(); - loader.load_plugin(plugin.span, &plugin.name(), args); } } } @@ -102,7 +102,7 @@ impl<'a> PluginLoader<'a> { } } - fn load_plugin(&mut self, span: Span, name: &str, args: Vec>) { + fn load_plugin(&mut self, span: Span, name: &str, args: Vec) { let registrar = self.reader.find_plugin_registrar(span, name); if let Some((lib, svh, index)) = registrar { diff --git a/src/librustc_plugin/registry.rs b/src/librustc_plugin/registry.rs index 54fa0197de4fe..5ae6584aed425 100644 --- a/src/librustc_plugin/registry.rs +++ b/src/librustc_plugin/registry.rs @@ -19,7 +19,6 @@ use syntax::ext::base::{SyntaxExtension, NamedSyntaxExtension, NormalTT}; use syntax::ext::base::{IdentTT, MultiModifier, MultiDecorator}; use syntax::ext::base::{MacroExpanderFn, MacroRulesTT}; use syntax::parse::token; -use syntax::ptr::P; use syntax::ast; use syntax::feature_gate::AttributeType; use syntax_pos::Span; @@ -41,7 +40,7 @@ pub struct Registry<'a> { pub sess: &'a Session, #[doc(hidden)] - pub args_hidden: Option>>, + pub args_hidden: Option>, #[doc(hidden)] pub krate_span: Span, @@ -95,7 +94,7 @@ impl<'a> Registry<'a> { /// /// Returns empty slice in case the plugin was loaded /// with `--extra-plugins` - pub fn args<'b>(&'b self) -> &'b [P] { + pub fn args<'b>(&'b self) -> &'b [ast::NestedMetaItem] { self.args_hidden.as_ref().map(|v| &v[..]).unwrap_or(&[]) } diff --git a/src/librustc_trans/assert_module_sources.rs b/src/librustc_trans/assert_module_sources.rs index e0532e7476f51..e2633c829761f 100644 --- a/src/librustc_trans/assert_module_sources.rs +++ b/src/librustc_trans/assert_module_sources.rs @@ -29,7 +29,7 @@ use rustc::ty::TyCtxt; use syntax::ast; -use syntax::attr::AttrMetaMethods; +use syntax::attr::{AttrMetaMethods, AttrNestedMetaItemMethods}; use syntax::parse::token::InternedString; use {ModuleSource, ModuleTranslation}; diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index e2e655ce38bcc..02fa073dd5523 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -26,10 +26,11 @@ pub use self::Visibility::*; use syntax::abi::Abi; use syntax::ast; use syntax::attr; -use syntax::attr::{AttributeMethods, AttrMetaMethods}; +use syntax::attr::{AttributeMethods, AttrMetaMethods, AttrNestedMetaItemMethods}; use syntax::codemap::Spanned; use syntax::parse::token::{self, InternedString, keywords}; use syntax::ptr::P; +use syntax::print::pprust as syntax_pprust; use syntax_pos::{self, DUMMY_SP, Pos}; use rustc_trans::back::link; @@ -501,11 +502,24 @@ impl Attributes for [Attribute] { } } +/// This is a flattened version of the AST's Attribute + MetaItem. #[derive(Clone, RustcEncodable, RustcDecodable, PartialEq, Debug)] pub enum Attribute { Word(String), List(String, Vec), - NameValue(String, String) + NameValue(String, String), + Literal(String), +} + +impl Clean for ast::NestedMetaItem { + fn clean(&self, cx: &DocContext) -> Attribute { + if let Some(mi) = self.meta_item() { + mi.clean(cx) + } else { // must be a literal + let lit = self.literal().unwrap(); + Literal(syntax_pprust::lit_to_string(lit)) + } + } } impl Clean for ast::MetaItem { @@ -528,12 +542,28 @@ impl Clean for ast::Attribute { } // This is a rough approximation that gets us what we want. -impl attr::AttrMetaMethods for Attribute { - fn name(&self) -> InternedString { +impl attr::AttrNestedMetaItemMethods for Attribute { + fn check_name(&self, name: &str) -> bool { + self.name().map_or(false, |mi_name| &*mi_name == name) + } + + fn literal(&self) -> Option<&ast::Lit> { None } + + fn is_literal(&self) -> bool { + match *self { + Literal(..) => true, + _ => false, + } + } + + fn meta_item(&self) -> Option<&P> { None } + + fn name(&self) -> Option { match *self { Word(ref n) | List(ref n, _) | NameValue(ref n, _) => { - token::intern_and_get_ident(n) - } + Some(token::intern_and_get_ident(n)) + }, + _ => None } } @@ -545,7 +575,8 @@ impl attr::AttrMetaMethods for Attribute { _ => None, } } - fn meta_item_list<'a>(&'a self) -> Option<&'a [P]> { None } + + fn word(&self) -> Option<&P> { None } fn is_word(&self) -> bool { match *self { @@ -554,12 +585,7 @@ impl attr::AttrMetaMethods for Attribute { } } - fn is_value_str(&self) -> bool { - match *self { - NameValue(..) => true, - _ => false, - } - } + fn meta_item_list<'a>(&'a self) -> Option<&'a [ast::NestedMetaItem]> { None } fn is_meta_item_list(&self) -> bool { match *self { @@ -2535,8 +2561,8 @@ impl Clean> for doctree::Import { // Don't inline doc(hidden) imports so they can be stripped at a later stage. let denied = self.vis != hir::Public || self.attrs.iter().any(|a| { &a.name()[..] == "doc" && match a.meta_item_list() { - Some(l) => attr::contains_name(l, "no_inline") || - attr::contains_name(l, "hidden"), + Some(l) => attr::list_contains_name(l, "no_inline") || + attr::list_contains_name(l, "hidden"), None => false, } }); diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs index 7d1dbbe5dc07d..23a047f922f9d 100644 --- a/src/librustdoc/test.rs +++ b/src/librustdoc/test.rs @@ -141,6 +141,7 @@ pub fn run(input: &str, // Look for #![doc(test(no_crate_inject))], used by crates in the std facade fn scrape_test_config(krate: &::rustc::hir::Crate) -> TestOptions { use syntax::attr::AttrMetaMethods; + use syntax::attr::AttrNestedMetaItemMethods; use syntax::print::pprust; let mut opts = TestOptions { @@ -162,7 +163,7 @@ fn scrape_test_config(krate: &::rustc::hir::Crate) -> TestOptions { if attr.check_name("attr") { if let Some(l) = attr.meta_item_list() { for item in l { - opts.attrs.push(pprust::meta_item_to_string(item)); + opts.attrs.push(pprust::meta_list_item_to_string(item)); } } } diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index 0334c5ef5c4f4..4e3a81b1baeac 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -17,7 +17,7 @@ use std::mem; use syntax::abi; use syntax::ast; use syntax::attr; -use syntax::attr::AttrMetaMethods; +use syntax::attr::{AttrMetaMethods, AttrNestedMetaItemMethods}; use syntax_pos::Span; use rustc::hir::map as hir_map; @@ -333,8 +333,8 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { let node = if item.vis == hir::Public { let please_inline = item.attrs.iter().any(|item| { match item.meta_item_list() { - Some(list) if &item.name()[..] == "doc" => { - list.iter().any(|i| &i.name()[..] == "inline") + Some(list) if item.check_name("doc") => { + list.iter().any(|i| i.check_name("inline")) } _ => false, } diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs index f8a5cb0b04a8e..63fd2e7686fdf 100644 --- a/src/libsyntax/ast.rs +++ b/src/libsyntax/ast.rs @@ -439,6 +439,22 @@ pub struct Crate { pub exported_macros: Vec, } +/// A spanned compile-time attribute list item. +pub type NestedMetaItem = Spanned; + +/// Possible values inside of compile-time attribute lists. +/// +/// E.g. the '..' in `#[name(..)]`. +#[derive(Clone, Eq, RustcEncodable, RustcDecodable, Hash, Debug, PartialEq)] +pub enum NestedMetaItemKind { + /// A full MetaItem, for recursive meta items. + MetaItem(P), + /// A literal. + /// + /// E.g. "foo", 64, true + Literal(Lit), +} + /// A spanned compile-time attribute item. /// /// E.g. `#[test]`, `#[derive(..)]` or `#[feature = "foo"]` @@ -456,7 +472,7 @@ pub enum MetaItemKind { /// List meta item. /// /// E.g. `derive(..)` as in `#[derive(..)]` - List(InternedString, Vec>), + List(InternedString, Vec), /// Name value meta item. /// /// E.g. `feature = "foo"` as in `#[feature = "foo"]` @@ -472,19 +488,21 @@ impl PartialEq for MetaItemKind { Word(ref no) => (*ns) == (*no), _ => false }, + List(ref ns, ref miss) => match *other { + List(ref no, ref miso) => { + ns == no && + miss.iter().all(|mi| { + miso.iter().any(|x| x.node == mi.node) + }) + } + _ => false + }, NameValue(ref ns, ref vs) => match *other { NameValue(ref no, ref vo) => { (*ns) == (*no) && vs.node == vo.node } _ => false }, - List(ref ns, ref miss) => match *other { - List(ref no, ref miso) => { - ns == no && - miss.iter().all(|mi| miso.iter().any(|x| x.node == mi.node)) - } - _ => false - } } } } @@ -1105,6 +1123,30 @@ impl LitKind { _ => false, } } + + /// Returns true if this literal has no suffix. Note: this will return true + /// for literals with prefixes such as raw strings and byte strings. + pub fn is_unsuffixed(&self) -> bool { + match *self { + // unsuffixed variants + LitKind::Str(..) => true, + LitKind::ByteStr(..) => true, + LitKind::Byte(..) => true, + LitKind::Char(..) => true, + LitKind::Int(_, LitIntType::Unsuffixed) => true, + LitKind::FloatUnsuffixed(..) => true, + LitKind::Bool(..) => true, + // suffixed variants + LitKind::Int(_, LitIntType::Signed(..)) => false, + LitKind::Int(_, LitIntType::Unsigned(..)) => false, + LitKind::Float(..) => false, + } + } + + /// Returns true if this literal has a suffix. + pub fn is_suffixed(&self) -> bool { + !self.is_unsuffixed() + } } // NB: If you change this, you'll probably want to change the corresponding diff --git a/src/libsyntax/attr.rs b/src/libsyntax/attr.rs index b622f6861b383..4897425f2c06c 100644 --- a/src/libsyntax/attr.rs +++ b/src/libsyntax/attr.rs @@ -15,9 +15,10 @@ pub use self::ReprAttr::*; pub use self::IntType::*; use ast; -use ast::{AttrId, Attribute, Attribute_, MetaItem, MetaItemKind}; -use ast::{Expr, Item, Local, Stmt, StmtKind}; -use codemap::{respan, spanned, dummy_spanned, Spanned}; +use ast::{AttrId, Attribute, Attribute_}; +use ast::{MetaItem, MetaItemKind, NestedMetaItem, NestedMetaItemKind}; +use ast::{Lit, Expr, Item, Local, Stmt, StmtKind}; +use codemap::{respan, spanned, dummy_spanned}; use syntax_pos::{Span, BytePos, DUMMY_SP}; use errors::Handler; use feature_gate::{Features, GatedCfg}; @@ -40,6 +41,7 @@ enum AttrError { MissingSince, MissingFeature, MultipleStabilityLevels, + UnsupportedLiteral } fn handle_errors(diag: &Handler, span: Span, error: AttrError) { @@ -52,10 +54,12 @@ fn handle_errors(diag: &Handler, span: Span, error: AttrError) { AttrError::MissingFeature => span_err!(diag, span, E0546, "missing 'feature'"), AttrError::MultipleStabilityLevels => span_err!(diag, span, E0544, "multiple stability levels"), + AttrError::UnsupportedLiteral => span_err!(diag, span, E0565, "unsupported literal"), } } pub fn mark_used(attr: &Attribute) { + debug!("Marking {:?} as used.", attr); let AttrId(id) = attr.node.id; USED_ATTRS.with(|slot| { let idx = (id / 64) as usize; @@ -77,6 +81,93 @@ pub fn is_used(attr: &Attribute) -> bool { }) } +pub trait AttrNestedMetaItemMethods { + /// Returns true if this list item is a MetaItem with a name of `name`. + fn check_name(&self, name: &str) -> bool { + self.meta_item().map_or(false, |meta_item| meta_item.check_name(name)) + } + + /// Returns the name of the meta item, e.g. `foo` in `#[foo]`, + /// `#[foo="bar"]` and `#[foo(bar)]`, if self is a MetaItem + fn name(&self) -> Option { + self.meta_item().and_then(|meta_item| Some(meta_item.name())) + } + + /// Returns the MetaItem if self is a NestedMetaItemKind::MetaItem. + fn meta_item(&self) -> Option<&P>; + + /// Returns the Lit if self is a NestedMetaItemKind::Literal. + fn literal(&self) -> Option<&Lit>; + + /// Gets the string value if self is a MetaItem and the MetaItem is a + /// MetaItemKind::NameValue variant containing a string, otherwise None. + fn value_str(&self) -> Option { + self.meta_item().and_then(|meta_item| meta_item.value_str()) + } + + /// Returns a MetaItem if self is a MetaItem with Kind Word. + fn word(&self) -> Option<&P> { + self.meta_item().and_then(|meta_item| if meta_item.is_word() { + Some(meta_item) + } else { + None + }) + } + + /// Gets a list of inner meta items from a list MetaItem type. + fn meta_item_list(&self) -> Option<&[NestedMetaItem]> { + self.meta_item().and_then(|meta_item| meta_item.meta_item_list()) + } + + /// Returns `true` if the variant is MetaItem. + fn is_meta_item(&self) -> bool { + self.meta_item().is_some() + } + + /// Returns `true` if the variant is Literal. + fn is_literal(&self) -> bool { + self.literal().is_some() + } + + /// Returns `true` if self is a MetaItem and the meta item is a word. + fn is_word(&self) -> bool { + self.word().is_some() + } + + /// Returns `true` if self is a MetaItem and the meta item is a ValueString. + fn is_value_str(&self) -> bool { + self.value_str().is_some() + } + + /// Returns `true` if self is a MetaItem and the meta item is a list. + fn is_meta_item_list(&self) -> bool { + self.meta_item_list().is_some() + } + + /// Returns the Span for `self`. + fn span(&self) -> Span; +} + +impl AttrNestedMetaItemMethods for NestedMetaItem { + fn meta_item(&self) -> Option<&P> { + match self.node { + NestedMetaItemKind::MetaItem(ref item) => Some(&item), + _ => None + } + } + + fn literal(&self) -> Option<&Lit> { + match self.node { + NestedMetaItemKind::Literal(ref lit) => Some(&lit), + _ => None + } + } + + fn span(&self) -> Span { + self.span + } +} + pub trait AttrMetaMethods { fn check_name(&self, name: &str) -> bool { name == &self.name()[..] @@ -89,8 +180,9 @@ pub trait AttrMetaMethods { /// Gets the string value if self is a MetaItemKind::NameValue variant /// containing a string, otherwise None. fn value_str(&self) -> Option; + /// Gets a list of inner meta items from a list MetaItem type. - fn meta_item_list(&self) -> Option<&[P]>; + fn meta_item_list(&self) -> Option<&[NestedMetaItem]>; /// Indicates if the attribute is a Word. fn is_word(&self) -> bool; @@ -116,11 +208,14 @@ impl AttrMetaMethods for Attribute { } matches } + fn name(&self) -> InternedString { self.meta().name() } + fn value_str(&self) -> Option { self.meta().value_str() } - fn meta_item_list(&self) -> Option<&[P]> { + + fn meta_item_list(&self) -> Option<&[NestedMetaItem]> { self.meta().meta_item_list() } @@ -150,7 +245,7 @@ impl AttrMetaMethods for MetaItem { } } - fn meta_item_list(&self) -> Option<&[P]> { + fn meta_item_list(&self) -> Option<&[NestedMetaItem]> { match self.node { MetaItemKind::List(_, ref l) => Some(&l[..]), _ => None @@ -171,7 +266,7 @@ impl AttrMetaMethods for MetaItem { impl AttrMetaMethods for P { fn name(&self) -> InternedString { (**self).name() } fn value_str(&self) -> Option { (**self).value_str() } - fn meta_item_list(&self) -> Option<&[P]> { + fn meta_item_list(&self) -> Option<&[NestedMetaItem]> { (**self).meta_item_list() } fn is_word(&self) -> bool { (**self).is_word() } @@ -229,10 +324,14 @@ pub fn mk_name_value_item(name: InternedString, value: ast::Lit) mk_spanned_name_value_item(DUMMY_SP, name, value) } -pub fn mk_list_item(name: InternedString, items: Vec>) -> P { +pub fn mk_list_item(name: InternedString, items: Vec) -> P { mk_spanned_list_item(DUMMY_SP, name, items) } +pub fn mk_list_word_item(name: InternedString) -> ast::NestedMetaItem { + dummy_spanned(NestedMetaItemKind::MetaItem(mk_spanned_word_item(DUMMY_SP, name))) +} + pub fn mk_word_item(name: InternedString) -> P { mk_spanned_word_item(DUMMY_SP, name) } @@ -242,7 +341,7 @@ pub fn mk_spanned_name_value_item(sp: Span, name: InternedString, value: ast::Li P(respan(sp, MetaItemKind::NameValue(name, value))) } -pub fn mk_spanned_list_item(sp: Span, name: InternedString, items: Vec>) +pub fn mk_spanned_list_item(sp: Span, name: InternedString, items: Vec) -> P { P(respan(sp, MetaItemKind::List(name, items))) } @@ -332,6 +431,14 @@ pub fn contains(haystack: &[P], needle: &MetaItem) -> bool { }) } +pub fn list_contains_name(items: &[AM], name: &str) -> bool { + debug!("attr::list_contains_name (name={})", name); + items.iter().any(|item| { + debug!(" testing: {:?}", item.name()); + item.check_name(name) + }) +} + pub fn contains_name(metas: &[AM], name: &str) -> bool { debug!("attr::contains_name (name={})", name); metas.iter().any(|item| { @@ -357,27 +464,6 @@ pub fn last_meta_item_value_str_by_name(items: &[P], name: &str) /* Higher-level applications */ -pub fn sort_meta_items(items: Vec>) -> Vec> { - // This is sort of stupid here, but we need to sort by - // human-readable strings. - let mut v = items.into_iter() - .map(|mi| (mi.name(), mi)) - .collect::)>>(); - - v.sort_by(|&(ref a, _), &(ref b, _)| a.cmp(b)); - - // There doesn't seem to be a more optimal way to do this - v.into_iter().map(|(_, m)| m.map(|Spanned {node, span}| { - Spanned { - node: match node { - MetaItemKind::List(n, mis) => MetaItemKind::List(n, sort_meta_items(mis)), - _ => node - }, - span: span - } - })).collect() -} - pub fn find_crate_name(attrs: &[Attribute]) -> Option { first_attr_value_str_by_name(attrs, "crate_name") } @@ -427,14 +513,15 @@ pub fn find_inline_attr(diagnostic: Option<&Handler>, attrs: &[Attribute]) -> In if items.len() != 1 { diagnostic.map(|d|{ span_err!(d, attr.span, E0534, "expected one argument"); }); InlineAttr::None - } else if contains_name(&items[..], "always") { + } else if list_contains_name(&items[..], "always") { InlineAttr::Always - } else if contains_name(&items[..], "never") { + } else if list_contains_name(&items[..], "never") { InlineAttr::Never } else { diagnostic.map(|d| { - span_err!(d, (*items[0]).span, E0535, "invalid argument"); + span_err!(d, items[0].span, E0535, "invalid argument"); }); + InlineAttr::None } } @@ -453,27 +540,44 @@ pub fn requests_inline(attrs: &[Attribute]) -> bool { /// Tests if a cfg-pattern matches the cfg set pub fn cfg_matches(cfgs: &[P], cfg: &ast::MetaItem, - sess: &ParseSess, features: Option<&Features>) + sess: &ParseSess, + features: Option<&Features>) -> bool { match cfg.node { - ast::MetaItemKind::List(ref pred, ref mis) if &pred[..] == "any" => - mis.iter().any(|mi| cfg_matches(cfgs, &mi, sess, features)), - ast::MetaItemKind::List(ref pred, ref mis) if &pred[..] == "all" => - mis.iter().all(|mi| cfg_matches(cfgs, &mi, sess, features)), - ast::MetaItemKind::List(ref pred, ref mis) if &pred[..] == "not" => { - if mis.len() != 1 { - span_err!(sess.span_diagnostic, cfg.span, E0536, "expected 1 cfg-pattern"); - return false; + ast::MetaItemKind::List(ref pred, ref mis) => { + for mi in mis.iter() { + if !mi.is_meta_item() { + handle_errors(&sess.span_diagnostic, mi.span, AttrError::UnsupportedLiteral); + return false; + } + } + + // The unwraps below may look dangerous, but we've already asserted + // that they won't fail with the loop above. + match &pred[..] { + "any" => mis.iter().any(|mi| { + cfg_matches(cfgs, mi.meta_item().unwrap(), sess, features) + }), + "all" => mis.iter().all(|mi| { + cfg_matches(cfgs, mi.meta_item().unwrap(), sess, features) + }), + "not" => { + if mis.len() != 1 { + span_err!(sess.span_diagnostic, cfg.span, E0536, "expected 1 cfg-pattern"); + return false; + } + + !cfg_matches(cfgs, mis[0].meta_item().unwrap(), sess, features) + }, + p => { + span_err!(sess.span_diagnostic, cfg.span, E0537, "invalid predicate `{}`", p); + false + } } - !cfg_matches(cfgs, &mis[0], sess, features) - } - ast::MetaItemKind::List(ref pred, _) => { - span_err!(sess.span_diagnostic, cfg.span, E0537, "invalid predicate `{}`", pred); - false }, ast::MetaItemKind::Word(_) | ast::MetaItemKind::NameValue(..) => { - if let (Some(features), Some(gated_cfg)) = (features, GatedCfg::gate(cfg)) { - gated_cfg.check_and_emit(sess, features); + if let (Some(feats), Some(gated_cfg)) = (features, GatedCfg::gate(cfg)) { + gated_cfg.check_and_emit(sess, feats); } contains(cfgs, cfg) } @@ -557,14 +661,19 @@ fn find_stability_generic<'a, I>(diagnostic: &Handler, let mut since = None; let mut reason = None; for meta in metas { - match &*meta.name() { - "since" => if !get(meta, &mut since) { continue 'outer }, - "reason" => if !get(meta, &mut reason) { continue 'outer }, - _ => { - handle_errors(diagnostic, meta.span, - AttrError::UnknownMetaItem(meta.name())); - continue 'outer + if let Some(mi) = meta.meta_item() { + match &*mi.name() { + "since" => if !get(mi, &mut since) { continue 'outer }, + "reason" => if !get(mi, &mut reason) { continue 'outer }, + _ => { + handle_errors(diagnostic, mi.span, + AttrError::UnknownMetaItem(mi.name())); + continue 'outer + } } + } else { + handle_errors(diagnostic, meta.span, AttrError::UnsupportedLiteral); + continue 'outer } } @@ -595,15 +704,20 @@ fn find_stability_generic<'a, I>(diagnostic: &Handler, let mut reason = None; let mut issue = None; for meta in metas { - match &*meta.name() { - "feature" => if !get(meta, &mut feature) { continue 'outer }, - "reason" => if !get(meta, &mut reason) { continue 'outer }, - "issue" => if !get(meta, &mut issue) { continue 'outer }, - _ => { - handle_errors(diagnostic, meta.span, - AttrError::UnknownMetaItem(meta.name())); - continue 'outer + if let Some(mi) = meta.meta_item() { + match &*mi.name() { + "feature" => if !get(mi, &mut feature) { continue 'outer }, + "reason" => if !get(mi, &mut reason) { continue 'outer }, + "issue" => if !get(mi, &mut issue) { continue 'outer }, + _ => { + handle_errors(diagnostic, meta.span, + AttrError::UnknownMetaItem(mi.name())); + continue 'outer + } } + } else { + handle_errors(diagnostic, meta.span, AttrError::UnsupportedLiteral); + continue 'outer } } @@ -645,14 +759,19 @@ fn find_stability_generic<'a, I>(diagnostic: &Handler, let mut feature = None; let mut since = None; for meta in metas { - match &*meta.name() { - "feature" => if !get(meta, &mut feature) { continue 'outer }, - "since" => if !get(meta, &mut since) { continue 'outer }, - _ => { - handle_errors(diagnostic, meta.span, - AttrError::UnknownMetaItem(meta.name())); - continue 'outer + if let NestedMetaItemKind::MetaItem(ref mi) = meta.node { + match &*mi.name() { + "feature" => if !get(mi, &mut feature) { continue 'outer }, + "since" => if !get(mi, &mut since) { continue 'outer }, + _ => { + handle_errors(diagnostic, meta.span, + AttrError::UnknownMetaItem(mi.name())); + continue 'outer + } } + } else { + handle_errors(diagnostic, meta.span, AttrError::UnsupportedLiteral); + continue 'outer } } @@ -739,14 +858,19 @@ fn find_deprecation_generic<'a, I>(diagnostic: &Handler, let mut since = None; let mut note = None; for meta in metas { - match &*meta.name() { - "since" => if !get(meta, &mut since) { continue 'outer }, - "note" => if !get(meta, &mut note) { continue 'outer }, - _ => { - handle_errors(diagnostic, meta.span, - AttrError::UnknownMetaItem(meta.name())); - continue 'outer + if let NestedMetaItemKind::MetaItem(ref mi) = meta.node { + match &*mi.name() { + "since" => if !get(mi, &mut since) { continue 'outer }, + "note" => if !get(mi, &mut note) { continue 'outer }, + _ => { + handle_errors(diagnostic, meta.span, + AttrError::UnknownMetaItem(mi.name())); + continue 'outer + } } + } else { + handle_errors(diagnostic, meta.span, AttrError::UnsupportedLiteral); + continue 'outer } } @@ -796,32 +920,36 @@ pub fn find_repr_attrs(diagnostic: &Handler, attr: &Attribute) -> Vec ast::MetaItemKind::List(ref s, ref items) if s == "repr" => { mark_used(attr); for item in items { - match item.node { - ast::MetaItemKind::Word(ref word) => { - let hint = match &word[..] { - // Can't use "extern" because it's not a lexical identifier. - "C" => Some(ReprExtern), - "packed" => Some(ReprPacked), - "simd" => Some(ReprSimd), - _ => match int_type_of_word(&word) { - Some(ity) => Some(ReprInt(item.span, ity)), - None => { - // Not a word we recognize - span_err!(diagnostic, item.span, E0552, - "unrecognized representation hint"); - None - } - } - }; + if !item.is_meta_item() { + handle_errors(diagnostic, item.span, AttrError::UnsupportedLiteral); + continue + } - match hint { - Some(h) => acc.push(h), - None => { } + if let Some(mi) = item.word() { + let word = &*mi.name(); + let hint = match word { + // Can't use "extern" because it's not a lexical identifier. + "C" => Some(ReprExtern), + "packed" => Some(ReprPacked), + "simd" => Some(ReprSimd), + _ => match int_type_of_word(word) { + Some(ity) => Some(ReprInt(item.span, ity)), + None => { + // Not a word we recognize + span_err!(diagnostic, item.span, E0552, + "unrecognized representation hint"); + None + } } + }; + + match hint { + Some(h) => acc.push(h), + None => { } } - // Not a word: - _ => span_err!(diagnostic, item.span, E0553, - "unrecognized enum representation hint"), + } else { + span_err!(diagnostic, item.span, E0553, + "unrecognized enum representation hint"); } } } diff --git a/src/libsyntax/config.rs b/src/libsyntax/config.rs index a825cf866a878..4663143f4b1f8 100644 --- a/src/libsyntax/config.rs +++ b/src/libsyntax/config.rs @@ -8,7 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use attr::{AttrMetaMethods, HasAttrs}; +use attr::{AttrMetaMethods, AttrNestedMetaItemMethods, HasAttrs}; use feature_gate::{emit_feature_err, EXPLAIN_STMT_ATTR_SYNTAX, Features, get_features, GateIssue}; use fold::Folder; use {fold, attr}; @@ -52,6 +52,7 @@ impl<'a> StripUnconfigured<'a> { return None; } }; + let (cfg, mi) = match (attr_list.len(), attr_list.get(0), attr_list.get(1)) { (2, Some(cfg), Some(mi)) => (cfg, mi), _ => { @@ -61,15 +62,24 @@ impl<'a> StripUnconfigured<'a> { } }; - if attr::cfg_matches(self.config, &cfg, self.sess, self.features) { - self.process_cfg_attr(respan(mi.span, ast::Attribute_ { - id: attr::mk_attr_id(), - style: attr.node.style, - value: mi.clone(), - is_sugared_doc: false, - })) - } else { - None + use attr::cfg_matches; + match (cfg.meta_item(), mi.meta_item()) { + (Some(cfg), Some(mi)) => + if cfg_matches(self.config, &cfg, self.sess, self.features) { + self.process_cfg_attr(respan(mi.span, ast::Attribute_ { + id: attr::mk_attr_id(), + style: attr.node.style, + value: mi.clone(), + is_sugared_doc: false, + })) + } else { + None + }, + _ => { + let msg = "unexpected literal(s) in `#[cfg_attr(, )]`"; + self.sess.span_diagnostic.span_err(attr.span, msg); + None + } } } @@ -91,7 +101,12 @@ impl<'a> StripUnconfigured<'a> { return true; } - attr::cfg_matches(self.config, &mis[0], self.sess, self.features) + if !mis[0].is_meta_item() { + self.sess.span_diagnostic.span_err(mis[0].span, "unexpected literal"); + return true; + } + + attr::cfg_matches(self.config, mis[0].meta_item().unwrap(), self.sess, self.features) }) } diff --git a/src/libsyntax/diagnostic_list.rs b/src/libsyntax/diagnostic_list.rs index 010b1d638e63c..9110e989a8a14 100644 --- a/src/libsyntax/diagnostic_list.rs +++ b/src/libsyntax/diagnostic_list.rs @@ -161,6 +161,24 @@ fn main() {} ``` "##, +E0565: r##" +A literal was used in an attribute that doesn't support literals. + +Erroneous code example: + +```compile_fail,E0565 +#[inline("always")] // error: unsupported literal +pub fn something() {} +``` + +Literals in attributes are new and largely unsupported. Work to support literals +where appropriate is ongoing. Try using an unquoted name instead: + +``` +#[inline(always)] +pub fn something() {} +``` +"##, } register_diagnostics! { diff --git a/src/libsyntax/ext/build.rs b/src/libsyntax/ext/build.rs index 5d6429f7bdfff..1d3939df6fb82 100644 --- a/src/libsyntax/ext/build.rs +++ b/src/libsyntax/ext/build.rs @@ -277,10 +277,13 @@ pub trait AstBuilder { fn attribute(&self, sp: Span, mi: P) -> ast::Attribute; fn meta_word(&self, sp: Span, w: InternedString) -> P; + + fn meta_list_item_word(&self, sp: Span, w: InternedString) -> ast::NestedMetaItem; + fn meta_list(&self, sp: Span, name: InternedString, - mis: Vec> ) + mis: Vec ) -> P; fn meta_name_value(&self, sp: Span, @@ -1141,10 +1144,16 @@ impl<'a> AstBuilder for ExtCtxt<'a> { fn meta_word(&self, sp: Span, w: InternedString) -> P { attr::mk_spanned_word_item(sp, w) } - fn meta_list(&self, sp: Span, name: InternedString, mis: Vec>) + + fn meta_list_item_word(&self, sp: Span, w: InternedString) -> ast::NestedMetaItem { + respan(sp, ast::NestedMetaItemKind::MetaItem(attr::mk_spanned_word_item(sp, w))) + } + + fn meta_list(&self, sp: Span, name: InternedString, mis: Vec) -> P { attr::mk_spanned_list_item(sp, name, mis) } + fn meta_name_value(&self, sp: Span, name: InternedString, value: ast::LitKind) -> P { attr::mk_spanned_name_value_item(sp, name, respan(sp, value)) diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index d746f8e21141f..df1d5c4d9ca2f 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -26,10 +26,8 @@ use self::AttributeType::*; use self::AttributeGate::*; use abi::Abi; -use ast::{NodeId, PatKind}; -use ast; -use attr; -use attr::AttrMetaMethods; +use ast::{self, NodeId, PatKind}; +use attr::{self, AttrMetaMethods, AttrNestedMetaItemMethods}; use codemap::CodeMap; use syntax_pos::Span; use errors::Handler; @@ -283,7 +281,10 @@ declare_features! ( (active, relaxed_adts, "1.12.0", Some(35626)), // The `!` type - (active, never_type, "1.13.0", Some(35121)) + (active, never_type, "1.13.0", Some(35121)), + + // Allows all literals in attribute lists and values of key-value pairs. + (active, attr_literals, "1.13.0", Some(34981)) ); declare_features! ( @@ -831,11 +832,34 @@ impl<'a> PostExpansionVisitor<'a> { } } +fn contains_novel_literal(item: &ast::MetaItem) -> bool { + use ast::MetaItemKind::*; + use ast::NestedMetaItemKind::*; + + match item.node { + Word(..) => false, + NameValue(_, ref lit) => !lit.node.is_str(), + List(_, ref list) => list.iter().any(|li| { + match li.node { + MetaItem(ref mi) => contains_novel_literal(&**mi), + Literal(_) => true, + } + }), + } +} + impl<'a> Visitor for PostExpansionVisitor<'a> { fn visit_attribute(&mut self, attr: &ast::Attribute) { if !self.context.cm.span_allows_unstable(attr.span) { + // check for gated attributes self.context.check_attribute(attr, false); } + + if contains_novel_literal(&*(attr.node.value)) { + gate_feature_post!(&self, attr_literals, attr.span, + "non-string literals in attributes, or string \ + literals in top-level positions, are experimental"); + } } fn visit_name(&mut self, sp: Span, name: ast::Name) { @@ -895,7 +919,7 @@ impl<'a> Visitor for PostExpansionVisitor<'a> { for attr in &i.attrs { if attr.name() == "repr" { for item in attr.meta_item_list().unwrap_or(&[]) { - if item.name() == "simd" { + if item.check_name("simd") { gate_feature_post!(&self, repr_simd, i.span, "SIMD types are experimental \ and possibly buggy"); @@ -1155,13 +1179,14 @@ pub fn get_features(span_handler: &Handler, krate_attrs: &[ast::Attribute]) -> F } Some(list) => { for mi in list { - let name = if mi.is_word() { - mi.name() - } else { - span_err!(span_handler, mi.span, E0556, - "malformed feature, expected just one word"); - continue - }; + let name = if let Some(word) = mi.word() { + word.name() + } else { + span_err!(span_handler, mi.span, E0556, + "malformed feature, expected just one word"); + continue + }; + if let Some(&(_, _, _, setter)) = ACTIVE_FEATURES.iter() .find(|& &(n, _, _, _)| name == n) { *(setter(&mut features)) = true; diff --git a/src/libsyntax/fold.rs b/src/libsyntax/fold.rs index b257ab98987dc..b361a856dbe2c 100644 --- a/src/libsyntax/fold.rs +++ b/src/libsyntax/fold.rs @@ -47,6 +47,10 @@ pub trait Folder : Sized { noop_fold_meta_items(meta_items, self) } + fn fold_meta_list_item(&mut self, list_item: NestedMetaItem) -> NestedMetaItem { + noop_fold_meta_list_item(list_item, self) + } + fn fold_meta_item(&mut self, meta_item: P) -> P { noop_fold_meta_item(meta_item, self) } @@ -513,12 +517,25 @@ pub fn noop_fold_mac(Spanned {node, span}: Mac, fld: &mut T) -> Mac { } } +pub fn noop_fold_meta_list_item(li: NestedMetaItem, fld: &mut T) + -> NestedMetaItem { + Spanned { + node: match li.node { + NestedMetaItemKind::MetaItem(mi) => { + NestedMetaItemKind::MetaItem(fld.fold_meta_item(mi)) + }, + NestedMetaItemKind::Literal(lit) => NestedMetaItemKind::Literal(lit) + }, + span: fld.new_span(li.span) + } +} + pub fn noop_fold_meta_item(mi: P, fld: &mut T) -> P { mi.map(|Spanned {node, span}| Spanned { node: match node { MetaItemKind::Word(id) => MetaItemKind::Word(id), MetaItemKind::List(id, mis) => { - MetaItemKind::List(id, mis.move_map(|e| fld.fold_meta_item(e))) + MetaItemKind::List(id, mis.move_map(|e| fld.fold_meta_list_item(e))) } MetaItemKind::NameValue(id, s) => MetaItemKind::NameValue(id, s) }, diff --git a/src/libsyntax/parse/attr.rs b/src/libsyntax/parse/attr.rs index 2ae3236cd5aa7..27dd055cd3ae7 100644 --- a/src/libsyntax/parse/attr.rs +++ b/src/libsyntax/parse/attr.rs @@ -193,9 +193,26 @@ impl<'a> Parser<'a> { Ok(attrs) } - /// matches meta_item = IDENT - /// | IDENT = lit - /// | IDENT meta_seq + fn parse_unsuffixed_lit(&mut self) -> PResult<'a, ast::Lit> { + let lit = self.parse_lit()?; + debug!("Checking if {:?} is unusuffixed.", lit); + + if !lit.node.is_unsuffixed() { + let msg = "suffixed literals are not allowed in attributes"; + self.diagnostic().struct_span_err(lit.span, msg) + .help("instead of using a suffixed literal \ + (1u8, 1.0f32, etc.), use an unsuffixed version \ + (1, 1.0, etc.).") + .emit() + } + + Ok(lit) + } + + /// Per RFC#1559, matches the following grammar: + /// + /// meta_item : IDENT ( '=' UNSUFFIXED_LIT | '(' meta_item_inner? ')' )? ; + /// meta_item_inner : (meta_item | UNSUFFIXED_LIT) (',' meta_item_inner)? ; pub fn parse_meta_item(&mut self) -> PResult<'a, P> { let nt_meta = match self.token { token::Interpolated(token::NtMeta(ref e)) => Some(e.clone()), @@ -213,16 +230,7 @@ impl<'a> Parser<'a> { match self.token { token::Eq => { self.bump(); - let lit = self.parse_lit()?; - // FIXME #623 Non-string meta items are not serialized correctly; - // just forbid them for now - match lit.node { - ast::LitKind::Str(..) => {} - _ => { - self.span_err(lit.span, - "non-string literals are not allowed in meta-items"); - } - } + let lit = self.parse_unsuffixed_lit()?; let hi = self.span.hi; Ok(P(spanned(lo, hi, ast::MetaItemKind::NameValue(name, lit)))) } @@ -238,11 +246,35 @@ impl<'a> Parser<'a> { } } - /// matches meta_seq = ( COMMASEP(meta_item) ) - fn parse_meta_seq(&mut self) -> PResult<'a, Vec>> { + /// matches meta_item_inner : (meta_item | UNSUFFIXED_LIT) ; + fn parse_meta_item_inner(&mut self) -> PResult<'a, ast::NestedMetaItem> { + let sp = self.span; + let lo = self.span.lo; + + match self.parse_unsuffixed_lit() { + Ok(lit) => { + return Ok(spanned(lo, self.span.hi, ast::NestedMetaItemKind::Literal(lit))) + } + Err(ref mut err) => self.diagnostic().cancel(err) + } + + match self.parse_meta_item() { + Ok(mi) => { + return Ok(spanned(lo, self.span.hi, ast::NestedMetaItemKind::MetaItem(mi))) + } + Err(ref mut err) => self.diagnostic().cancel(err) + } + + let found = self.this_token_to_string(); + let msg = format!("expected unsuffixed literal or identifier, found {}", found); + Err(self.diagnostic().struct_span_err(sp, &msg)) + } + + /// matches meta_seq = ( COMMASEP(meta_item_inner) ) + fn parse_meta_seq(&mut self) -> PResult<'a, Vec> { self.parse_unspanned_seq(&token::OpenDelim(token::Paren), &token::CloseDelim(token::Paren), SeqSep::trailing_allowed(token::Comma), - |p: &mut Parser<'a>| p.parse_meta_item()) + |p: &mut Parser<'a>| p.parse_meta_item_inner()) } } diff --git a/src/libsyntax/print/pprust.rs b/src/libsyntax/print/pprust.rs index a77c678248b56..562cc896aef01 100644 --- a/src/libsyntax/print/pprust.rs +++ b/src/libsyntax/print/pprust.rs @@ -120,7 +120,7 @@ pub fn print_crate<'a>(cm: &'a CodeMap, // of the feature gate, so we fake them up here. // #![feature(prelude_import)] - let prelude_import_meta = attr::mk_word_item(InternedString::new("prelude_import")); + let prelude_import_meta = attr::mk_list_word_item(InternedString::new("prelude_import")); let list = attr::mk_list_item(InternedString::new("feature"), vec![prelude_import_meta]); let fake_attr = attr::mk_attr_inner(attr::mk_attr_id(), list); @@ -406,6 +406,10 @@ pub fn block_to_string(blk: &ast::Block) -> String { }) } +pub fn meta_list_item_to_string(li: &ast::NestedMetaItem) -> String { + to_string(|s| s.print_meta_list_item(li)) +} + pub fn meta_item_to_string(mi: &ast::MetaItem) -> String { to_string(|s| s.print_meta_item(mi)) } @@ -764,6 +768,17 @@ pub trait PrintState<'a> { } } + fn print_meta_list_item(&mut self, item: &ast::NestedMetaItem) -> io::Result<()> { + match item.node { + ast::NestedMetaItemKind::MetaItem(ref mi) => { + self.print_meta_item(mi) + }, + ast::NestedMetaItemKind::Literal(ref lit) => { + self.print_literal(lit) + } + } + } + fn print_meta_item(&mut self, item: &ast::MetaItem) -> io::Result<()> { try!(self.ibox(INDENT_UNIT)); match item.node { @@ -780,7 +795,7 @@ pub trait PrintState<'a> { try!(self.popen()); try!(self.commasep(Consistent, &items[..], - |s, i| s.print_meta_item(&i))); + |s, i| s.print_meta_list_item(&i))); try!(self.pclose()); } } diff --git a/src/libsyntax/test.rs b/src/libsyntax/test.rs index faf6a17a15045..ce917f248e1ef 100644 --- a/src/libsyntax/test.rs +++ b/src/libsyntax/test.rs @@ -19,8 +19,7 @@ use std::iter; use std::slice; use std::mem; use std::vec; -use attr::AttrMetaMethods; -use attr; +use attr::{self, AttrMetaMethods, AttrNestedMetaItemMethods}; use syntax_pos::{self, DUMMY_SP, NO_EXPANSION, Span, FileMap, BytePos}; use std::rc::Rc; @@ -210,9 +209,8 @@ impl fold::Folder for EntryPointCleaner { folded.map(|ast::Item {id, ident, attrs, node, vis, span}| { let allow_str = InternedString::new("allow"); let dead_code_str = InternedString::new("dead_code"); - let allow_dead_code_item = - attr::mk_list_item(allow_str, - vec![attr::mk_word_item(dead_code_str)]); + let word_vec = vec![attr::mk_list_word_item(dead_code_str)]; + let allow_dead_code_item = attr::mk_list_item(allow_str, word_vec); let allow_dead_code = attr::mk_attr_outer(attr::mk_attr_id(), allow_dead_code_item); @@ -413,6 +411,7 @@ fn should_panic(i: &ast::Item) -> ShouldPanic { Some(attr) => { let msg = attr.meta_item_list() .and_then(|list| list.iter().find(|mi| mi.check_name("expected"))) + .and_then(|li| li.meta_item()) .and_then(|mi| mi.value_str()); ShouldPanic::Yes(msg) } diff --git a/src/libsyntax_ext/deriving/cmp/eq.rs b/src/libsyntax_ext/deriving/cmp/eq.rs index 2ab0f0ff54669..2515435abeb9e 100644 --- a/src/libsyntax_ext/deriving/cmp/eq.rs +++ b/src/libsyntax_ext/deriving/cmp/eq.rs @@ -40,7 +40,7 @@ pub fn expand_deriving_eq(cx: &mut ExtCtxt, } let inline = cx.meta_word(span, InternedString::new("inline")); - let hidden = cx.meta_word(span, InternedString::new("hidden")); + let hidden = cx.meta_list_item_word(span, InternedString::new("hidden")); let doc = cx.meta_list(span, InternedString::new("doc"), vec![hidden]); let attrs = vec![cx.attribute(span, inline), cx.attribute(span, doc)]; let trait_def = TraitDef { diff --git a/src/libsyntax_ext/deriving/generic/mod.rs b/src/libsyntax_ext/deriving/generic/mod.rs index cd49e7ec9d2c6..bae40ddf45c9f 100644 --- a/src/libsyntax_ext/deriving/generic/mod.rs +++ b/src/libsyntax_ext/deriving/generic/mod.rs @@ -623,7 +623,7 @@ impl<'a> TraitDef<'a> { let unused_qual = cx.attribute(self.span, cx.meta_list(self.span, InternedString::new("allow"), - vec![cx.meta_word(self.span, + vec![cx.meta_list_item_word(self.span, InternedString::new("unused_qualifications"))])); let mut a = vec![attr, unused_qual]; a.extend(self.attributes.iter().cloned()); diff --git a/src/libsyntax_ext/deriving/mod.rs b/src/libsyntax_ext/deriving/mod.rs index aee86b246b985..ffc1bfd6db8cd 100644 --- a/src/libsyntax_ext/deriving/mod.rs +++ b/src/libsyntax_ext/deriving/mod.rs @@ -11,7 +11,7 @@ //! The compiler code necessary to implement the `#[derive]` extensions. use syntax::ast::{self, MetaItem}; -use syntax::attr::AttrMetaMethods; +use syntax::attr::{AttrNestedMetaItemMethods, AttrMetaMethods}; use syntax::ext::base::{Annotatable, ExtCtxt, SyntaxEnv}; use syntax::ext::base::{MultiDecorator, MultiItemDecorator, MultiModifier}; use syntax::ext::build::AstBuilder; @@ -98,8 +98,8 @@ fn expand_derive(cx: &mut ExtCtxt, let mut eq_span = None; for titem in traits.iter().rev() { - let tname = if titem.is_word() { - titem.name() + let tname = if let Some(word) = titem.word() { + word.name() } else { cx.span_err(titem.span, "malformed `derive` entry"); continue; diff --git a/src/test/compile-fail-fulldeps/auxiliary/macro_crate_test.rs b/src/test/compile-fail-fulldeps/auxiliary/macro_crate_test.rs index a6bc9db199c8b..6db10eeae769b 100644 --- a/src/test/compile-fail-fulldeps/auxiliary/macro_crate_test.rs +++ b/src/test/compile-fail-fulldeps/auxiliary/macro_crate_test.rs @@ -17,7 +17,8 @@ extern crate syntax_pos; extern crate rustc; extern crate rustc_plugin; -use syntax::ast::{self, Item, MetaItem, ImplItem, TraitItem, ItemKind}; +use syntax::ast::{self, Item, MetaItem, ItemKind}; +use syntax::attr::{AttrMetaMethods, AttrNestedMetaItemMethods}; use syntax::ext::base::*; use syntax::parse::{self, token}; use syntax::ptr::P; @@ -62,8 +63,8 @@ fn expand_identity(cx: &mut ExtCtxt, _span: Span, tts: &[TokenTree]) } fn expand_into_foo_multi(cx: &mut ExtCtxt, - sp: Span, - attr: &MetaItem, + _sp: Span, + _attr: &MetaItem, it: Annotatable) -> Annotatable { match it { Annotatable::Item(it) => { @@ -72,7 +73,7 @@ fn expand_into_foo_multi(cx: &mut ExtCtxt, ..(*quote_item!(cx, enum Foo2 { Bar2, Baz2 }).unwrap()).clone() })) } - Annotatable::ImplItem(it) => { + Annotatable::ImplItem(_) => { quote_item!(cx, impl X { fn foo(&self) -> i32 { 42 } }).unwrap().and_then(|i| { match i.node { ItemKind::Impl(_, _, _, _, _, mut items) => { @@ -82,7 +83,7 @@ fn expand_into_foo_multi(cx: &mut ExtCtxt, } }) } - Annotatable::TraitItem(it) => { + Annotatable::TraitItem(_) => { quote_item!(cx, trait X { fn foo(&self) -> i32 { 0 } }).unwrap().and_then(|i| { match i.node { ItemKind::Trait(_, _, _, mut items) => { @@ -97,15 +98,15 @@ fn expand_into_foo_multi(cx: &mut ExtCtxt, // Create a duplicate of the annotatable, based on the MetaItem fn expand_duplicate(cx: &mut ExtCtxt, - sp: Span, + _sp: Span, mi: &MetaItem, it: &Annotatable, push: &mut FnMut(Annotatable)) { let copy_name = match mi.node { ast::MetaItemKind::List(_, ref xs) => { - if let ast::MetaItemKind::Word(ref w) = xs[0].node { - token::str_to_ident(&w) + if let Some(word) = xs[0].word() { + token::str_to_ident(&word.name()) } else { cx.span_err(mi.span, "Expected word"); return; diff --git a/src/test/compile-fail/E0565-1.rs b/src/test/compile-fail/E0565-1.rs new file mode 100644 index 0000000000000..d3e68c7c0daf8 --- /dev/null +++ b/src/test/compile-fail/E0565-1.rs @@ -0,0 +1,17 @@ +// Copyright 2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(attr_literals)] + +// deprecated doesn't currently support literals +#[deprecated("since")] //~ ERROR E0565 +fn f() { } + +fn main() { } diff --git a/src/test/compile-fail/E0565.rs b/src/test/compile-fail/E0565.rs new file mode 100644 index 0000000000000..b2d369223e7da --- /dev/null +++ b/src/test/compile-fail/E0565.rs @@ -0,0 +1,17 @@ +// Copyright 2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(attr_literals)] + +// repr currently doesn't support literals +#[repr("C")] //~ ERROR E0565 +struct A { } + +fn main() { } diff --git a/src/test/compile-fail/attr-literals.rs b/src/test/compile-fail/attr-literals.rs new file mode 100644 index 0000000000000..b54288035175d --- /dev/null +++ b/src/test/compile-fail/attr-literals.rs @@ -0,0 +1,33 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Check that literals in attributes parse just fine. + +#![feature(rustc_attrs, attr_literals)] +#![allow(dead_code)] +#![allow(unused_variables)] + +#[fake_attr] //~ ERROR attribute `fake_attr` is currently unknown +#[fake_attr(100)] //~ ERROR attribute `fake_attr` is currently unknown +#[fake_attr(1, 2, 3)] //~ ERROR attribute `fake_attr` is currently unknown +#[fake_attr("hello")] //~ ERROR attribute `fake_attr` is currently unknown +#[fake_attr(name = "hello")] //~ ERROR attribute `fake_attr` is currently unknown +#[fake_attr(1, "hi", key = 12, true, false)] //~ ERROR attribute `fake_attr` is currently unknown +#[fake_attr(key = "hello", val = 10)] //~ ERROR attribute `fake_attr` is currently unknown +#[fake_attr(key("hello"), val(10))] //~ ERROR attribute `fake_attr` is currently unknown +#[fake_attr(enabled = true, disabled = false)] //~ ERROR attribute `fake_attr` is currently unknown +#[fake_attr(true)] //~ ERROR attribute `fake_attr` is currently unknown +#[fake_attr(pi = 3.14159)] //~ ERROR attribute `fake_attr` is currently unknown +#[fake_attr(b"hi")] //~ ERROR attribute `fake_attr` is currently unknown +#[fake_doc(r"doc")] //~ ERROR attribute `fake_doc` is currently unknown +struct Q { } + +#[rustc_error] +fn main() { } diff --git a/src/test/compile-fail/gated-attr-literals.rs b/src/test/compile-fail/gated-attr-literals.rs new file mode 100644 index 0000000000000..f3132d5593e62 --- /dev/null +++ b/src/test/compile-fail/gated-attr-literals.rs @@ -0,0 +1,44 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Check that literals in attributes don't parse without the feature gate. + +#![feature(rustc_attrs)] +#![allow(dead_code)] +#![allow(unused_variables)] + +#[fake_attr] //~ ERROR attribute `fake_attr` is currently unknown +#[fake_attr(100)] //~ ERROR attribute `fake_attr` is currently unknown + //~^ ERROR non-string literals in attributes +#[fake_attr(1, 2, 3)] //~ ERROR attribute `fake_attr` is currently unknown + //~^ ERROR non-string literals in attributes +#[fake_attr("hello")] //~ ERROR attribute `fake_attr` is currently unknown + //~^ ERROR string literals in top-level positions, are experimental +#[fake_attr(name = "hello")] //~ ERROR attribute `fake_attr` is currently unknown +#[fake_attr(1, "hi", key = 12, true, false)] //~ ERROR attribute `fake_attr` is currently unknown + //~^ ERROR non-string literals in attributes, or string literals in top-level positions +#[fake_attr(key = "hello", val = 10)] //~ ERROR attribute `fake_attr` is currently unknown + //~^ ERROR non-string literals in attributes +#[fake_attr(key("hello"), val(10))] //~ ERROR attribute `fake_attr` is currently unknown + //~^ ERROR non-string literals in attributes, or string literals in top-level positions +#[fake_attr(enabled = true, disabled = false)] //~ ERROR attribute `fake_attr` is currently unknown + //~^ ERROR non-string literals in attributes +#[fake_attr(true)] //~ ERROR attribute `fake_attr` is currently unknown + //~^ ERROR non-string literals in attributes +#[fake_attr(pi = 3.14159)] //~ ERROR attribute `fake_attr` is currently unknown + //~^ ERROR non-string literals in attributes +#[fake_attr(b"hi")] //~ ERROR attribute `fake_attr` is currently unknown + //~^ ERROR string literals in top-level positions, are experimental +#[fake_doc(r"doc")] //~ ERROR attribute `fake_doc` is currently unknown + //~^ ERROR string literals in top-level positions, are experimental +struct Q { } + +#[rustc_error] +fn main() { } diff --git a/src/test/parse-fail/suffixed-literal-meta.rs b/src/test/parse-fail/suffixed-literal-meta.rs new file mode 100644 index 0000000000000..0e2840c69d364 --- /dev/null +++ b/src/test/parse-fail/suffixed-literal-meta.rs @@ -0,0 +1,25 @@ +// Copyright 2012 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-flags: -Z parse-only + +#[foo = 1usize] //~ ERROR: suffixed literals are not allowed in attributes +#[foo = 1u8] //~ ERROR: suffixed literals are not allowed in attributes +#[foo = 1u16] //~ ERROR: suffixed literals are not allowed in attributes +#[foo = 1u32] //~ ERROR: suffixed literals are not allowed in attributes +#[foo = 1u64] //~ ERROR: suffixed literals are not allowed in attributes +#[foo = 1isize] //~ ERROR: suffixed literals are not allowed in attributes +#[foo = 1i8] //~ ERROR: suffixed literals are not allowed in attributes +#[foo = 1i16] //~ ERROR: suffixed literals are not allowed in attributes +#[foo = 1i32] //~ ERROR: suffixed literals are not allowed in attributes +#[foo = 1i64] //~ ERROR: suffixed literals are not allowed in attributes +#[foo = 1.0f32] //~ ERROR: suffixed literals are not allowed in attributes +#[foo = 1.0f64] //~ ERROR: suffixed literals are not allowed in attributes +fn main() { } diff --git a/src/test/parse-fail/non-str-meta.rs b/src/test/pretty/attr-literals.rs similarity index 66% rename from src/test/parse-fail/non-str-meta.rs rename to src/test/pretty/attr-literals.rs index 3e2e69d2814e5..ba8c580cb0a01 100644 --- a/src/test/parse-fail/non-str-meta.rs +++ b/src/test/pretty/attr-literals.rs @@ -8,10 +8,16 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -// compile-flags: -Z parse-only +// pp-exact +// Tests literals in attributes. -// Issue #623 - non-string meta items are not serialized correctly; -// for now just forbid them +#![feature(custom_attribute, attr_literals)] -#[foo = 1] //~ ERROR: non-string literals are not allowed in meta-items -fn main() { } +fn main() { + #![hello("hi", 1, 2, 1.012, pi = 3.14, bye, name("John"))] + #[align = 8] + fn f() { } + + #[vec(1, 2, 3)] + fn g() { } +} diff --git a/src/test/run-pass-fulldeps/auxiliary/custom_derive_plugin.rs b/src/test/run-pass-fulldeps/auxiliary/custom_derive_plugin.rs index 42135703b75a4..274e430bbea74 100644 --- a/src/test/run-pass-fulldeps/auxiliary/custom_derive_plugin.rs +++ b/src/test/run-pass-fulldeps/auxiliary/custom_derive_plugin.rs @@ -24,7 +24,6 @@ use syntax::ast; use syntax::ext::base::{MultiDecorator, ExtCtxt, Annotatable}; use syntax::ext::build::AstBuilder; use syntax::parse::token; -use syntax::ptr::P; use syntax_ext::deriving::generic::{cs_fold, TraitDef, MethodDef, combine_substructure}; use syntax_ext::deriving::generic::ty::{Literal, LifetimeBounds, Path, borrowed_explicit_self}; use syntax_pos::Span; diff --git a/src/test/run-pass-fulldeps/auxiliary/custom_derive_plugin_attr.rs b/src/test/run-pass-fulldeps/auxiliary/custom_derive_plugin_attr.rs index eeecd0b24e29e..882e90b2d6c15 100644 --- a/src/test/run-pass-fulldeps/auxiliary/custom_derive_plugin_attr.rs +++ b/src/test/run-pass-fulldeps/auxiliary/custom_derive_plugin_attr.rs @@ -26,7 +26,7 @@ use syntax::ext::base::{MultiDecorator, ExtCtxt, Annotatable}; use syntax::ext::build::AstBuilder; use syntax::parse::token; use syntax::ptr::P; -use syntax_ext::deriving::generic::{cs_fold, TraitDef, MethodDef, combine_substructure}; +use syntax_ext::deriving::generic::{TraitDef, MethodDef, combine_substructure}; use syntax_ext::deriving::generic::{Substructure, Struct, EnumMatching}; use syntax_ext::deriving::generic::ty::{Literal, LifetimeBounds, Path, borrowed_explicit_self}; use syntax_pos::Span; diff --git a/src/test/run-pass-fulldeps/auxiliary/macro_crate_test.rs b/src/test/run-pass-fulldeps/auxiliary/macro_crate_test.rs index 3f50811f826e0..e37cd89f29991 100644 --- a/src/test/run-pass-fulldeps/auxiliary/macro_crate_test.rs +++ b/src/test/run-pass-fulldeps/auxiliary/macro_crate_test.rs @@ -17,8 +17,11 @@ extern crate rustc; extern crate rustc_plugin; extern crate syntax_pos; -use syntax::ast::{self, Item, MetaItem, ImplItem, TraitItem, ItemKind}; +use syntax::ast::{self, Item, MetaItem, ItemKind}; +use syntax::codemap::DUMMY_SP; +use syntax::attr::{AttrMetaMethods, AttrNestedMetaItemMethods}; use syntax::ext::base::*; +use syntax::ext::quote::rt::ToTokens; use syntax::parse::{self, token}; use syntax::ptr::P; use syntax::tokenstream::TokenTree; @@ -41,10 +44,13 @@ pub fn plugin_registrar(reg: &mut Registry) { token::intern("duplicate"), // FIXME (#22405): Replace `Box::new` with `box` here when/if possible. MultiDecorator(Box::new(expand_duplicate))); + reg.register_syntax_extension( + token::intern("caller"), + // FIXME (#22405): Replace `Box::new` with `box` here when/if possible. + MultiDecorator(Box::new(expand_caller))); } -fn expand_make_a_1(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) - -> Box { +fn expand_make_a_1(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box { if !tts.is_empty() { cx.span_fatal(sp, "make_a_1 takes no arguments"); } @@ -52,19 +58,18 @@ fn expand_make_a_1(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) } // See Issue #15750 -fn expand_identity(cx: &mut ExtCtxt, _span: Span, tts: &[TokenTree]) - -> Box { +fn expand_identity(cx: &mut ExtCtxt, _span: Span, tts: &[TokenTree]) -> Box { // Parse an expression and emit it unchanged. - let mut parser = parse::new_parser_from_tts(cx.parse_sess(), - cx.cfg(), tts.to_vec()); + let mut parser = parse::new_parser_from_tts(cx.parse_sess(), cx.cfg(), tts.to_vec()); let expr = parser.parse_expr().unwrap(); MacEager::expr(quote_expr!(&mut *cx, $expr)) } fn expand_into_foo_multi(cx: &mut ExtCtxt, - sp: Span, - attr: &MetaItem, - it: Annotatable) -> Vec { + _sp: Span, + _attr: &MetaItem, + it: Annotatable) + -> Vec { match it { Annotatable::Item(it) => vec![ Annotatable::Item(P(Item { @@ -74,7 +79,7 @@ fn expand_into_foo_multi(cx: &mut ExtCtxt, Annotatable::Item(quote_item!(cx, enum Foo3 { Bar }).unwrap()), Annotatable::Item(quote_item!(cx, #[cfg(any())] fn foo2() {}).unwrap()), ], - Annotatable::ImplItem(it) => vec![ + Annotatable::ImplItem(_it) => vec![ quote_item!(cx, impl X { fn foo(&self) -> i32 { 42 } }).unwrap().and_then(|i| { match i.node { ItemKind::Impl(_, _, _, _, _, mut items) => { @@ -84,7 +89,7 @@ fn expand_into_foo_multi(cx: &mut ExtCtxt, } }) ], - Annotatable::TraitItem(it) => vec![ + Annotatable::TraitItem(_it) => vec![ quote_item!(cx, trait X { fn foo(&self) -> i32 { 0 } }).unwrap().and_then(|i| { match i.node { ItemKind::Trait(_, _, _, mut items) => { @@ -99,15 +104,14 @@ fn expand_into_foo_multi(cx: &mut ExtCtxt, // Create a duplicate of the annotatable, based on the MetaItem fn expand_duplicate(cx: &mut ExtCtxt, - sp: Span, + _sp: Span, mi: &MetaItem, it: &Annotatable, - push: &mut FnMut(Annotatable)) -{ + push: &mut FnMut(Annotatable)) { let copy_name = match mi.node { ast::MetaItemKind::List(_, ref xs) => { - if let ast::MetaItemKind::Word(ref w) = xs[0].node { - token::str_to_ident(&w) + if let Some(word) = xs[0].word() { + token::str_to_ident(&word.name()) } else { cx.span_err(mi.span, "Expected word"); return; @@ -142,4 +146,69 @@ fn expand_duplicate(cx: &mut ExtCtxt, } } +pub fn token_separate(ecx: &ExtCtxt, things: &[T], + token: token::Token) -> Vec { + let mut output: Vec = vec![]; + for (i, thing) in things.iter().enumerate() { + output.extend(thing.to_tokens(ecx)); + if i < things.len() - 1 { + output.push(TokenTree::Token(DUMMY_SP, token.clone())); + } + } + + output +} + +fn expand_caller(cx: &mut ExtCtxt, + sp: Span, + mi: &MetaItem, + it: &Annotatable, + push: &mut FnMut(Annotatable)) { + let (orig_fn_name, ret_type) = match *it { + Annotatable::Item(ref item) => match item.node { + ItemKind::Fn(ref decl, _, _, _, _, _) => { + (item.ident, &decl.output) + } + _ => cx.span_fatal(item.span, "Only functions with return types can be annotated.") + }, + _ => cx.span_fatal(sp, "Only functions can be annotated.") + }; + + let (caller_name, arguments) = if let Some(list) = mi.meta_item_list() { + if list.len() < 2 { + cx.span_fatal(mi.span(), "Need a function name and at least one parameter."); + } + + let fn_name = match list[0].name() { + Some(name) => token::str_to_ident(&name), + None => cx.span_fatal(list[0].span(), "First parameter must be an ident.") + }; + + (fn_name, &list[1..]) + } else { + cx.span_fatal(mi.span, "Expected list."); + }; + + let literals: Vec = arguments.iter().map(|arg| { + if let Some(lit) = arg.literal() { + lit.clone() + } else { + cx.span_fatal(arg.span(), "Expected literal."); + } + }).collect(); + + let arguments = token_separate(cx, literals.as_slice(), token::Comma); + if let ast::FunctionRetTy::Ty(ref rt) = *ret_type { + push(Annotatable::Item(quote_item!(cx, + fn $caller_name() -> $rt { + $orig_fn_name($arguments) + }).unwrap())) + } else { + push(Annotatable::Item(quote_item!(cx, + fn $caller_name() { + $orig_fn_name($arguments) + }).unwrap())) + } +} + pub fn foo() {} diff --git a/src/test/run-pass-fulldeps/auxiliary/plugin_args.rs b/src/test/run-pass-fulldeps/auxiliary/plugin_args.rs index f0edc0f2b120f..f21c914a76c9c 100644 --- a/src/test/run-pass-fulldeps/auxiliary/plugin_args.rs +++ b/src/test/run-pass-fulldeps/auxiliary/plugin_args.rs @@ -30,7 +30,7 @@ use syntax::tokenstream; use rustc_plugin::Registry; struct Expander { - args: Vec>, + args: Vec, } impl TTMacroExpander for Expander { @@ -38,7 +38,7 @@ impl TTMacroExpander for Expander { ecx: &'cx mut ExtCtxt, sp: Span, _: &[tokenstream::TokenTree]) -> Box { - let args = self.args.iter().map(|i| pprust::meta_item_to_string(&*i)) + let args = self.args.iter().map(|i| pprust::meta_list_item_to_string(i)) .collect::>().join(", "); let interned = token::intern_and_get_ident(&args[..]); MacEager::expr(ecx.expr_str(sp, interned)) diff --git a/src/test/run-pass-fulldeps/macro-crate-multi-decorator-literals.rs b/src/test/run-pass-fulldeps/macro-crate-multi-decorator-literals.rs new file mode 100644 index 0000000000000..6dc651bb653a3 --- /dev/null +++ b/src/test/run-pass-fulldeps/macro-crate-multi-decorator-literals.rs @@ -0,0 +1,58 @@ +// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:macro_crate_test.rs +// ignore-stage1 + +#![feature(plugin, custom_attribute, attr_literals)] +#![plugin(macro_crate_test)] + +#[macro_use] +#[no_link] +extern crate macro_crate_test; + +// The `caller(name, args...)` attribute emits a new nullary function named +// `name` that calls the annotated function with `args`. As an example, consider +// the following: +// +// #[caller(simple, 1, "hello", 3.14)] +// fn f(num: isize, string: &'static str, float: f32) -> (isize, &'static str, float) { +// (num, string, float) +// } +// +// This results in a function named `simple` that calls `f(1, "hello", 3.14)`. +// As a result, the expression `simple()` evaluates to `(1, "helllo", 3.14)`. + +#[caller(simple, 1, "hello", 3.14)] +#[caller(simple1, 2, "bye", 6.28)] +#[caller(simple2, 3, "hi", 1.01)] +fn f(num: isize, string: &'static str, float: f32) -> (isize, &'static str, f32) { + (num, string, float) +} + +#[caller(complex, true, 10)] +#[caller(complex1, false, 15)] +#[caller(complex2, true, 20)] +fn g(emit: bool, num: i32) -> Option { + match emit { + true => Some(num), + false => None + } +} + +fn main() { + assert_eq!(simple(), (1, "hello", 3.14)); + assert_eq!(simple1(), (2, "bye", 6.28)); + assert_eq!(simple2(), (3, "hi", 1.01)); + + assert_eq!(complex(), Some(10)); + assert_eq!(complex1(), None); + assert_eq!(complex2(), Some(20)); +}