]>
Commit | Line | Data |
---|---|---|
ba9703b0 XL |
1 | //! Conditional compilation stripping. |
2 | ||
74b04a01 | 3 | use rustc_ast::ptr::P; |
29967ef6 | 4 | use rustc_ast::token::{DelimToken, Token, TokenKind}; |
cdc7bbd5 XL |
5 | use rustc_ast::tokenstream::{AttrAnnotatedTokenStream, AttrAnnotatedTokenTree}; |
6 | use rustc_ast::tokenstream::{DelimSpan, Spacing}; | |
7 | use rustc_ast::tokenstream::{LazyTokenStream, TokenTree}; | |
94222f64 | 8 | use rustc_ast::{self as ast, AstLike, AttrStyle, Attribute, MetaItem}; |
74b04a01 | 9 | use rustc_attr as attr; |
dfeec247 | 10 | use rustc_data_structures::fx::FxHashMap; |
ba9703b0 | 11 | use rustc_data_structures::map_in_place::MapInPlace; |
dfeec247 XL |
12 | use rustc_errors::{error_code, struct_span_err, Applicability, Handler}; |
13 | use rustc_feature::{Feature, Features, State as FeatureState}; | |
14 | use rustc_feature::{ | |
15 | ACCEPTED_FEATURES, ACTIVE_FEATURES, REMOVED_FEATURES, STABLE_REMOVED_FEATURES, | |
16 | }; | |
94222f64 | 17 | use rustc_parse::validate_attr; |
3dfed10e XL |
18 | use rustc_session::parse::feature_err; |
19 | use rustc_session::Session; | |
dfeec247 XL |
20 | use rustc_span::edition::{Edition, ALL_EDITIONS}; |
21 | use rustc_span::symbol::{sym, Symbol}; | |
22 | use rustc_span::{Span, DUMMY_SP}; | |
60c5eb7d | 23 | |
3157f602 XL |
24 | /// A folder that strips out items that do not belong in the current configuration. |
25 | pub struct StripUnconfigured<'a> { | |
3dfed10e | 26 | pub sess: &'a Session, |
3157f602 | 27 | pub features: Option<&'a Features>, |
cdc7bbd5 XL |
28 | /// If `true`, perform cfg-stripping on attached tokens. |
29 | /// This is only used for the input to derive macros, | |
30 | /// which needs eager expansion of `cfg` and `cfg_attr` | |
31 | pub config_tokens: bool, | |
1a4d82fc JJ |
32 | } |
33 | ||
dfeec247 | 34 | fn get_features( |
3dfed10e | 35 | sess: &Session, |
dfeec247 XL |
36 | span_handler: &Handler, |
37 | krate_attrs: &[ast::Attribute], | |
dfeec247 XL |
38 | ) -> Features { |
39 | fn feature_removed(span_handler: &Handler, span: Span, reason: Option<&str>) { | |
40 | let mut err = struct_span_err!(span_handler, span, E0557, "feature has been removed"); | |
41 | err.span_label(span, "feature has been removed"); | |
42 | if let Some(reason) = reason { | |
43 | err.note(reason); | |
44 | } | |
45 | err.emit(); | |
46 | } | |
47 | ||
48 | fn active_features_up_to(edition: Edition) -> impl Iterator<Item = &'static Feature> { | |
49 | ACTIVE_FEATURES.iter().filter(move |feature| { | |
50 | if let Some(feature_edition) = feature.edition { | |
51 | feature_edition <= edition | |
52 | } else { | |
53 | false | |
54 | } | |
55 | }) | |
56 | } | |
57 | ||
58 | let mut features = Features::default(); | |
59 | let mut edition_enabled_features = FxHashMap::default(); | |
3dfed10e | 60 | let crate_edition = sess.edition(); |
dfeec247 XL |
61 | |
62 | for &edition in ALL_EDITIONS { | |
63 | if edition <= crate_edition { | |
64 | // The `crate_edition` implies its respective umbrella feature-gate | |
65 | // (i.e., `#![feature(rust_20XX_preview)]` isn't needed on edition 20XX). | |
66 | edition_enabled_features.insert(edition.feature_name(), edition); | |
67 | } | |
68 | } | |
69 | ||
70 | for feature in active_features_up_to(crate_edition) { | |
71 | feature.set(&mut features, DUMMY_SP); | |
72 | edition_enabled_features.insert(feature.name, crate_edition); | |
73 | } | |
74 | ||
75 | // Process the edition umbrella feature-gates first, to ensure | |
76 | // `edition_enabled_features` is completed before it's queried. | |
77 | for attr in krate_attrs { | |
94222f64 | 78 | if !attr.has_name(sym::feature) { |
dfeec247 XL |
79 | continue; |
80 | } | |
81 | ||
82 | let list = match attr.meta_item_list() { | |
83 | Some(list) => list, | |
84 | None => continue, | |
85 | }; | |
86 | ||
87 | for mi in list { | |
88 | if !mi.is_word() { | |
89 | continue; | |
90 | } | |
91 | ||
92 | let name = mi.name_or_empty(); | |
93 | ||
94 | let edition = ALL_EDITIONS.iter().find(|e| name == e.feature_name()).copied(); | |
95 | if let Some(edition) = edition { | |
96 | if edition <= crate_edition { | |
97 | continue; | |
98 | } | |
99 | ||
100 | for feature in active_features_up_to(edition) { | |
101 | // FIXME(Manishearth) there is currently no way to set | |
102 | // lib features by edition | |
103 | feature.set(&mut features, DUMMY_SP); | |
104 | edition_enabled_features.insert(feature.name, edition); | |
105 | } | |
106 | } | |
107 | } | |
108 | } | |
109 | ||
110 | for attr in krate_attrs { | |
94222f64 | 111 | if !attr.has_name(sym::feature) { |
dfeec247 XL |
112 | continue; |
113 | } | |
114 | ||
115 | let list = match attr.meta_item_list() { | |
116 | Some(list) => list, | |
117 | None => continue, | |
118 | }; | |
119 | ||
120 | let bad_input = |span| { | |
121 | struct_span_err!(span_handler, span, E0556, "malformed `feature` attribute input") | |
122 | }; | |
123 | ||
124 | for mi in list { | |
125 | let name = match mi.ident() { | |
126 | Some(ident) if mi.is_word() => ident.name, | |
127 | Some(ident) => { | |
128 | bad_input(mi.span()) | |
129 | .span_suggestion( | |
130 | mi.span(), | |
131 | "expected just one word", | |
132 | format!("{}", ident.name), | |
133 | Applicability::MaybeIncorrect, | |
134 | ) | |
135 | .emit(); | |
136 | continue; | |
137 | } | |
138 | None => { | |
139 | bad_input(mi.span()).span_label(mi.span(), "expected just one word").emit(); | |
140 | continue; | |
141 | } | |
142 | }; | |
143 | ||
144 | if let Some(edition) = edition_enabled_features.get(&name) { | |
145 | let msg = | |
146 | &format!("the feature `{}` is included in the Rust {} edition", name, edition); | |
147 | span_handler.struct_span_warn_with_code(mi.span(), msg, error_code!(E0705)).emit(); | |
148 | continue; | |
149 | } | |
150 | ||
151 | if ALL_EDITIONS.iter().any(|e| name == e.feature_name()) { | |
152 | // Handled in the separate loop above. | |
153 | continue; | |
154 | } | |
155 | ||
156 | let removed = REMOVED_FEATURES.iter().find(|f| name == f.name); | |
157 | let stable_removed = STABLE_REMOVED_FEATURES.iter().find(|f| name == f.name); | |
158 | if let Some(Feature { state, .. }) = removed.or(stable_removed) { | |
159 | if let FeatureState::Removed { reason } | FeatureState::Stabilized { reason } = | |
160 | state | |
161 | { | |
162 | feature_removed(span_handler, mi.span(), *reason); | |
163 | continue; | |
164 | } | |
165 | } | |
166 | ||
167 | if let Some(Feature { since, .. }) = ACCEPTED_FEATURES.iter().find(|f| name == f.name) { | |
168 | let since = Some(Symbol::intern(since)); | |
169 | features.declared_lang_features.push((name, mi.span(), since)); | |
170 | continue; | |
171 | } | |
172 | ||
3dfed10e | 173 | if let Some(allowed) = sess.opts.debugging_opts.allow_features.as_ref() { |
c295e0f8 | 174 | if allowed.iter().all(|f| name.as_str() != *f) { |
dfeec247 XL |
175 | struct_span_err!( |
176 | span_handler, | |
177 | mi.span(), | |
178 | E0725, | |
179 | "the feature `{}` is not in the list of allowed features", | |
180 | name | |
181 | ) | |
182 | .emit(); | |
183 | continue; | |
184 | } | |
185 | } | |
186 | ||
187 | if let Some(f) = ACTIVE_FEATURES.iter().find(|f| name == f.name) { | |
188 | f.set(&mut features, mi.span()); | |
189 | features.declared_lang_features.push((name, mi.span(), None)); | |
190 | continue; | |
191 | } | |
192 | ||
193 | features.declared_lib_features.push((name, mi.span())); | |
194 | } | |
195 | } | |
196 | ||
197 | features | |
198 | } | |
199 | ||
9e0c209e | 200 | // `cfg_attr`-process the crate's attributes and compute the crate's features. |
3dfed10e | 201 | pub fn features(sess: &Session, mut krate: ast::Crate) -> (ast::Crate, Features) { |
cdc7bbd5 | 202 | let mut strip_unconfigured = StripUnconfigured { sess, features: None, config_tokens: false }; |
9e0c209e | 203 | |
74b04a01 | 204 | let unconfigured_attrs = krate.attrs.clone(); |
3dfed10e | 205 | let diag = &sess.parse_sess.span_diagnostic; |
74b04a01 | 206 | let err_count = diag.err_count(); |
6a06907d | 207 | let features = match strip_unconfigured.configure_krate_attrs(krate.attrs) { |
74b04a01 XL |
208 | None => { |
209 | // The entire crate is unconfigured. | |
9e0c209e | 210 | krate.attrs = Vec::new(); |
6a06907d | 211 | krate.items = Vec::new(); |
74b04a01 | 212 | Features::default() |
9e0c209e | 213 | } |
74b04a01 XL |
214 | Some(attrs) => { |
215 | krate.attrs = attrs; | |
3dfed10e | 216 | let features = get_features(sess, diag, &krate.attrs); |
74b04a01 XL |
217 | if err_count == diag.err_count() { |
218 | // Avoid reconfiguring malformed `cfg_attr`s. | |
219 | strip_unconfigured.features = Some(&features); | |
6a06907d XL |
220 | // Run configuration again, this time with features available |
221 | // so that we can perform feature-gating. | |
222 | strip_unconfigured.configure_krate_attrs(unconfigured_attrs); | |
74b04a01 XL |
223 | } |
224 | features | |
9e0c209e | 225 | } |
74b04a01 | 226 | }; |
9e0c209e SL |
227 | (krate, features) |
228 | } | |
229 | ||
e74abb32 | 230 | #[macro_export] |
9e0c209e SL |
231 | macro_rules! configure { |
232 | ($this:ident, $node:ident) => { | |
233 | match $this.configure($node) { | |
234 | Some(node) => node, | |
235 | None => return Default::default(), | |
236 | } | |
dfeec247 | 237 | }; |
9e0c209e SL |
238 | } |
239 | ||
3157f602 | 240 | impl<'a> StripUnconfigured<'a> { |
6a06907d | 241 | pub fn configure<T: AstLike>(&mut self, mut node: T) -> Option<T> { |
9fa01778 | 242 | self.process_cfg_attrs(&mut node); |
5869c6ff | 243 | if self.in_cfg(node.attrs()) { |
cdc7bbd5 | 244 | self.try_configure_tokens(&mut node); |
5869c6ff XL |
245 | Some(node) |
246 | } else { | |
5869c6ff XL |
247 | None |
248 | } | |
1a4d82fc | 249 | } |
1a4d82fc | 250 | |
cdc7bbd5 XL |
251 | fn try_configure_tokens<T: AstLike>(&mut self, node: &mut T) { |
252 | if self.config_tokens { | |
253 | if let Some(Some(tokens)) = node.tokens_mut() { | |
254 | let attr_annotated_tokens = tokens.create_token_stream(); | |
255 | *tokens = LazyTokenStream::new(self.configure_tokens(&attr_annotated_tokens)); | |
256 | } | |
257 | } | |
258 | } | |
259 | ||
6a06907d XL |
260 | fn configure_krate_attrs( |
261 | &mut self, | |
262 | mut attrs: Vec<ast::Attribute>, | |
263 | ) -> Option<Vec<ast::Attribute>> { | |
264 | attrs.flat_map_in_place(|attr| self.process_cfg_attr(attr)); | |
cdc7bbd5 XL |
265 | if self.in_cfg(&attrs) { Some(attrs) } else { None } |
266 | } | |
267 | ||
268 | /// Performs cfg-expansion on `stream`, producing a new `AttrAnnotatedTokenStream`. | |
269 | /// This is only used during the invocation of `derive` proc-macros, | |
270 | /// which require that we cfg-expand their entire input. | |
271 | /// Normal cfg-expansion operates on parsed AST nodes via the `configure` method | |
272 | fn configure_tokens(&mut self, stream: &AttrAnnotatedTokenStream) -> AttrAnnotatedTokenStream { | |
273 | fn can_skip(stream: &AttrAnnotatedTokenStream) -> bool { | |
274 | stream.0.iter().all(|(tree, _spacing)| match tree { | |
275 | AttrAnnotatedTokenTree::Attributes(_) => false, | |
276 | AttrAnnotatedTokenTree::Token(_) => true, | |
277 | AttrAnnotatedTokenTree::Delimited(_, _, inner) => can_skip(inner), | |
278 | }) | |
279 | } | |
280 | ||
281 | if can_skip(stream) { | |
282 | return stream.clone(); | |
6a06907d | 283 | } |
cdc7bbd5 XL |
284 | |
285 | let trees: Vec<_> = stream | |
286 | .0 | |
287 | .iter() | |
288 | .flat_map(|(tree, spacing)| match tree.clone() { | |
289 | AttrAnnotatedTokenTree::Attributes(mut data) => { | |
290 | let mut attrs: Vec<_> = std::mem::take(&mut data.attrs).into(); | |
291 | attrs.flat_map_in_place(|attr| self.process_cfg_attr(attr)); | |
292 | data.attrs = attrs.into(); | |
293 | ||
294 | if self.in_cfg(&data.attrs) { | |
295 | data.tokens = LazyTokenStream::new( | |
296 | self.configure_tokens(&data.tokens.create_token_stream()), | |
297 | ); | |
298 | Some((AttrAnnotatedTokenTree::Attributes(data), *spacing)).into_iter() | |
299 | } else { | |
300 | None.into_iter() | |
301 | } | |
302 | } | |
303 | AttrAnnotatedTokenTree::Delimited(sp, delim, mut inner) => { | |
304 | inner = self.configure_tokens(&inner); | |
305 | Some((AttrAnnotatedTokenTree::Delimited(sp, delim, inner), *spacing)) | |
306 | .into_iter() | |
307 | } | |
94222f64 XL |
308 | AttrAnnotatedTokenTree::Token(ref token) if let TokenKind::Interpolated(ref nt) = token.kind => { |
309 | panic!( | |
310 | "Nonterminal should have been flattened at {:?}: {:?}", | |
311 | token.span, nt | |
312 | ); | |
313 | } | |
cdc7bbd5 | 314 | AttrAnnotatedTokenTree::Token(token) => { |
94222f64 | 315 | Some((AttrAnnotatedTokenTree::Token(token), *spacing)).into_iter() |
cdc7bbd5 XL |
316 | } |
317 | }) | |
318 | .collect(); | |
319 | AttrAnnotatedTokenStream::new(trees) | |
6a06907d XL |
320 | } |
321 | ||
0bf4aa26 XL |
322 | /// Parse and expand all `cfg_attr` attributes into a list of attributes |
323 | /// that are within each `cfg_attr` that has a true configuration predicate. | |
324 | /// | |
74b04a01 | 325 | /// Gives compiler warnings if any `cfg_attr` does not contain any |
0bf4aa26 XL |
326 | /// attributes and is in the original source code. Gives compiler errors if |
327 | /// the syntax of any `cfg_attr` is incorrect. | |
6a06907d | 328 | fn process_cfg_attrs<T: AstLike>(&mut self, node: &mut T) { |
9fa01778 XL |
329 | node.visit_attrs(|attrs| { |
330 | attrs.flat_map_in_place(|attr| self.process_cfg_attr(attr)); | |
331 | }); | |
1a4d82fc | 332 | } |
85aaf69f | 333 | |
0bf4aa26 XL |
334 | /// Parse and expand a single `cfg_attr` attribute into a list of attributes |
335 | /// when the configuration predicate is true, or otherwise expand into an | |
336 | /// empty list of attributes. | |
337 | /// | |
338 | /// Gives a compiler warning when the `cfg_attr` contains no attributes and | |
339 | /// is in the original source file. Gives a compiler error if the syntax of | |
9fa01778 | 340 | /// the attribute is incorrect. |
60c5eb7d XL |
341 | fn process_cfg_attr(&mut self, attr: Attribute) -> Vec<Attribute> { |
342 | if !attr.has_name(sym::cfg_attr) { | |
0bf4aa26 | 343 | return vec![attr]; |
85aaf69f SL |
344 | } |
345 | ||
94222f64 XL |
346 | let (cfg_predicate, expanded_attrs) = |
347 | match rustc_parse::parse_cfg_attr(&attr, &self.sess.parse_sess) { | |
348 | None => return vec![], | |
349 | Some(r) => r, | |
350 | }; | |
9e0c209e | 351 | |
dc9dc135 XL |
352 | // Lint on zero attributes in source. |
353 | if expanded_attrs.is_empty() { | |
354 | return vec![attr]; | |
0bf4aa26 XL |
355 | } |
356 | ||
3dfed10e | 357 | if !attr::cfg_matches(&cfg_predicate, &self.sess.parse_sess, self.features) { |
60c5eb7d XL |
358 | return vec![]; |
359 | } | |
360 | ||
361 | // We call `process_cfg_attr` recursively in case there's a | |
362 | // `cfg_attr` inside of another `cfg_attr`. E.g. | |
363 | // `#[cfg_attr(false, cfg_attr(true, some_attr))]`. | |
364 | expanded_attrs | |
365 | .into_iter() | |
366 | .flat_map(|(item, span)| { | |
cdc7bbd5 | 367 | let orig_tokens = attr.tokens().to_tokenstream(); |
29967ef6 XL |
368 | |
369 | // We are taking an attribute of the form `#[cfg_attr(pred, attr)]` | |
370 | // and producing an attribute of the form `#[attr]`. We | |
371 | // have captured tokens for `attr` itself, but we need to | |
372 | // synthesize tokens for the wrapper `#` and `[]`, which | |
373 | // we do below. | |
374 | ||
375 | // Use the `#` in `#[cfg_attr(pred, attr)]` as the `#` token | |
376 | // for `attr` when we expand it to `#[attr]` | |
cdc7bbd5 XL |
377 | let mut orig_trees = orig_tokens.trees(); |
378 | let pound_token = match orig_trees.next().unwrap() { | |
379 | TokenTree::Token(token @ Token { kind: TokenKind::Pound, .. }) => token, | |
380 | _ => panic!("Bad tokens for attribute {:?}", attr), | |
381 | }; | |
382 | let pound_span = pound_token.span; | |
383 | ||
384 | let mut trees = vec![(AttrAnnotatedTokenTree::Token(pound_token), Spacing::Alone)]; | |
385 | if attr.style == AttrStyle::Inner { | |
386 | // For inner attributes, we do the same thing for the `!` in `#![some_attr]` | |
387 | let bang_token = match orig_trees.next().unwrap() { | |
388 | TokenTree::Token(token @ Token { kind: TokenKind::Not, .. }) => token, | |
389 | _ => panic!("Bad tokens for attribute {:?}", attr), | |
390 | }; | |
391 | trees.push((AttrAnnotatedTokenTree::Token(bang_token), Spacing::Alone)); | |
29967ef6 XL |
392 | } |
393 | // We don't really have a good span to use for the syntheized `[]` | |
394 | // in `#[attr]`, so just use the span of the `#` token. | |
cdc7bbd5 XL |
395 | let bracket_group = AttrAnnotatedTokenTree::Delimited( |
396 | DelimSpan::from_single(pound_span), | |
29967ef6 XL |
397 | DelimToken::Bracket, |
398 | item.tokens | |
399 | .as_ref() | |
400 | .unwrap_or_else(|| panic!("Missing tokens for {:?}", item)) | |
401 | .create_token_stream(), | |
402 | ); | |
cdc7bbd5 XL |
403 | trees.push((bracket_group, Spacing::Alone)); |
404 | let tokens = Some(LazyTokenStream::new(AttrAnnotatedTokenStream::new(trees))); | |
29967ef6 | 405 | self.process_cfg_attr(attr::mk_attr_from_item(item, tokens, attr.style, span)) |
60c5eb7d | 406 | }) |
0bf4aa26 | 407 | .collect() |
60c5eb7d XL |
408 | } |
409 | ||
9fa01778 | 410 | /// Determines if a node with the given attributes should be included in this configuration. |
6a06907d | 411 | fn in_cfg(&self, attrs: &[Attribute]) -> bool { |
3157f602 | 412 | attrs.iter().all(|attr| { |
94222f64 | 413 | if !is_cfg(attr) { |
cc61c64b | 414 | return true; |
8faf50e0 | 415 | } |
3dfed10e | 416 | let meta_item = match validate_attr::parse_meta(&self.sess.parse_sess, attr) { |
74b04a01 XL |
417 | Ok(meta_item) => meta_item, |
418 | Err(mut err) => { | |
419 | err.emit(); | |
420 | return true; | |
421 | } | |
422 | }; | |
17df50a5 XL |
423 | parse_cfg(&meta_item, &self.sess).map_or(true, |meta_item| { |
424 | attr::cfg_matches(&meta_item, &self.sess.parse_sess, self.features) | |
425 | }) | |
3157f602 XL |
426 | }) |
427 | } | |
92a42be0 | 428 | |
0531ce1d | 429 | /// If attributes are not allowed on expressions, emit an error for `attr` |
6a06907d | 430 | crate fn maybe_emit_expr_attr_err(&self, attr: &Attribute) { |
5869c6ff | 431 | if !self.features.map_or(true, |features| features.stmt_expr_attributes) { |
dfeec247 | 432 | let mut err = feature_err( |
3dfed10e | 433 | &self.sess.parse_sess, |
dfeec247 XL |
434 | sym::stmt_expr_attributes, |
435 | attr.span, | |
436 | "attributes on expressions are experimental", | |
437 | ); | |
0531ce1d | 438 | |
60c5eb7d | 439 | if attr.is_doc_comment() { |
0531ce1d | 440 | err.help("`///` is for documentation comments. For a plain comment, use `//`."); |
92a42be0 | 441 | } |
0531ce1d XL |
442 | |
443 | err.emit(); | |
92a42be0 SL |
444 | } |
445 | } | |
92a42be0 | 446 | |
9fa01778 | 447 | pub fn configure_expr(&mut self, expr: &mut P<ast::Expr>) { |
6a06907d XL |
448 | for attr in expr.attrs.iter() { |
449 | self.maybe_emit_expr_attr_err(attr); | |
450 | } | |
3157f602 XL |
451 | |
452 | // If an expr is valid to cfg away it will have been removed by the | |
453 | // outer stmt or expression folder before descending in here. | |
454 | // Anything else is always required, and thus has to error out | |
455 | // in case of a cfg attr. | |
456 | // | |
9fa01778 XL |
457 | // N.B., this is intentionally not part of the visit_expr() function |
458 | // in order for filter_map_expr() to be able to avoid this check | |
94222f64 | 459 | if let Some(attr) = expr.attrs().iter().find(|a| is_cfg(*a)) { |
3157f602 | 460 | let msg = "removing an expression is not supported in this position"; |
3dfed10e | 461 | self.sess.parse_sess.span_diagnostic.span_err(attr.span, msg); |
92a42be0 | 462 | } |
3157f602 | 463 | |
cdc7bbd5 XL |
464 | self.process_cfg_attrs(expr); |
465 | self.try_configure_tokens(&mut *expr); | |
92a42be0 | 466 | } |
92a42be0 SL |
467 | } |
468 | ||
17df50a5 XL |
469 | pub fn parse_cfg<'a>(meta_item: &'a MetaItem, sess: &Session) -> Option<&'a MetaItem> { |
470 | let error = |span, msg, suggestion: &str| { | |
471 | let mut err = sess.parse_sess.span_diagnostic.struct_span_err(span, msg); | |
472 | if !suggestion.is_empty() { | |
473 | err.span_suggestion( | |
474 | span, | |
475 | "expected syntax is", | |
476 | suggestion.into(), | |
477 | Applicability::HasPlaceholders, | |
478 | ); | |
479 | } | |
480 | err.emit(); | |
481 | None | |
482 | }; | |
483 | let span = meta_item.span; | |
484 | match meta_item.meta_item_list() { | |
485 | None => error(span, "`cfg` is not followed by parentheses", "cfg(/* predicate */)"), | |
486 | Some([]) => error(span, "`cfg` predicate is not specified", ""), | |
487 | Some([_, .., l]) => error(l.span(), "multiple `cfg` predicates are specified", ""), | |
488 | Some([single]) => match single.meta_item() { | |
489 | Some(meta_item) => Some(meta_item), | |
490 | None => error(single.span(), "`cfg` predicate key cannot be a literal", ""), | |
491 | }, | |
492 | } | |
493 | } | |
494 | ||
94222f64 XL |
495 | fn is_cfg(attr: &Attribute) -> bool { |
496 | attr.has_name(sym::cfg) | |
92a42be0 | 497 | } |