summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_builtin_macros/src/format.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_builtin_macros/src/format.rs')
-rw-r--r--compiler/rustc_builtin_macros/src/format.rs254
1 files changed, 122 insertions, 132 deletions
diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs
index e93a23394..f0fc61d7c 100644
--- a/compiler/rustc_builtin_macros/src/format.rs
+++ b/compiler/rustc_builtin_macros/src/format.rs
@@ -7,7 +7,7 @@ use rustc_ast::{
FormatDebugHex, FormatOptions, FormatPlaceholder, FormatSign, FormatTrait,
};
use rustc_data_structures::fx::FxHashSet;
-use rustc_errors::{pluralize, Applicability, MultiSpan, PResult};
+use rustc_errors::{Applicability, MultiSpan, PResult, SingleLabelManySpans};
use rustc_expand::base::{self, *};
use rustc_parse_format as parse;
use rustc_span::symbol::{Ident, Symbol};
@@ -36,6 +36,23 @@ enum PositionUsedAs {
}
use PositionUsedAs::*;
+use crate::errors;
+
+struct MacroInput {
+ fmtstr: P<Expr>,
+ args: FormatArguments,
+ /// Whether the first argument was a string literal or a result from eager macro expansion.
+ /// If it's not a string literal, we disallow implicit argument capturing.
+ ///
+ /// This does not correspond to whether we can treat spans to the literal normally, as the whole
+ /// invocation might be the result of another macro expansion, in which case this flag may still be true.
+ ///
+ /// See [RFC 2795] for more information.
+ ///
+ /// [RFC 2795]: https://rust-lang.github.io/rfcs/2795-format-args-implicit-identifiers.html#macro-hygiene
+ is_direct_literal: bool,
+}
+
/// Parses the arguments from the given list of tokens, returning the diagnostic
/// if there's a parse error so we can continue parsing other format!
/// expressions.
@@ -45,39 +62,31 @@ use PositionUsedAs::*;
/// ```text
/// Ok((fmtstr, parsed arguments))
/// ```
-fn parse_args<'a>(
- ecx: &mut ExtCtxt<'a>,
- sp: Span,
- tts: TokenStream,
-) -> PResult<'a, (P<Expr>, FormatArguments)> {
+fn parse_args<'a>(ecx: &mut ExtCtxt<'a>, sp: Span, tts: TokenStream) -> PResult<'a, MacroInput> {
let mut args = FormatArguments::new();
let mut p = ecx.new_parser_from_tts(tts);
if p.token == token::Eof {
- return Err(ecx.struct_span_err(sp, "requires at least a format string argument"));
+ return Err(ecx.create_err(errors::FormatRequiresString { span: sp }));
}
let first_token = &p.token;
- let fmtstr = match first_token.kind {
- token::TokenKind::Literal(token::Lit {
- kind: token::LitKind::Str | token::LitKind::StrRaw(_),
- ..
- }) => {
- // If the first token is a string literal, then a format expression
- // is constructed from it.
- //
- // This allows us to properly handle cases when the first comma
- // after the format string is mistakenly replaced with any operator,
- // which cause the expression parser to eat too much tokens.
- p.parse_literal_maybe_minus()?
- }
- _ => {
- // Otherwise, we fall back to the expression parser.
- p.parse_expr()?
- }
+
+ let fmtstr = if let token::Literal(lit) = first_token.kind && matches!(lit.kind, token::Str | token::StrRaw(_)) {
+ // This allows us to properly handle cases when the first comma
+ // after the format string is mistakenly replaced with any operator,
+ // which cause the expression parser to eat too much tokens.
+ p.parse_literal_maybe_minus()?
+ } else {
+ // Otherwise, we fall back to the expression parser.
+ p.parse_expr()?
};
+ // Only allow implicit captures to be used when the argument is a direct literal
+ // instead of a macro expanding to one.
+ let is_direct_literal = matches!(fmtstr.kind, ExprKind::Lit(_));
+
let mut first = true;
while p.token != token::Eof {
@@ -114,13 +123,12 @@ fn parse_args<'a>(
p.expect(&token::Eq)?;
let expr = p.parse_expr()?;
if let Some((_, prev)) = args.by_name(ident.name) {
- ecx.struct_span_err(
- ident.span,
- &format!("duplicate argument named `{}`", ident),
- )
- .span_label(prev.kind.ident().unwrap().span, "previously here")
- .span_label(ident.span, "duplicate argument")
- .emit();
+ ecx.emit_err(errors::FormatDuplicateArg {
+ span: ident.span,
+ prev: prev.kind.ident().unwrap().span,
+ duplicate: ident.span,
+ ident,
+ });
continue;
}
args.add(FormatArgument { kind: FormatArgumentKind::Named(ident), expr });
@@ -128,36 +136,39 @@ fn parse_args<'a>(
_ => {
let expr = p.parse_expr()?;
if !args.named_args().is_empty() {
- let mut err = ecx.struct_span_err(
- expr.span,
- "positional arguments cannot follow named arguments",
- );
- err.span_label(
- expr.span,
- "positional arguments must be before named arguments",
- );
- for arg in args.named_args() {
- if let Some(name) = arg.kind.ident() {
- err.span_label(name.span.to(arg.expr.span), "named argument");
- }
- }
- err.emit();
+ ecx.emit_err(errors::PositionalAfterNamed {
+ span: expr.span,
+ args: args
+ .named_args()
+ .iter()
+ .filter_map(|a| {
+ if let Some(ident) = a.kind.ident() {
+ Some((a, ident))
+ } else {
+ None
+ }
+ })
+ .map(|(arg, n)| n.span.to(arg.expr.span))
+ .collect(),
+ });
}
args.add(FormatArgument { kind: FormatArgumentKind::Normal, expr });
}
}
}
- Ok((fmtstr, args))
+ Ok(MacroInput { fmtstr, args, is_direct_literal })
}
-pub fn make_format_args(
+fn make_format_args(
ecx: &mut ExtCtxt<'_>,
- efmt: P<Expr>,
- mut args: FormatArguments,
+ input: MacroInput,
append_newline: bool,
) -> Result<FormatArgs, ()> {
let msg = "format argument must be a string literal";
- let unexpanded_fmt_span = efmt.span;
+ let unexpanded_fmt_span = input.fmtstr.span;
+
+ let MacroInput { fmtstr: efmt, mut args, is_direct_literal } = input;
+
let (fmt_str, fmt_style, fmt_span) = match expr_to_spanned_string(ecx, efmt, msg) {
Ok(mut fmt) if append_newline => {
fmt.0 = Symbol::intern(&format!("{}\n", fmt.0));
@@ -208,11 +219,11 @@ pub fn make_format_args(
}
}
- let is_literal = parser.is_literal;
+ let is_source_literal = parser.is_source_literal;
if !parser.errors.is_empty() {
let err = parser.errors.remove(0);
- let sp = if is_literal {
+ let sp = if is_source_literal {
fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end))
} else {
// The format string could be another macro invocation, e.g.:
@@ -225,13 +236,19 @@ pub fn make_format_args(
// argument span here.
fmt_span
};
- let mut e = ecx.struct_span_err(sp, &format!("invalid format string: {}", err.description));
- e.span_label(sp, err.label + " in format string");
+ let mut e = errors::InvalidFormatString {
+ span: sp,
+ note_: None,
+ label_: None,
+ sugg_: None,
+ desc: err.description,
+ label1: err.label,
+ };
if let Some(note) = err.note {
- e.note(&note);
+ e.note_ = Some(errors::InvalidFormatStringNote { note });
}
- if let Some((label, span)) = err.secondary_label && is_literal {
- e.span_label(fmt_span.from_inner(InnerSpan::new(span.start, span.end)), label);
+ if let Some((label, span)) = err.secondary_label && is_source_literal {
+ e.label_ = Some(errors::InvalidFormatStringLabel { span: fmt_span.from_inner(InnerSpan::new(span.start, span.end)), label } );
}
if err.should_be_replaced_with_positional_argument {
let captured_arg_span =
@@ -241,22 +258,20 @@ pub fn make_format_args(
Some(arg) => arg.expr.span,
None => fmt_span,
};
- e.multipart_suggestion_verbose(
- "consider using a positional formatting argument instead",
- vec![
- (captured_arg_span, args.unnamed_args().len().to_string()),
- (span.shrink_to_hi(), format!(", {}", arg)),
- ],
- Applicability::MachineApplicable,
- );
+ e.sugg_ = Some(errors::InvalidFormatStringSuggestion {
+ captured: captured_arg_span,
+ len: args.unnamed_args().len().to_string(),
+ span: span.shrink_to_hi(),
+ arg,
+ });
}
}
- e.emit();
+ ecx.emit_err(e);
return Err(());
}
let to_span = |inner_span: rustc_parse_format::InnerSpan| {
- is_literal.then(|| {
+ is_source_literal.then(|| {
fmt_span.from_inner(InnerSpan { start: inner_span.start, end: inner_span.end })
})
};
@@ -304,15 +319,12 @@ pub fn make_format_args(
// Name not found in `args`, so we add it as an implicitly captured argument.
let span = span.unwrap_or(fmt_span);
let ident = Ident::new(name, span);
- let expr = if is_literal {
+ let expr = if is_direct_literal {
ecx.expr_ident(span, ident)
} else {
// For the moment capturing variables from format strings expanded from macros is
// disabled (see RFC #2795)
- ecx.struct_span_err(span, &format!("there is no argument named `{name}`"))
- .note(format!("did you intend to capture a variable `{name}` from the surrounding scope?"))
- .note("to avoid ambiguity, `format_args!` cannot capture variables when the format string is expanded from a macro")
- .emit();
+ ecx.emit_err(errors::FormatNoArgNamed { span, name });
DummyResult::raw_expr(span, true)
};
Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr }))
@@ -466,12 +478,8 @@ pub fn make_format_args(
.enumerate()
.filter(|&(_, used)| !used)
.map(|(i, _)| {
- let msg = if let FormatArgumentKind::Named(_) = args.explicit_args()[i].kind {
- "named argument never used"
- } else {
- "argument never used"
- };
- (args.explicit_args()[i].expr.span, msg)
+ let named = matches!(args.explicit_args()[i].kind, FormatArgumentKind::Named(_));
+ (args.explicit_args()[i].expr.span, named)
})
.collect::<Vec<_>>();
@@ -522,22 +530,8 @@ fn invalid_placeholder_type_error(
fmt_span: Span,
) {
let sp = ty_span.map(|sp| fmt_span.from_inner(InnerSpan::new(sp.start, sp.end)));
- let mut err =
- ecx.struct_span_err(sp.unwrap_or(fmt_span), &format!("unknown format trait `{}`", ty));
- err.note(
- "the only appropriate formatting traits are:\n\
- - ``, which uses the `Display` trait\n\
- - `?`, which uses the `Debug` trait\n\
- - `e`, which uses the `LowerExp` trait\n\
- - `E`, which uses the `UpperExp` trait\n\
- - `o`, which uses the `Octal` trait\n\
- - `p`, which uses the `Pointer` trait\n\
- - `b`, which uses the `Binary` trait\n\
- - `x`, which uses the `LowerHex` trait\n\
- - `X`, which uses the `UpperHex` trait",
- );
- if let Some(sp) = sp {
- for (fmt, name) in &[
+ let suggs = if let Some(sp) = sp {
+ [
("", "Display"),
("?", "Debug"),
("e", "LowerExp"),
@@ -547,40 +541,38 @@ fn invalid_placeholder_type_error(
("b", "Binary"),
("x", "LowerHex"),
("X", "UpperHex"),
- ] {
- err.tool_only_span_suggestion(
- sp,
- &format!("use the `{}` trait", name),
- *fmt,
- Applicability::MaybeIncorrect,
- );
- }
- }
- err.emit();
+ ]
+ .into_iter()
+ .map(|(fmt, trait_name)| errors::FormatUnknownTraitSugg { span: sp, fmt, trait_name })
+ .collect()
+ } else {
+ vec![]
+ };
+ ecx.emit_err(errors::FormatUnknownTrait { span: sp.unwrap_or(fmt_span), ty, suggs });
}
fn report_missing_placeholders(
ecx: &mut ExtCtxt<'_>,
- unused: Vec<(Span, &str)>,
+ unused: Vec<(Span, bool)>,
detect_foreign_fmt: bool,
str_style: Option<usize>,
fmt_str: &str,
fmt_span: Span,
) {
- let mut diag = if let &[(span, msg)] = &unused[..] {
- let mut diag = ecx.struct_span_err(span, msg);
- diag.span_label(span, msg);
- diag
+ let mut diag = if let &[(span, named)] = &unused[..] {
+ //let mut diag = ecx.struct_span_err(span, msg);
+ //diag.span_label(span, msg);
+ //diag
+ ecx.create_err(errors::FormatUnusedArg { span, named })
} else {
- let mut diag = ecx.struct_span_err(
- unused.iter().map(|&(sp, _)| sp).collect::<Vec<Span>>(),
- "multiple unused formatting arguments",
- );
- diag.span_label(fmt_span, "multiple missing formatting specifiers");
- for &(span, msg) in &unused {
- diag.span_label(span, msg);
- }
- diag
+ let unused_labels =
+ unused.iter().map(|&(span, named)| errors::FormatUnusedArg { span, named }).collect();
+ let unused_spans = unused.iter().map(|&(span, _)| span).collect();
+ ecx.create_err(errors::FormatUnusedArgs {
+ fmt: fmt_span,
+ unused: unused_spans,
+ unused_labels,
+ })
};
// Used to ensure we only report translations for *one* kind of foreign format.
@@ -759,18 +751,16 @@ fn report_invalid_references(
} else {
MultiSpan::from_spans(spans)
};
- e = ecx.struct_span_err(
+ e = ecx.create_err(errors::FormatPositionalMismatch {
span,
- &format!(
- "{} positional argument{} in format string, but {}",
- num_placeholders,
- pluralize!(num_placeholders),
- num_args_desc,
- ),
- );
- for arg in args.explicit_args() {
- e.span_label(arg.expr.span, "");
- }
+ n: num_placeholders,
+ desc: num_args_desc,
+ highlight: SingleLabelManySpans {
+ spans: args.explicit_args().iter().map(|arg| arg.expr.span).collect(),
+ label: "",
+ kind: rustc_errors::LabelKind::Label,
+ },
+ });
// Point out `{:.*}` placeholders: those take an extra argument.
let mut has_precision_star = false;
for piece in template {
@@ -814,7 +804,7 @@ fn report_invalid_references(
// for `println!("{7:7$}", 1);`
indexes.sort();
indexes.dedup();
- let span: MultiSpan = if !parser.is_literal || parser.arg_places.is_empty() {
+ let span: MultiSpan = if !parser.is_source_literal || parser.arg_places.is_empty() {
MultiSpan::from_span(fmt_span)
} else {
MultiSpan::from_spans(invalid_refs.iter().filter_map(|&(_, span, _, _)| span).collect())
@@ -855,8 +845,8 @@ fn expand_format_args_impl<'cx>(
) -> Box<dyn base::MacResult + 'cx> {
sp = ecx.with_def_site_ctxt(sp);
match parse_args(ecx, sp, tts) {
- Ok((efmt, args)) => {
- if let Ok(format_args) = make_format_args(ecx, efmt, args, nl) {
+ Ok(input) => {
+ if let Ok(format_args) = make_format_args(ecx, input, nl) {
MacEager::expr(ecx.expr(sp, ExprKind::FormatArgs(P(format_args))))
} else {
MacEager::expr(DummyResult::raw_expr(sp, true))