]> git.lizzy.rs Git - rust.git/blob - src/libsyntax/attr.rs
Add AttrId to Attribute_
[rust.git] / src / libsyntax / attr.rs
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.
4 //
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.
10
11 // Functions dealing with attributes and meta items
12
13 use ast;
14 use ast::{AttrId, Attribute, Attribute_, MetaItem, MetaWord, MetaNameValue, MetaList};
15 use codemap::{Span, Spanned, spanned, dummy_spanned};
16 use codemap::BytePos;
17 use diagnostic::SpanHandler;
18 use parse::comments::{doc_comment_style, strip_doc_comment_decoration};
19 use parse::token::InternedString;
20 use parse::token;
21 use crateid::CrateId;
22
23 use collections::HashSet;
24
25 local_data_key!(used_attrs: HashSet<AttrId>)
26
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));
31 }
32
33 pub fn is_used(attr: &Attribute) -> bool {
34     used_attrs.get().map_or(false, |used| used.contains(&attr.node.id))
35 }
36
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.
41
42     /// Retrieve the name of the meta item, e.g. foo in #[foo],
43     /// #[foo="bar"] and #[foo(bar)]
44     fn name(&self) -> InternedString;
45
46     /**
47      * Gets the string value if self is a MetaNameValue variant
48      * containing a string, otherwise None.
49      */
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]>;
53
54     /**
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`
57      */
58     fn name_str_pair(&self) -> Option<(InternedString,InternedString)>;
59 }
60
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()
65     }
66     fn meta_item_list<'a>(&'a self) -> Option<&'a [@MetaItem]> {
67         self.node.value.meta_item_list()
68     }
69     fn name_str_pair(&self) -> Option<(InternedString,InternedString)> {
70         self.meta().name_str_pair()
71     }
72 }
73
74 impl AttrMetaMethods for MetaItem {
75     fn name(&self) -> InternedString {
76         match self.node {
77             MetaWord(ref n) => (*n).clone(),
78             MetaNameValue(ref n, _) => (*n).clone(),
79             MetaList(ref n, _) => (*n).clone(),
80         }
81     }
82
83     fn value_str(&self) -> Option<InternedString> {
84         match self.node {
85             MetaNameValue(_, ref v) => {
86                 match v.node {
87                     ast::LitStr(ref s, _) => Some((*s).clone()),
88                     _ => None,
89                 }
90             },
91             _ => None
92         }
93     }
94
95     fn meta_item_list<'a>(&'a self) -> Option<&'a [@MetaItem]> {
96         match self.node {
97             MetaList(_, ref l) => Some(l.as_slice()),
98             _ => None
99         }
100     }
101
102     fn name_str_pair(&self) -> Option<(InternedString,InternedString)> {
103         self.value_str().map(|s| (self.name(), s))
104     }
105 }
106
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()
113     }
114     fn name_str_pair(&self) -> Option<(InternedString,InternedString)> {
115         (**self).name_str_pair()
116     }
117 }
118
119
120 pub trait AttributeMethods {
121     fn meta(&self) -> @MetaItem;
122     fn desugar_doc(&self) -> Attribute;
123 }
124
125 impl AttributeMethods for Attribute {
126     /// Extract the MetaItem from inside this Attribute.
127     fn meta(&self) -> @MetaItem {
128         self.node.value
129     }
130
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)
143             } else {
144                 mk_attr_inner(self.node.id, meta)
145             }
146         } else {
147             *self
148         }
149     }
150 }
151
152 /* Constructors */
153
154 pub fn mk_name_value_item_str(name: InternedString, value: InternedString)
155                               -> @MetaItem {
156     let value_lit = dummy_spanned(ast::LitStr(value, ast::CookedStr));
157     mk_name_value_item(name, value_lit)
158 }
159
160 pub fn mk_name_value_item(name: InternedString, value: ast::Lit)
161                           -> @MetaItem {
162     @dummy_spanned(MetaNameValue(name, value))
163 }
164
165 pub fn mk_list_item(name: InternedString, items: Vec<@MetaItem> ) -> @MetaItem {
166     @dummy_spanned(MetaList(name, items))
167 }
168
169 pub fn mk_word_item(name: InternedString) -> @MetaItem {
170     @dummy_spanned(MetaWord(name))
171 }
172
173 local_data_key!(next_attr_id: uint)
174
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));
178     AttrId(id)
179 }
180
181 /// Returns an inner attribute with the given value.
182 pub fn mk_attr_inner(id: AttrId, item: @MetaItem) -> Attribute {
183     dummy_spanned(Attribute_ {
184         id: id,
185         style: ast::AttrInner,
186         value: item,
187         is_sugared_doc: false,
188     })
189 }
190
191 /// Returns an outer attribute with the given value.
192 pub fn mk_attr_outer(id: AttrId, item: @MetaItem) -> Attribute {
193     dummy_spanned(Attribute_ {
194         id: id,
195         style: ast::AttrOuter,
196         value: item,
197         is_sugared_doc: false,
198     })
199 }
200
201 pub fn mk_sugared_doc_attr(id: AttrId, text: InternedString, lo: BytePos,
202                            hi: BytePos)
203                            -> Attribute {
204     let style = doc_comment_style(text.get());
205     let lit = spanned(lo, hi, ast::LitStr(text, ast::CookedStr));
206     let attr = Attribute_ {
207         id: id,
208         style: style,
209         value: @spanned(lo, hi, MetaNameValue(InternedString::new("doc"),
210                                               lit)),
211         is_sugared_doc: true
212     };
213     spanned(lo, hi, attr)
214 }
215
216 /* Searching */
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
226     })
227 }
228
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)
234     })
235 }
236
237 pub fn first_attr_value_str_by_name(attrs: &[Attribute], name: &str)
238                                  -> Option<InternedString> {
239     attrs.iter()
240         .find(|at| at.name().equiv(&name))
241         .and_then(|at| at.value_str())
242 }
243
244 pub fn last_meta_item_value_str_by_name(items: &[@MetaItem], name: &str)
245                                      -> Option<InternedString> {
246     items.iter()
247          .rev()
248          .find(|mi| mi.name().equiv(&name))
249          .and_then(|i| i.value_str())
250 }
251
252 /* Higher-level applications */
253
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)> >();
260
261     v.sort_by(|&(ref a, _), &(ref b, _)| a.cmp(b));
262
263     // There doesn't seem to be a more optimal way to do this
264     v.move_iter().map(|(_, m)| {
265         match m.node {
266             MetaList(ref n, ref mis) => {
267                 @Spanned {
268                     node: MetaList((*n).clone(),
269                                    sort_meta_items(mis.as_slice())),
270                     .. /*bad*/ (*m).clone()
271                 }
272             }
273             _ => m
274         }
275     }).collect()
276 }
277
278 /**
279  * From a list of crate attributes get only the meta_items that affect crate
280  * linkage
281  */
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()),
287             _ => ()
288         }
289     }
290     result
291 }
292
293 pub fn find_crateid(attrs: &[Attribute]) -> Option<CrateId> {
294     match first_attr_value_str_by_name(attrs, "crate_id") {
295         None => None,
296         Some(id) => from_str::<CrateId>(id.get()),
297     }
298 }
299
300 #[deriving(Eq)]
301 pub enum InlineAttr {
302     InlineNone,
303     InlineHint,
304     InlineAlways,
305     InlineNever,
306 }
307
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") {
316                 InlineAlways
317             } else if contains_name(items.as_slice(), "never") {
318                 InlineNever
319             } else {
320                 InlineHint
321             }
322           }
323           _ => ia
324         }
325     })
326 }
327
328 /// Tests if any `cfg(...)` meta items in `metas` match `cfg`. e.g.
329 ///
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;
338
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
344             debug!("is cfg");
345             no_cfgs = false;
346              // only #[cfg(...)] ones are understood.
347             match mi.meta_item_list() {
348                 Some(cfg_meta) => {
349                     debug!("is cfg(...)");
350                     cfg_meta.iter().all(|cfg_mi| {
351                         debug!("cfg({}[...])", cfg_mi.name());
352                         match cfg_mi.node {
353                             ast::MetaList(ref s, ref not_cfgs)
354                             if s.equiv(&("not")) => {
355                                 debug!("not!");
356                                 // inside #[cfg(not(...))], so these need to all
357                                 // not match.
358                                 !not_cfgs.iter().all(|mi| {
359                                     debug!("cfg(not({}[...]))", mi.name());
360                                     contains(cfg, *mi)
361                                 })
362                             }
363                             _ => contains(cfg, *cfg_mi)
364                         }
365                     })
366                 }
367                 None => false
368             }
369         } else {
370             false
371         }
372     });
373     debug!("test_cfg (no_cfgs={}, some_cfg_matches={})", no_cfgs, some_cfg_matches);
374     no_cfgs || some_cfg_matches
375 }
376
377 /// Represents the #[deprecated="foo"] and friends attributes.
378 pub struct Stability {
379     pub level: StabilityLevel,
380     pub text: Option<InternedString>
381 }
382
383 /// The available stability levels.
384 #[deriving(Eq,Ord,Clone,Show)]
385 pub enum StabilityLevel {
386     Deprecated,
387     Experimental,
388     Unstable,
389     Stable,
390     Frozen,
391     Locked
392 }
393
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> {
397     for m in metas {
398         let level = match m.name().get() {
399             "deprecated" => Deprecated,
400             "experimental" => Experimental,
401             "unstable" => Unstable,
402             "stable" => Stable,
403             "frozen" => Frozen,
404             "locked" => Locked,
405             _ => continue // not a stability level
406         };
407
408         return Some(Stability {
409                 level: level,
410                 text: m.value_str()
411             });
412     }
413     None
414 }
415
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();
420
421         if !set.insert(name.clone()) {
422             diagnostic.span_fatal(meta.span,
423                                   format!("duplicate meta item `{}`",
424                                           name).as_slice());
425         }
426     }
427 }
428
429
430 /**
431  * Fold this over attributes to parse #[repr(...)] forms.
432  *
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.
437  *
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.
441  */
442 pub fn find_repr_attr(diagnostic: &SpanHandler, attr: @ast::MetaItem, acc: ReprAttr)
443     -> ReprAttr {
444     let mut acc = acc;
445     match attr.node {
446         ast::MetaList(ref s, ref items) if s.equiv(&("repr")) => {
447             for item in items.iter() {
448                 match item.node {
449                     ast::MetaWord(ref word) => {
450                         let hint = match word.get() {
451                             // Can't use "extern" because it's not a lexical identifier.
452                             "C" => ReprExtern,
453                             _ => match int_type_of_word(word.get()) {
454                                 Some(ity) => ReprInt(item.span, ity),
455                                 None => {
456                                     // Not a word we recognize
457                                     diagnostic.span_err(item.span,
458                                                         "unrecognized representation hint");
459                                     ReprAny
460                                 }
461                             }
462                         };
463                         if hint != ReprAny {
464                             if acc == ReprAny {
465                                 acc = hint;
466                             } else if acc != hint {
467                                 diagnostic.span_warn(item.span,
468                                                      "conflicting representation hint ignored")
469                             }
470                         }
471                     }
472                     // Not a word:
473                     _ => diagnostic.span_err(item.span, "unrecognized representation hint")
474                 }
475             }
476         }
477         // Not a "repr" hint: ignore.
478         _ => { }
479     }
480     acc
481 }
482
483 fn int_type_of_word(s: &str) -> Option<IntType> {
484     match s {
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)),
495         _ => None
496     }
497 }
498
499 #[deriving(Eq, Show)]
500 pub enum ReprAttr {
501     ReprAny,
502     ReprInt(Span, IntType),
503     ReprExtern
504 }
505
506 impl ReprAttr {
507     pub fn is_ffi_safe(&self) -> bool {
508         match *self {
509             ReprAny => false,
510             ReprInt(_sp, ity) => ity.is_ffi_safe(),
511             ReprExtern => true
512         }
513     }
514 }
515
516 #[deriving(Eq, Show)]
517 pub enum IntType {
518     SignedInt(ast::IntTy),
519     UnsignedInt(ast::UintTy)
520 }
521
522 impl IntType {
523     #[inline]
524     pub fn is_signed(self) -> bool {
525         match self {
526             SignedInt(..) => true,
527             UnsignedInt(..) => false
528         }
529     }
530     fn is_ffi_safe(self) -> bool {
531         match self {
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,
536             _ => false
537         }
538     }
539 }