mirror of
				https://github.com/rust-lang/rust.git
				synced 2025-10-31 04:57:19 +00:00 
			
		
		
		
	 99f5945f85
			
		
	
	
		99f5945f85
		
	
	
	
	
		
			
			The value in `MacArgs::Eq` is currently represented as a `Token`. Because of `TokenKind::Interpolated`, `Token` can be either a token or an arbitrary AST fragment. In practice, a `MacArgs::Eq` starts out as a literal or macro call AST fragment, and then is later lowered to a literal token. But this is very non-obvious. `Token` is a much more general type than what is needed. This commit restricts things, by introducing a new type `MacArgsEqKind` that is either an AST expression (pre-lowering) or an AST literal (post-lowering). The downside is that the code is a bit more verbose in a few places. The benefit is that makes it much clearer what the possibilities are (though also shorter in some other places). Also, it removes one use of `TokenKind::Interpolated`, taking us a step closer to removing that variant, which will let us make `Token` impl `Copy` and remove many "handle Interpolated" code paths in the parser. Things to note: - Error messages have improved. Messages like this: ``` unexpected token: `"bug" + "found"` ``` now say "unexpected expression", which makes more sense. Although arbitrary expressions can exist within tokens thanks to `TokenKind::Interpolated`, that's not obvious to anyone who doesn't know compiler internals. - In `parse_mac_args_common`, we no longer need to collect tokens for the value expression.
		
			
				
	
	
		
			201 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			201 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| //! Meta-syntax validation logic of attributes for post-expansion.
 | |
| 
 | |
| use crate::parse_in;
 | |
| 
 | |
| use rustc_ast::tokenstream::DelimSpan;
 | |
| use rustc_ast::{self as ast, Attribute, MacArgs, MacArgsEq, MacDelimiter, MetaItem, MetaItemKind};
 | |
| use rustc_ast_pretty::pprust;
 | |
| use rustc_errors::{Applicability, FatalError, PResult};
 | |
| use rustc_feature::{AttributeTemplate, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP};
 | |
| use rustc_session::lint::builtin::ILL_FORMED_ATTRIBUTE_INPUT;
 | |
| use rustc_session::parse::ParseSess;
 | |
| use rustc_span::{sym, Symbol};
 | |
| 
 | |
| pub fn check_meta(sess: &ParseSess, attr: &Attribute) {
 | |
|     if attr.is_doc_comment() {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     let attr_info = attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name));
 | |
| 
 | |
|     // Check input tokens for built-in and key-value attributes.
 | |
|     match attr_info {
 | |
|         // `rustc_dummy` doesn't have any restrictions specific to built-in attributes.
 | |
|         Some(BuiltinAttribute { name, template, .. }) if *name != sym::rustc_dummy => {
 | |
|             check_builtin_attribute(sess, attr, *name, *template)
 | |
|         }
 | |
|         _ if let MacArgs::Eq(..) = attr.get_normal_item().args => {
 | |
|             // All key-value attributes are restricted to meta-item syntax.
 | |
|             parse_meta(sess, attr)
 | |
|                 .map_err(|mut err| {
 | |
|                     err.emit();
 | |
|                 })
 | |
|                 .ok();
 | |
|         }
 | |
|         _ => {}
 | |
|     }
 | |
| }
 | |
| 
 | |
| pub fn parse_meta<'a>(sess: &'a ParseSess, attr: &Attribute) -> PResult<'a, MetaItem> {
 | |
|     let item = attr.get_normal_item();
 | |
|     Ok(MetaItem {
 | |
|         span: attr.span,
 | |
|         path: item.path.clone(),
 | |
|         kind: match &item.args {
 | |
|             MacArgs::Empty => MetaItemKind::Word,
 | |
|             MacArgs::Delimited(dspan, delim, t) => {
 | |
|                 check_meta_bad_delim(sess, *dspan, *delim, "wrong meta list delimiters");
 | |
|                 let nmis = parse_in(sess, t.clone(), "meta list", |p| p.parse_meta_seq_top())?;
 | |
|                 MetaItemKind::List(nmis)
 | |
|             }
 | |
|             MacArgs::Eq(_, MacArgsEq::Ast(expr)) => {
 | |
|                 if let ast::ExprKind::Lit(lit) = &expr.kind {
 | |
|                     if !lit.kind.is_unsuffixed() {
 | |
|                         let mut err = sess.span_diagnostic.struct_span_err(
 | |
|                             lit.span,
 | |
|                             "suffixed literals are not allowed in attributes",
 | |
|                         );
 | |
|                         err.help(
 | |
|                             "instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), \
 | |
|                             use an unsuffixed version (`1`, `1.0`, etc.)",
 | |
|                         );
 | |
|                         return Err(err);
 | |
|                     } else {
 | |
|                         MetaItemKind::NameValue(lit.clone())
 | |
|                     }
 | |
|                 } else {
 | |
|                     // The non-error case can happen with e.g. `#[foo = 1+1]`. The error case can
 | |
|                     // happen with e.g. `#[foo = include_str!("non-existent-file.rs")]`; in that
 | |
|                     // case we delay the error because an earlier error will have already been
 | |
|                     // reported.
 | |
|                     let msg = format!("unexpected expression: `{}`", pprust::expr_to_string(expr));
 | |
|                     let mut err = sess.span_diagnostic.struct_span_err(expr.span, msg);
 | |
|                     if let ast::ExprKind::Err = expr.kind {
 | |
|                         err.downgrade_to_delayed_bug();
 | |
|                     }
 | |
|                     return Err(err);
 | |
|                 }
 | |
|             }
 | |
|             MacArgs::Eq(_, MacArgsEq::Hir(lit)) => MetaItemKind::NameValue(lit.clone()),
 | |
|         },
 | |
|     })
 | |
