1 // Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 // Functions dealing with attributes and meta items
14 use ast::{AttrId, Attribute, Attribute_, MetaItem, MetaWord, MetaNameValue, MetaList};
15 use codemap::{Span, Spanned, spanned, dummy_spanned};
17 use diagnostic::SpanHandler;
18 use parse::comments::{doc_comment_style, strip_doc_comment_decoration};
19 use parse::token::InternedString;
23 use collections::HashSet;
25 local_data_key!(used_attrs: HashSet<AttrId>)
27 pub fn mark_used(attr: &Attribute) {
28 let mut used = used_attrs.replace(None).unwrap_or_else(|| HashSet::new());
29 used.insert(attr.node.id);
30 used_attrs.replace(Some(used));
33 pub fn is_used(attr: &Attribute) -> bool {
34 used_attrs.get().map_or(false, |used| used.contains(&attr.node.id))
37 pub trait AttrMetaMethods {
38 // This could be changed to `fn check_name(&self, name: InternedString) ->
39 // bool` which would facilitate a side table recording which
40 // attributes/meta items are used/unused.
42 /// Retrieve the name of the meta item, e.g. foo in #[foo],
43 /// #[foo="bar"] and #[foo(bar)]
44 fn name(&self) -> InternedString;
47 * Gets the string value if self is a MetaNameValue variant
48 * containing a string, otherwise None.
50 fn value_str(&self) -> Option<InternedString>;
51 /// Gets a list of inner meta items from a list MetaItem type.
52 fn meta_item_list<'a>(&'a self) -> Option<&'a [@MetaItem]>;
55 * If the meta item is a name-value type with a string value then returns
56 * a tuple containing the name and string value, otherwise `None`
58 fn name_str_pair(&self) -> Option<(InternedString,InternedString)>;
61 impl AttrMetaMethods for Attribute {
62 fn name(&self) -> InternedString { self.meta().name() }
63 fn value_str(&self) -> Option<InternedString> {
64 self.meta().value_str()
66 fn meta_item_list<'a>(&'a self) -> Option<&'a [@MetaItem]> {
67 self.node.value.meta_item_list()
69 fn name_str_pair(&self) -> Option<(InternedString,InternedString)> {
70 self.meta().name_str_pair()
74 impl AttrMetaMethods for MetaItem {
75 fn name(&self) -> InternedString {
77 MetaWord(ref n) => (*n).clone(),
78 MetaNameValue(ref n, _) => (*n).clone(),
79 MetaList(ref n, _) => (*n).clone(),
83 fn value_str(&self) -> Option<InternedString> {
85 MetaNameValue(_, ref v) => {
87 ast::LitStr(ref s, _) => Some((*s).clone()),
95 fn meta_item_list<'a>(&'a self) -> Option<&'a [@MetaItem]> {
97 MetaList(_, ref l) => Some(l.as_slice()),
102 fn name_str_pair(&self) -> Option<(InternedString,InternedString)> {
103 self.value_str().map(|s| (self.name(), s))
107 // Annoying, but required to get test_cfg to work
108 impl AttrMetaMethods for @MetaItem {
109 fn name(&self) -> InternedString { (**self).name() }
110 fn value_str(&self) -> Option<InternedString> { (**self).value_str() }
111 fn meta_item_list<'a>(&'a self) -> Option<&'a [@MetaItem]> {
112 (**self).meta_item_list()
114 fn name_str_pair(&self) -> Option<(InternedString,InternedString)> {
115 (**self).name_str_pair()
120 pub trait AttributeMethods {
121 fn meta(&self) -> @MetaItem;
122 fn desugar_doc(&self) -> Attribute;
125 impl AttributeMethods for Attribute {
126 /// Extract the MetaItem from inside this Attribute.
127 fn meta(&self) -> @MetaItem {
131 /// Convert self to a normal #[doc="foo"] comment, if it is a
132 /// comment like `///` or `/** */`. (Returns self unchanged for
133 /// non-sugared doc attributes.)
134 fn desugar_doc(&self) -> Attribute {
135 if self.node.is_sugared_doc {
136 let comment = self.value_str().unwrap();
137 let meta = mk_name_value_item_str(
138 InternedString::new("doc"),
139 token::intern_and_get_ident(strip_doc_comment_decoration(
140 comment.get()).as_slice()));
141 if self.node.style == ast::AttrOuter {
142 mk_attr_outer(self.node.id, meta)
144 mk_attr_inner(self.node.id, meta)
154 pub fn mk_name_value_item_str(name: InternedString, value: InternedString)
156 let value_lit = dummy_spanned(ast::LitStr(value, ast::CookedStr));
157 mk_name_value_item(name, value_lit)
160 pub fn mk_name_value_item(name: InternedString, value: ast::Lit)
162 @dummy_spanned(MetaNameValue(name, value))
165 pub fn mk_list_item(name: InternedString, items: Vec<@MetaItem> ) -> @MetaItem {
166 @dummy_spanned(MetaList(name, items))
169 pub fn mk_word_item(name: InternedString) -> @MetaItem {
170 @dummy_spanned(MetaWord(name))
173 local_data_key!(next_attr_id: uint)
175 pub fn mk_attr_id() -> AttrId {
176 let id = next_attr_id.replace(None).unwrap_or(0);
177 next_attr_id.replace(Some(id + 1));
181 /// Returns an inner attribute with the given value.
182 pub fn mk_attr_inner(id: AttrId, item: @MetaItem) -> Attribute {
183 dummy_spanned(Attribute_ {
185 style: ast::AttrInner,
187 is_sugared_doc: false,
191 /// Returns an outer attribute with the given value.
192 pub fn mk_attr_outer(id: AttrId, item: @MetaItem) -> Attribute {
193 dummy_spanned(Attribute_ {
195 style: ast::AttrOuter,
197 is_sugared_doc: false,
201 pub fn mk_sugared_doc_attr(id: AttrId, text: InternedString, lo: BytePos,
204 let style = doc_comment_style(text.get());
205 let lit = spanned(lo, hi, ast::LitStr(text, ast::CookedStr));
206 let attr = Attribute_ {
209 value: @spanned(lo, hi, MetaNameValue(InternedString::new("doc"),
213 spanned(lo, hi, attr)
217 /// Check if `needle` occurs in `haystack` by a structural
218 /// comparison. This is slightly subtle, and relies on ignoring the
219 /// span included in the `==` comparison a plain MetaItem.
220 pub fn contains(haystack: &[@ast::MetaItem],
221 needle: @ast::MetaItem) -> bool {
222 debug!("attr::contains (name={})", needle.name());
223 haystack.iter().any(|item| {
224 debug!(" testing: {}", item.name());
225 item.node == needle.node
229 pub fn contains_name<AM: AttrMetaMethods>(metas: &[AM], name: &str) -> bool {
230 debug!("attr::contains_name (name={})", name);
231 metas.iter().any(|item| {
232 debug!(" testing: {}", item.name());
233 item.name().equiv(&name)
237 pub fn first_attr_value_str_by_name(attrs: &[Attribute], name: &str)
238 -> Option<InternedString> {
240 .find(|at| at.name().equiv(&name))
241 .and_then(|at| at.value_str())
244 pub fn last_meta_item_value_str_by_name(items: &[@MetaItem], name: &str)
245 -> Option<InternedString> {
248 .find(|mi| mi.name().equiv(&name))
249 .and_then(|i| i.value_str())
252 /* Higher-level applications */
254 pub fn sort_meta_items(items: &[@MetaItem]) -> Vec<@MetaItem> {
255 // This is sort of stupid here, but we need to sort by
256 // human-readable strings.
257 let mut v = items.iter()
258 .map(|&mi| (mi.name(), mi))
259 .collect::<Vec<(InternedString, @MetaItem)> >();
261 v.sort_by(|&(ref a, _), &(ref b, _)| a.cmp(b));
263 // There doesn't seem to be a more optimal way to do this
264 v.move_iter().map(|(_, m)| {
266 MetaList(ref n, ref mis) => {
268 node: MetaList((*n).clone(),
269 sort_meta_items(mis.as_slice())),
270 .. /*bad*/ (*m).clone()
279 * From a list of crate attributes get only the meta_items that affect crate
282 pub fn find_linkage_metas(attrs: &[Attribute]) -> Vec<@MetaItem> {
283 let mut result = Vec::new();
284 for attr in attrs.iter().filter(|at| at.name().equiv(&("link"))) {
285 match attr.meta().node {
286 MetaList(_, ref items) => result.push_all(items.as_slice()),
293 pub fn find_crateid(attrs: &[Attribute]) -> Option<CrateId> {
294 match first_attr_value_str_by_name(attrs, "crate_id") {
296 Some(id) => from_str::<CrateId>(id.get()),
301 pub enum InlineAttr {
308 /// True if something like #[inline] is found in the list of attrs.
309 pub fn find_inline_attr(attrs: &[Attribute]) -> InlineAttr {
310 // FIXME (#2809)---validate the usage of #[inline] and #[inline]
311 attrs.iter().fold(InlineNone, |ia,attr| {
312 match attr.node.value.node {
313 MetaWord(ref n) if n.equiv(&("inline")) => InlineHint,
314 MetaList(ref n, ref items) if n.equiv(&("inline")) => {
315 if contains_name(items.as_slice(), "always") {
317 } else if contains_name(items.as_slice(), "never") {
328 /// Tests if any `cfg(...)` meta items in `metas` match `cfg`. e.g.
330 /// test_cfg(`[foo="a", bar]`, `[cfg(foo), cfg(bar)]`) == true
331 /// test_cfg(`[foo="a", bar]`, `[cfg(not(bar))]`) == false
332 /// test_cfg(`[foo="a", bar]`, `[cfg(bar, foo="a")]`) == true
333 /// test_cfg(`[foo="a", bar]`, `[cfg(bar, foo="b")]`) == false
334 pub fn test_cfg<AM: AttrMetaMethods, It: Iterator<AM>>
335 (cfg: &[@MetaItem], mut metas: It) -> bool {
336 // having no #[cfg(...)] attributes counts as matching.
337 let mut no_cfgs = true;
339 // this would be much nicer as a chain of iterator adaptors, but
340 // this doesn't work.
341 let some_cfg_matches = metas.any(|mi| {
342 debug!("testing name: {}", mi.name());
343 if mi.name().equiv(&("cfg")) { // it is a #[cfg()] attribute
346 // only #[cfg(...)] ones are understood.
347 match mi.meta_item_list() {
349 debug!("is cfg(...)");
350 cfg_meta.iter().all(|cfg_mi| {
351 debug!("cfg({}[...])", cfg_mi.name());
353 ast::MetaList(ref s, ref not_cfgs)
354 if s.equiv(&("not")) => {
356 // inside #[cfg(not(...))], so these need to all
358 !not_cfgs.iter().all(|mi| {
359 debug!("cfg(not({}[...]))", mi.name());
363 _ => contains(cfg, *cfg_mi)
373 debug!("test_cfg (no_cfgs={}, some_cfg_matches={})", no_cfgs, some_cfg_matches);
374 no_cfgs || some_cfg_matches
377 /// Represents the #[deprecated="foo"] and friends attributes.
378 pub struct Stability {
379 pub level: StabilityLevel,
380 pub text: Option<InternedString>
383 /// The available stability levels.
384 #[deriving(Eq,Ord,Clone,Show)]
385 pub enum StabilityLevel {
394 /// Find the first stability attribute. `None` if none exists.
395 pub fn find_stability<AM: AttrMetaMethods, It: Iterator<AM>>(mut metas: It)
396 -> Option<Stability> {
398 let level = match m.name().get() {
399 "deprecated" => Deprecated,
400 "experimental" => Experimental,
401 "unstable" => Unstable,
405 _ => continue // not a stability level
408 return Some(Stability {
416 pub fn require_unique_names(diagnostic: &SpanHandler, metas: &[@MetaItem]) {
417 let mut set = HashSet::new();
418 for meta in metas.iter() {
419 let name = meta.name();
421 if !set.insert(name.clone()) {
422 diagnostic.span_fatal(meta.span,
423 format!("duplicate meta item `{}`",
431 * Fold this over attributes to parse #[repr(...)] forms.
433 * Valid repr contents: any of the primitive integral type names (see
434 * `int_type_of_word`, below) to specify the discriminant type; and `C`, to use
435 * the same discriminant size that the corresponding C enum would. These are
436 * not allowed on univariant or zero-variant enums, which have no discriminant.
438 * If a discriminant type is so specified, then the discriminant will be
439 * present (before fields, if any) with that type; reprensentation
440 * optimizations which would remove it will not be done.
442 pub fn find_repr_attr(diagnostic: &SpanHandler, attr: @ast::MetaItem, acc: ReprAttr)
446 ast::MetaList(ref s, ref items) if s.equiv(&("repr")) => {
447 for item in items.iter() {
449 ast::MetaWord(ref word) => {
450 let hint = match word.get() {
451 // Can't use "extern" because it's not a lexical identifier.
453 _ => match int_type_of_word(word.get()) {
454 Some(ity) => ReprInt(item.span, ity),
456 // Not a word we recognize
457 diagnostic.span_err(item.span,
458 "unrecognized representation hint");
466 } else if acc != hint {
467 diagnostic.span_warn(item.span,
468 "conflicting representation hint ignored")
473 _ => diagnostic.span_err(item.span, "unrecognized representation hint")
477 // Not a "repr" hint: ignore.
483 fn int_type_of_word(s: &str) -> Option<IntType> {
485 "i8" => Some(SignedInt(ast::TyI8)),
486 "u8" => Some(UnsignedInt(ast::TyU8)),
487 "i16" => Some(SignedInt(ast::TyI16)),
488 "u16" => Some(UnsignedInt(ast::TyU16)),
489 "i32" => Some(SignedInt(ast::TyI32)),
490 "u32" => Some(UnsignedInt(ast::TyU32)),
491 "i64" => Some(SignedInt(ast::TyI64)),
492 "u64" => Some(UnsignedInt(ast::TyU64)),
493 "int" => Some(SignedInt(ast::TyI)),
494 "uint" => Some(UnsignedInt(ast::TyU)),
499 #[deriving(Eq, Show)]
502 ReprInt(Span, IntType),
507 pub fn is_ffi_safe(&self) -> bool {
510 ReprInt(_sp, ity) => ity.is_ffi_safe(),
516 #[deriving(Eq, Show)]
518 SignedInt(ast::IntTy),
519 UnsignedInt(ast::UintTy)
524 pub fn is_signed(self) -> bool {
526 SignedInt(..) => true,
527 UnsignedInt(..) => false
530 fn is_ffi_safe(self) -> bool {
532 SignedInt(ast::TyI8) | UnsignedInt(ast::TyU8) |
533 SignedInt(ast::TyI16) | UnsignedInt(ast::TyU16) |
534 SignedInt(ast::TyI32) | UnsignedInt(ast::TyU32) |
535 SignedInt(ast::TyI64) | UnsignedInt(ast::TyU64) => true,