]> git.lizzy.rs Git - rust.git/blob - src/libsyntax/attr.rs
bbd333163b593edfcd48948d25b380e188f3f227
[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     fn check_name(&self, name: &str) -> bool {
39         name == self.name().get()
40     }
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 check_name(&self, name: &str) -> bool {
63         if name == self.name().get() {
64             mark_used(self);
65             true
66         } else {
67             false
68         }
69     }
70     fn name(&self) -> InternedString { self.meta().name() }
71     fn value_str(&self) -> Option<InternedString> {
72         self.meta().value_str()
73     }
74     fn meta_item_list<'a>(&'a self) -> Option<&'a [@MetaItem]> {
75         self.node.value.meta_item_list()
76     }
77     fn name_str_pair(&self) -> Option<(InternedString,InternedString)> {
78         self.meta().name_str_pair()
79     }
80 }
81
82 impl AttrMetaMethods for MetaItem {
83     fn name(&self) -> InternedString {
84         match self.node {
85             MetaWord(ref n) => (*n).clone(),
86             MetaNameValue(ref n, _) => (*n).clone(),
87             MetaList(ref n, _) => (*n).clone(),
88         }
89     }
90
91     fn value_str(&self) -> Option<InternedString> {
92         match self.node {
93             MetaNameValue(_, ref v) => {
94                 match v.node {
95                     ast::LitStr(ref s, _) => Some((*s).clone()),
96                     _ => None,
97                 }
98             },
99             _ => None
100         }
101     }
102
103     fn meta_item_list<'a>(&'a self) -> Option<&'a [@MetaItem]> {
104         match self.node {
105             MetaList(_, ref l) => Some(l.as_slice()),
106             _ => None
107         }
108     }
109
110     fn name_str_pair(&self) -> Option<(InternedString,InternedString)> {
111         self.value_str().map(|s| (self.name(), s))
112     }
113 }
114
115 // Annoying, but required to get test_cfg to work
116 impl AttrMetaMethods for @MetaItem {
117     fn name(&self) -> InternedString { (**self).name() }
118     fn value_str(&self) -> Option<InternedString> { (**self).value_str() }
119     fn meta_item_list<'a>(&'a self) -> Option<&'a [@MetaItem]> {
120         (**self).meta_item_list()
121     }
122     fn name_str_pair(&self) -> Option<(InternedString,InternedString)> {
123         (**self).name_str_pair()
124     }
125 }
126
127
128 pub trait AttributeMethods {
129     fn meta(&self) -> @MetaItem;
130     fn desugar_doc(&self) -> Attribute;
131 }
132
133 impl AttributeMethods for Attribute {
134     /// Extract the MetaItem from inside this Attribute.
135     fn meta(&self) -> @MetaItem {
136         self.node.value
137     }
138
139     /// Convert self to a normal #[doc="foo"] comment, if it is a
140     /// comment like `///` or `/** */`. (Returns self unchanged for
141     /// non-sugared doc attributes.)
142     fn desugar_doc(&self) -> Attribute {
143         if self.node.is_sugared_doc {
144             let comment = self.value_str().unwrap();
145             let meta = mk_name_value_item_str(
146                 InternedString::new("doc"),
147                 token::intern_and_get_ident(strip_doc_comment_decoration(
148                         comment.get()).as_slice()));
149             if self.node.style == ast::AttrOuter {
150                 mk_attr_outer(self.node.id, meta)
151             } else {
152                 mk_attr_inner(self.node.id, meta)
153             }
154         } else {
155             *self
156         }
157     }
158 }
159
160 /* Constructors */
161
162 pub fn mk_name_value_item_str(name: InternedString, value: InternedString)
163                               -> @MetaItem {
164     let value_lit = dummy_spanned(ast::LitStr(value, ast::CookedStr));
165     mk_name_value_item(name, value_lit)
166 }
167
168 pub fn mk_name_value_item(name: InternedString, value: ast::Lit)
169                           -> @MetaItem {
170     @dummy_spanned(MetaNameValue(name, value))
171 }
172
173 pub fn mk_list_item(name: InternedString, items: Vec<@MetaItem> ) -> @MetaItem {
174     @dummy_spanned(MetaList(name, items))
175 }
176
177 pub fn mk_word_item(name: InternedString) -> @MetaItem {
178     @dummy_spanned(MetaWord(name))
179 }
180
181 local_data_key!(next_attr_id: uint)
182
183 pub fn mk_attr_id() -> AttrId {
184     let id = next_attr_id.replace(None).unwrap_or(0);
185     next_attr_id.replace(Some(id + 1));
186     AttrId(id)
187 }
188
189 /// Returns an inner attribute with the given value.
190 pub fn mk_attr_inner(id: AttrId, item: @MetaItem) -> Attribute {
191     dummy_spanned(Attribute_ {
192         id: id,
193         style: ast::AttrInner,
194         value: item,
195         is_sugared_doc: false,
196     })
197 }
198
199 /// Returns an outer attribute with the given value.
200 pub fn mk_attr_outer(id: AttrId, item: @MetaItem) -> Attribute {
201     dummy_spanned(Attribute_ {
202         id: id,
203         style: ast::AttrOuter,
204         value: item,
205         is_sugared_doc: false,
206     })
207 }
208
209 pub fn mk_sugared_doc_attr(id: AttrId, text: InternedString, lo: BytePos,
210                            hi: BytePos)
211                            -> Attribute {
212     let style = doc_comment_style(text.get());
213     let lit = spanned(lo, hi, ast::LitStr(text, ast::CookedStr));
214     let attr = Attribute_ {
215         id: id,
216         style: style,
217         value: @spanned(lo, hi, MetaNameValue(InternedString::new("doc"),
218                                               lit)),
219         is_sugared_doc: true
220     };
221     spanned(lo, hi, attr)
222 }
223
224 /* Searching */
225 /// Check if `needle` occurs in `haystack` by a structural
226 /// comparison. This is slightly subtle, and relies on ignoring the
227 /// span included in the `==` comparison a plain MetaItem.
228 pub fn contains(haystack: &[@ast::MetaItem],
229                 needle: @ast::MetaItem) -> bool {
230     debug!("attr::contains (name={})", needle.name());
231     haystack.iter().any(|item| {
232         debug!("  testing: {}", item.name());
233         item.node == needle.node
234     })
235 }
236
237 pub fn contains_name<AM: AttrMetaMethods>(metas: &[AM], name: &str) -> bool {
238     debug!("attr::contains_name (name={})", name);
239     metas.iter().any(|item| {
240         debug!("  testing: {}", item.name());
241         item.check_name(name)
242     })
243 }
244
245 pub fn first_attr_value_str_by_name(attrs: &[Attribute], name: &str)
246                                  -> Option<InternedString> {
247     attrs.iter()
248         .find(|at| at.check_name(name))
249         .and_then(|at| at.value_str())
250 }
251
252 pub fn last_meta_item_value_str_by_name(items: &[@MetaItem], name: &str)
253                                      -> Option<InternedString> {
254     items.iter()
255          .rev()
256          .find(|mi| mi.check_name(name))
257          .and_then(|i| i.value_str())
258 }
259
260 /* Higher-level applications */
261
262 pub fn sort_meta_items(items: &[@MetaItem]) -> Vec<@MetaItem> {
263     // This is sort of stupid here, but we need to sort by
264     // human-readable strings.
265     let mut v = items.iter()
266         .map(|&mi| (mi.name(), mi))
267         .collect::<Vec<(InternedString, @MetaItem)> >();
268
269     v.sort_by(|&(ref a, _), &(ref b, _)| a.cmp(b));
270
271     // There doesn't seem to be a more optimal way to do this
272     v.move_iter().map(|(_, m)| {
273         match m.node {
274             MetaList(ref n, ref mis) => {
275                 @Spanned {
276                     node: MetaList((*n).clone(),
277                                    sort_meta_items(mis.as_slice())),
278                     .. /*bad*/ (*m).clone()
279                 }
280             }
281             _ => m
282         }
283     }).collect()
284 }
285
286 /**
287  * From a list of crate attributes get only the meta_items that affect crate
288  * linkage
289  */
290 pub fn find_linkage_metas(attrs: &[Attribute]) -> Vec<@MetaItem> {
291     let mut result = Vec::new();
292     for attr in attrs.iter().filter(|at| at.check_name("link")) {
293         match attr.meta().node {
294             MetaList(_, ref items) => result.push_all(items.as_slice()),
295             _ => ()
296         }
297     }
298     result
299 }
300
301 pub fn find_crateid(attrs: &[Attribute]) -> Option<CrateId> {
302     match first_attr_value_str_by_name(attrs, "crate_id") {
303         None => None,
304         Some(id) => from_str::<CrateId>(id.get()),
305     }
306 }
307
308 #[deriving(Eq)]
309 pub enum InlineAttr {
310     InlineNone,
311     InlineHint,
312     InlineAlways,
313     InlineNever,
314 }
315
316 /// True if something like #[inline] is found in the list of attrs.
317 pub fn find_inline_attr(attrs: &[Attribute]) -> InlineAttr {
318     // FIXME (#2809)---validate the usage of #[inline] and #[inline]
319     attrs.iter().fold(InlineNone, |ia,attr| {
320         match attr.node.value.node {
321             MetaWord(ref n) if n.equiv(&("inline")) => {
322                 mark_used(attr);
323                 InlineHint
324             }
325             MetaList(ref n, ref items) if n.equiv(&("inline")) => {
326                 mark_used(attr);
327                 if contains_name(items.as_slice(), "always") {
328                     InlineAlways
329                 } else if contains_name(items.as_slice(), "never") {
330                     InlineNever
331                 } else {
332                     InlineHint
333                 }
334             }
335             _ => ia
336         }
337     })
338 }
339
340 /// Tests if any `cfg(...)` meta items in `metas` match `cfg`. e.g.
341 ///
342 /// test_cfg(`[foo="a", bar]`, `[cfg(foo), cfg(bar)]`) == true
343 /// test_cfg(`[foo="a", bar]`, `[cfg(not(bar))]`) == false
344 /// test_cfg(`[foo="a", bar]`, `[cfg(bar, foo="a")]`) == true
345 /// test_cfg(`[foo="a", bar]`, `[cfg(bar, foo="b")]`) == false
346 pub fn test_cfg<AM: AttrMetaMethods, It: Iterator<AM>>
347     (cfg: &[@MetaItem], mut metas: It) -> bool {
348     // having no #[cfg(...)] attributes counts as matching.
349     let mut no_cfgs = true;
350
351     // this would be much nicer as a chain of iterator adaptors, but
352     // this doesn't work.
353     let some_cfg_matches = metas.any(|mi| {
354         debug!("testing name: {}", mi.name());
355         if mi.check_name("cfg") { // it is a #[cfg()] attribute
356             debug!("is cfg");
357             no_cfgs = false;
358              // only #[cfg(...)] ones are understood.
359             match mi.meta_item_list() {
360                 Some(cfg_meta) => {
361                     debug!("is cfg(...)");
362                     cfg_meta.iter().all(|cfg_mi| {
363                         debug!("cfg({}[...])", cfg_mi.name());
364                         match cfg_mi.node {
365                             ast::MetaList(ref s, ref not_cfgs)
366                             if s.equiv(&("not")) => {
367                                 debug!("not!");
368                                 // inside #[cfg(not(...))], so these need to all
369                                 // not match.
370                                 !not_cfgs.iter().all(|mi| {
371                                     debug!("cfg(not({}[...]))", mi.name());
372                                     contains(cfg, *mi)
373                                 })
374                             }
375                             _ => contains(cfg, *cfg_mi)
376                         }
377                     })
378                 }
379                 None => false
380             }
381         } else {
382             false
383         }
384     });
385     debug!("test_cfg (no_cfgs={}, some_cfg_matches={})", no_cfgs, some_cfg_matches);
386     no_cfgs || some_cfg_matches
387 }
388
389 /// Represents the #[deprecated="foo"] and friends attributes.
390 pub struct Stability {
391     pub level: StabilityLevel,
392     pub text: Option<InternedString>
393 }
394
395 /// The available stability levels.
396 #[deriving(Eq,Ord,Clone,Show)]
397 pub enum StabilityLevel {
398     Deprecated,
399     Experimental,
400     Unstable,
401     Stable,
402     Frozen,
403     Locked
404 }
405
406 pub fn find_stability_generic<'a,
407                               AM: AttrMetaMethods,
408                               I: Iterator<&'a AM>>
409                              (mut attrs: I)
410                              -> Option<(Stability, &'a AM)> {
411     for attr in attrs {
412         let level = match attr.name().get() {
413             "deprecated" => Deprecated,
414             "experimental" => Experimental,
415             "unstable" => Unstable,
416             "stable" => Stable,
417             "frozen" => Frozen,
418             "locked" => Locked,
419             _ => continue // not a stability level
420         };
421
422         return Some((Stability {
423                 level: level,
424                 text: attr.value_str()
425             }, attr));
426     }
427     None
428 }
429
430 /// Find the first stability attribute. `None` if none exists.
431 pub fn find_stability(attrs: &[Attribute]) -> Option<Stability> {
432     find_stability_generic(attrs.iter()).map(|(s, attr)| {
433         mark_used(attr);
434         s
435     })
436 }
437
438 pub fn require_unique_names(diagnostic: &SpanHandler, metas: &[@MetaItem]) {
439     let mut set = HashSet::new();
440     for meta in metas.iter() {
441         let name = meta.name();
442
443         if !set.insert(name.clone()) {
444             diagnostic.span_fatal(meta.span,
445                                   format!("duplicate meta item `{}`",
446                                           name).as_slice());
447         }
448     }
449 }
450
451
452 /**
453  * Fold this over attributes to parse #[repr(...)] forms.
454  *
455  * Valid repr contents: any of the primitive integral type names (see
456  * `int_type_of_word`, below) to specify the discriminant type; and `C`, to use
457  * the same discriminant size that the corresponding C enum would.  These are
458  * not allowed on univariant or zero-variant enums, which have no discriminant.
459  *
460  * If a discriminant type is so specified, then the discriminant will be
461  * present (before fields, if any) with that type; reprensentation
462  * optimizations which would remove it will not be done.
463  */
464 pub fn find_repr_attr(diagnostic: &SpanHandler, attr: &Attribute, acc: ReprAttr)
465     -> ReprAttr {
466     let mut acc = acc;
467     info!("{}", ::print::pprust::attribute_to_str(attr));
468     match attr.node.value.node {
469         ast::MetaList(ref s, ref items) if s.equiv(&("repr")) => {
470             mark_used(attr);
471             for item in items.iter() {
472                 match item.node {
473                     ast::MetaWord(ref word) => {
474                         let hint = match word.get() {
475                             // Can't use "extern" because it's not a lexical identifier.
476                             "C" => ReprExtern,
477                             _ => match int_type_of_word(word.get()) {
478                                 Some(ity) => ReprInt(item.span, ity),
479                                 None => {
480                                     // Not a word we recognize
481                                     diagnostic.span_err(item.span,
482                                                         "unrecognized representation hint");
483                                     ReprAny
484                                 }
485                             }
486                         };
487                         if hint != ReprAny {
488                             if acc == ReprAny {
489                                 acc = hint;
490                             } else if acc != hint {
491                                 diagnostic.span_warn(item.span,
492                                                      "conflicting representation hint ignored")
493                             }
494                         }
495                     }
496                     // Not a word:
497                     _ => diagnostic.span_err(item.span, "unrecognized representation hint")
498                 }
499             }
500         }
501         // Not a "repr" hint: ignore.
502         _ => { }
503     }
504     acc
505 }
506
507 fn int_type_of_word(s: &str) -> Option<IntType> {
508     match s {
509         "i8" => Some(SignedInt(ast::TyI8)),
510         "u8" => Some(UnsignedInt(ast::TyU8)),
511         "i16" => Some(SignedInt(ast::TyI16)),
512         "u16" => Some(UnsignedInt(ast::TyU16)),
513         "i32" => Some(SignedInt(ast::TyI32)),
514         "u32" => Some(UnsignedInt(ast::TyU32)),
515         "i64" => Some(SignedInt(ast::TyI64)),
516         "u64" => Some(UnsignedInt(ast::TyU64)),
517         "int" => Some(SignedInt(ast::TyI)),
518         "uint" => Some(UnsignedInt(ast::TyU)),
519         _ => None
520     }
521 }
522
523 #[deriving(Eq, Show)]
524 pub enum ReprAttr {
525     ReprAny,
526     ReprInt(Span, IntType),
527     ReprExtern
528 }
529
530 impl ReprAttr {
531     pub fn is_ffi_safe(&self) -> bool {
532         match *self {
533             ReprAny => false,
534             ReprInt(_sp, ity) => ity.is_ffi_safe(),
535             ReprExtern => true
536         }
537     }
538 }
539
540 #[deriving(Eq, Show)]
541 pub enum IntType {
542     SignedInt(ast::IntTy),
543     UnsignedInt(ast::UintTy)
544 }
545
546 impl IntType {
547     #[inline]
548     pub fn is_signed(self) -> bool {
549         match self {
550             SignedInt(..) => true,
551             UnsignedInt(..) => false
552         }
553     }
554     fn is_ffi_safe(self) -> bool {
555         match self {
556             SignedInt(ast::TyI8) | UnsignedInt(ast::TyU8) |
557             SignedInt(ast::TyI16) | UnsignedInt(ast::TyU16) |
558             SignedInt(ast::TyI32) | UnsignedInt(ast::TyU32) |
559             SignedInt(ast::TyI64) | UnsignedInt(ast::TyU64) => true,
560             _ => false
561         }
562     }
563 }