| }
 | |
| 
 | |
| pub fn check_meta_bad_delim(sess: &ParseSess, span: DelimSpan, delim: MacDelimiter, msg: &str) {
 | |
|     if let ast::MacDelimiter::Parenthesis = delim {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     sess.span_diagnostic
 | |
|         .struct_span_err(span.entire(), msg)
 | |
|         .multipart_suggestion(
 | |
|             "the delimiters should be `(` and `)`",
 | |
|             vec![(span.open, "(".to_string()), (span.close, ")".to_string())],
 | |
|             Applicability::MachineApplicable,
 | |
|         )
 | |
|         .emit();
 | |
| }
 | |
| 
 | |
| /// Checks that the given meta-item is compatible with this `AttributeTemplate`.
 | |
| fn is_attr_template_compatible(template: &AttributeTemplate, meta: &ast::MetaItemKind) -> bool {
 | |
|     match meta {
 | |
|         MetaItemKind::Word => template.word,
 | |
|         MetaItemKind::List(..) => template.list.is_some(),
 | |
|         MetaItemKind::NameValue(lit) if lit.kind.is_str() => template.name_value_str.is_some(),
 | |
|         MetaItemKind::NameValue(..) => false,
 | |
|     }
 | |
| }
 | |
| 
 | |
| pub fn check_builtin_attribute(
 | |
|     sess: &ParseSess,
 | |
|     attr: &Attribute,
 | |
|     name: Symbol,
 | |
|     template: AttributeTemplate,
 | |
| ) {
 | |
|     // Some special attributes like `cfg` must be checked
 | |
|     // before the generic check, so we skip them here.
 | |
|     let should_skip = |name| name == sym::cfg;
 | |
| 
 | |
|     match parse_meta(sess, attr) {
 | |
|         Ok(meta) => {
 | |
|             if !should_skip(name) && !is_attr_template_compatible(&template, &meta.kind) {
 | |
|                 emit_malformed_attribute(sess, attr, name, template);
 | |
|             }
 | |
|         }
 | |
|         Err(mut err) => {
 | |
|             err.emit();
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| fn emit_malformed_attribute(
 | |
|     sess: &ParseSess,
 | |
|     attr: &Attribute,
 | |
|     name: Symbol,
 | |
|     template: AttributeTemplate,
 | |
| ) {
 | |
|     // Some of previously accepted forms were used in practice,
 | |
|     // report them as warnings for now.
 | |
|     let should_warn = |name| {
 | |
|         matches!(name, sym::doc | sym::ignore | sym::inline | sym::link | sym::test | sym::bench)
 | |
|     };
 | |
| 
 | |
|     let error_msg = format!("malformed `{}` attribute input", name);
 | |
|     let mut msg = "attribute must be of the form ".to_owned();
 | |
|     let mut suggestions = vec![];
 | |
|     let mut first = true;
 | |
|     let inner = if attr.style == ast::AttrStyle::Inner { "!" } else { "" };
 | |
|     if template.word {
 | |
|         first = false;
 | |
|         let code = format!("#{}[{}]", inner, name);
 | |
|         msg.push_str(&format!("`{}`", &code));
 | |
|         suggestions.push(code);
 | |
|     }
 | |
|     if let Some(descr) = template.list {
 | |
|         if !first {
 | |
|             msg.push_str(" or ");
 | |
|         }
 | |
|         first = false;
 | |
|         let code = format!("#{}[{}({})]", inner, name, descr);
 | |
|         msg.push_str(&format!("`{}`", &code));
 | |
|         suggestions.push(code);
 | |
|     }
 | |
|     if let Some(descr) = template.name_value_str {
 | |
|         if !first {
 | |
|             msg.push_str(" or ");
 | |
|         }
 | |
|         let code = format!("#{}[{} = \"{}\"]", inner, name, descr);
 | |
|         msg.push_str(&format!("`{}`", &code));
 | |
|         suggestions.push(code);
 | |
|     }
 | |
|     if should_warn(name) {
 | |
|         sess.buffer_lint(&ILL_FORMED_ATTRIBUTE_INPUT, attr.span, ast::CRATE_NODE_ID, &msg);
 | |
|     } else {
 | |
|         sess.span_diagnostic
 | |
|             .struct_span_err(attr.span, &error_msg)
 | |
|             .span_suggestions(
 | |
|                 attr.span,
 | |
|                 if suggestions.len() == 1 {
 | |
|                     "must be of the form"
 | |
|                 } else {
 | |
|                     "the following are the possible correct uses"
 | |
|                 },
 | |
|                 suggestions.into_iter(),
 | |
|                 Applicability::HasPlaceholders,
 | |
|             )
 | |
|             .emit();
 | |
|     }
 | |
| }
 | |
| 
 | |
| pub fn emit_fatal_malformed_builtin_attribute(
 | |
|     sess: &ParseSess,
 | |
|     attr: &Attribute,
 | |
|     name: Symbol,
 | |
| ) -> ! {
 | |
|     let template = BUILTIN_ATTRIBUTE_MAP.get(&name).expect("builtin attr defined").template;
 | |
|     emit_malformed_attribute(sess, attr, name, template);
 | |
|     // This is fatal, otherwise it will likely cause a cascade of other errors
 | |
|     // (and an error here is expected to be very rare).
 | |
|     FatalError.raise()
 | |
| }
 |