Skip to content

Commit 4f70795

Browse files
committed
Auto merge of #52394 - estebank:println, r=<try>
Improve suggestion for missing fmt str in println Avoid using `concat!(fmt, "\n")` to improve the diagnostics being emitted when the first `println!()` argument isn't a formatting string literal. Fix #52347.
2 parents 878dd0b + 9112e1a commit 4f70795

23 files changed

+323
-123
lines changed

src/Cargo.lock

+3
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,9 @@ dependencies = [
715715
[[package]]
716716
name = "fmt_macros"
717717
version = "0.0.0"
718+
dependencies = [
719+
"syntax 0.0.0",
720+
]
718721

719722
[[package]]
720723
name = "fnv"

src/libfmt_macros/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@ version = "0.0.0"
77
name = "fmt_macros"
88
path = "lib.rs"
99
crate-type = ["dylib"]
10+
11+
[dependencies]
12+
syntax = { path = "../libsyntax" }

src/libfmt_macros/lib.rs

+39-12
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ pub use self::Alignment::*;
2828
pub use self::Flag::*;
2929
pub use self::Count::*;
3030

31+
extern crate syntax;
32+
3133
use std::str;
3234
use std::string;
3335
use std::iter;
@@ -150,12 +152,20 @@ pub struct Parser<'a> {
150152
pub errors: Vec<ParseError>,
151153
/// Current position of implicit positional argument pointer
152154
curarg: usize,
155+
/// The style of the string (raw or not), used to position spans correctly
156+
style: syntax::ast::StrStyle,
157+
/// How many newlines have been seen in the string so far, to adjust the error spans
158+
seen_newlines: usize,
153159
}
154160

155161
impl<'a> Iterator for Parser<'a> {
156162
type Item = Piece<'a>;
157163

158164
fn next(&mut self) -> Option<Piece<'a>> {
165+
let raw = match self.style {
166+
syntax::ast::StrStyle::Raw(raw) => raw as usize + self.seen_newlines,
167+
_ => 0,
168+
};
159169
if let Some(&(pos, c)) = self.cur.peek() {
160170
match c {
161171
'{' => {
@@ -170,20 +180,24 @@ impl<'a> Iterator for Parser<'a> {
170180
}
171181
'}' => {
172182
self.cur.next();
173-
let pos = pos + 1;
174183
if self.consume('}') {
175-
Some(String(self.string(pos)))
184+
Some(String(self.string(pos + 1)))
176185
} else {
186+
let err_pos = pos + raw + 1;
177187
self.err_with_note(
178188
"unmatched `}` found",
179189
"unmatched `}`",
180190
"if you intended to print `}`, you can escape it using `}}`",
181-
pos,
182-
pos,
191+
err_pos,
192+
err_pos,
183193
);
184194
None
185195
}
186196
}
197+
'\n' => {
198+
self.seen_newlines += 1;
199+
Some(String(self.string(pos)))
200+
}
187201
_ => Some(String(self.string(pos))),
188202
}
189203
} else {
@@ -194,12 +208,14 @@ impl<'a> Iterator for Parser<'a> {
194208

195209
impl<'a> Parser<'a> {
196210
/// Creates a new parser for the given format string
197-
pub fn new(s: &'a str) -> Parser<'a> {
211+
pub fn new(s: &'a str, style: syntax::ast::StrStyle) -> Parser<'a> {
198212
Parser {
199213
input: s,
200214
cur: s.char_indices().peekable(),
201215
errors: vec![],
202216
curarg: 0,
217+
style,
218+
seen_newlines: 0,
203219
}
204220
}
205221

@@ -262,24 +278,35 @@ impl<'a> Parser<'a> {
262278
/// found, an error is emitted.
263279
fn must_consume(&mut self, c: char) {
264280
self.ws();
281+
let raw = match self.style {
282+
syntax::ast::StrStyle::Raw(raw) => raw as usize,
283+
_ => 0,
284+
};
285+
286+
let padding = raw + self.seen_newlines;
265287
if let Some(&(pos, maybe)) = self.cur.peek() {
266288
if c == maybe {
267289
self.cur.next();
268290
} else {
291+
let pos = pos + padding + 1;
269292
self.err(format!("expected `{:?}`, found `{:?}`", c, maybe),
270293
format!("expected `{}`", c),
271-
pos + 1,
272-
pos + 1);
294+
pos,
295+
pos);
273296
}
274297
} else {
275298
let msg = format!("expected `{:?}` but string was terminated", c);
276-
let pos = self.input.len() + 1; // point at closing `"`
299+
// point at closing `"`, unless the last char is `\n` to account for `println`
300+
let pos = match self.input.chars().last() {
301+
Some('\n') => self.input.len(),
302+
_ => self.input.len() + 1,
303+
};
277304
if c == '}' {
278305
self.err_with_note(msg,
279306
format!("expected `{:?}`", c),
280307
"if you intended to print `{`, you can escape it using `{{`",
281-
pos,
282-
pos);
308+
pos + padding,
309+
pos + padding);
283310
} else {
284311
self.err(msg, format!("expected `{:?}`", c), pos, pos);
285312
}
@@ -536,7 +563,7 @@ mod tests {
536563
use super::*;
537564

538565
fn same(fmt: &'static str, p: &[Piece<'static>]) {
539-
let parser = Parser::new(fmt);
566+
let parser = Parser::new(fmt, syntax::ast::StrStyle::Cooked);
540567
assert!(parser.collect::<Vec<Piece<'static>>>() == p);
541568
}
542569

@@ -552,7 +579,7 @@ mod tests {
552579
}
553580

554581
fn musterr(s: &str) {
555-
let mut p = Parser::new(s);
582+
let mut p = Parser::new(s, syntax::ast::StrStyle::Cooked);
556583
p.next();
557584
assert!(!p.errors.is_empty());
558585
}

src/librustc/traits/on_unimplemented.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use ty::{self, TyCtxt, GenericParamDefKind};
1515
use util::common::ErrorReported;
1616
use util::nodemap::FxHashMap;
1717

18-
use syntax::ast::{MetaItem, NestedMetaItem};
18+
use syntax::ast::{self, MetaItem, NestedMetaItem};
1919
use syntax::attr;
2020
use syntax_pos::Span;
2121
use syntax_pos::symbol::LocalInternedString;
@@ -242,7 +242,7 @@ impl<'a, 'gcx, 'tcx> OnUnimplementedFormatString {
242242
{
243243
let name = tcx.item_name(trait_def_id);
244244
let generics = tcx.generics_of(trait_def_id);
245-
let parser = Parser::new(&self.0);
245+
let parser = Parser::new(&self.0, ast::StrStyle::Cooked);
246246
let mut result = Ok(());
247247
for token in parser {
248248
match token {
@@ -298,7 +298,7 @@ impl<'a, 'gcx, 'tcx> OnUnimplementedFormatString {
298298
Some((name, value))
299299
}).collect::<FxHashMap<String, String>>();
300300

301-
let parser = Parser::new(&self.0);
301+
let parser = Parser::new(&self.0, ast::StrStyle::Cooked);
302302
parser.map(|p| {
303303
match p {
304304
Piece::String(s) => s,

src/libstd/macros.rs

+22-2
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,17 @@ macro_rules! print {
153153
/// ```
154154
#[macro_export]
155155
#[stable(feature = "rust1", since = "1.0.0")]
156+
#[allow_internal_unstable]
156157
macro_rules! println {
157158
() => (print!("\n"));
158-
($fmt:expr) => (print!(concat!($fmt, "\n")));
159-
($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
159+
($($arg:tt)*) => ({
160+
#[cfg(not(stage0))] {
161+
($crate::io::_print(format_args_nl!($($arg)*)));
162+
}
163+
#[cfg(stage0)] {
164+
print!("{}\n", format_args!($($arg)*))
165+
}
166+
})
160167
}
161168

162169
/// Macro for printing to the standard error.
@@ -399,6 +406,19 @@ pub mod builtin {
399406
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ });
400407
}
401408

409+
/// Internal version of [`format_args`].
410+
///
411+
/// This macro differs from [`format_args`] in that it appends a newline to the format string
412+
/// and nothing more. It is perma-unstable.
413+
///
414+
/// [`format_args`]: ../std/macro.format_args.html
415+
#[doc(hidden)]
416+
#[unstable(feature = "println_format_args", issue="0")]
417+
#[macro_export]
418+
macro_rules! format_args_nl {
419+
($fmt:expr) => ({ /* compiler built-in */ });
420+
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ });
421+
}
402422
/// Inspect an environment variable at compile time.
403423
///
404424
/// This macro will expand to the value of the named environment variable at

src/libsyntax/ext/base.rs

+15-10
Original file line numberDiff line numberDiff line change
@@ -959,29 +959,34 @@ impl<'a> ExtCtxt<'a> {
959959
/// Extract a string literal from the macro expanded version of `expr`,
960960
/// emitting `err_msg` if `expr` is not a string literal. This does not stop
961961
/// compilation on error, merely emits a non-fatal error and returns None.
962-
pub fn expr_to_spanned_string(cx: &mut ExtCtxt, expr: P<ast::Expr>, err_msg: &str)
963-
-> Option<Spanned<(Symbol, ast::StrStyle)>> {
962+
pub fn expr_to_spanned_string<'a>(
963+
cx: &'a mut ExtCtxt,
964+
expr: P<ast::Expr>,
965+
err_msg: &str,
966+
) -> Result<Spanned<(Symbol, ast::StrStyle)>, DiagnosticBuilder<'a>> {
964967
// Update `expr.span`'s ctxt now in case expr is an `include!` macro invocation.
965968
let expr = expr.map(|mut expr| {
966969
expr.span = expr.span.apply_mark(cx.current_expansion.mark);
967970
expr
968971
});
969972

970-
// we want to be able to handle e.g. concat("foo", "bar")
973+
// we want to be able to handle e.g. `concat!("foo", "bar")`
971974
let expr = cx.expander().fold_expr(expr);
972-
match expr.node {
975+
Err(match expr.node {
973976
ast::ExprKind::Lit(ref l) => match l.node {
974-
ast::LitKind::Str(s, style) => return Some(respan(expr.span, (s, style))),
975-
_ => cx.span_err(l.span, err_msg)
977+
ast::LitKind::Str(s, style) => return Ok(respan(expr.span, (s, style))),
978+
_ => cx.struct_span_err(l.span, err_msg)
976979
},
977-
_ => cx.span_err(expr.span, err_msg)
978-
}
979-
None
980+
_ => cx.struct_span_err(expr.span, err_msg)
981+
})
980982
}
981983

982984
pub fn expr_to_string(cx: &mut ExtCtxt, expr: P<ast::Expr>, err_msg: &str)
983985
-> Option<(Symbol, ast::StrStyle)> {
984-
expr_to_spanned_string(cx, expr, err_msg).map(|s| s.node)
986+
expr_to_spanned_string(cx, expr, err_msg)
987+
.map_err(|mut err| err.emit())
988+
.ok()
989+
.map(|s| s.node)
985990
}
986991

987992
/// Non-fatally assert that `tts` is empty. Note that this function

src/libsyntax_ext/concat.rs

+7-8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub fn expand_syntax_ext(
2727
None => return base::DummyResult::expr(sp),
2828
};
2929
let mut accumulator = String::new();
30+
let mut missing_literal = vec![];
3031
for e in es {
3132
match e.node {
3233
ast::ExprKind::Lit(ref lit) => match lit.node {
@@ -51,17 +52,15 @@ pub fn expand_syntax_ext(
5152
}
5253
},
5354
_ => {
54-
let mut err = cx.struct_span_err(e.span, "expected a literal");
55-
let snippet = cx.codemap().span_to_snippet(e.span).unwrap();
56-
err.span_suggestion(
57-
e.span,
58-
"you might be missing a string literal to format with",
59-
format!("\"{{}}\", {}", snippet),
60-
);
61-
err.emit();
55+
missing_literal.push(e.span);
6256
}
6357
}
6458
}
59+
if missing_literal.len() > 0 {
60+
let mut err = cx.struct_span_err(missing_literal, "expected a literal");
61+
err.note("only literals (like `\"foo\"`, `42` and `3.14`) can be passed to `concat!()`");
62+
err.emit();
63+
}
6564
let sp = sp.apply_mark(cx.current_expansion.mark);
6665
base::MacEager::expr(cx.expr_str(sp, Symbol::intern(&accumulator)))
6766
}

src/libsyntax_ext/format.rs

+43-9
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,20 @@ pub fn expand_format_args<'cx>(ecx: &'cx mut ExtCtxt,
683683
sp = sp.apply_mark(ecx.current_expansion.mark);
684684
match parse_args(ecx, sp, tts) {
685685
Some((efmt, args, names)) => {
686-
MacEager::expr(expand_preparsed_format_args(ecx, sp, efmt, args, names))
686+
MacEager::expr(expand_preparsed_format_args(ecx, sp, efmt, args, names, false))
687+
}
688+
None => DummyResult::expr(sp),
689+
}
690+
}
691+
692+
pub fn expand_format_args_nl<'cx>(ecx: &'cx mut ExtCtxt,
693+
mut sp: Span,
694+
tts: &[tokenstream::TokenTree])
695+
-> Box<dyn base::MacResult + 'cx> {
696+
sp = sp.apply_mark(ecx.current_expansion.mark);
697+
match parse_args(ecx, sp, tts) {
698+
Some((efmt, args, names)) => {
699+
MacEager::expr(expand_preparsed_format_args(ecx, sp, efmt, args, names, true))
687700
}
688701
None => DummyResult::expr(sp),
689702
}
@@ -695,18 +708,37 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt,
695708
sp: Span,
696709
efmt: P<ast::Expr>,
697710
args: Vec<P<ast::Expr>>,
698-
names: HashMap<String, usize>)
711+
names: HashMap<String, usize>,
712+
append_newline: bool)
699713
-> P<ast::Expr> {
700714
// NOTE: this verbose way of initializing `Vec<Vec<ArgumentType>>` is because
701715
// `ArgumentType` does not derive `Clone`.
702716
let arg_types: Vec<_> = (0..args.len()).map(|_| Vec::new()).collect();
703717
let arg_unique_types: Vec<_> = (0..args.len()).map(|_| Vec::new()).collect();
704718
let mut macsp = ecx.call_site();
705719
macsp = macsp.apply_mark(ecx.current_expansion.mark);
706-
let msg = "format argument must be a string literal.";
720+
let msg = "format argument must be a string literal";
721+
let fmt_sp = efmt.span;
707722
let fmt = match expr_to_spanned_string(ecx, efmt, msg) {
708-
Some(fmt) => fmt,
709-
None => return DummyResult::raw_expr(sp),
723+
Ok(mut fmt) if append_newline => {
724+
fmt.node.0 = Symbol::intern(&format!("{}\n", fmt.node.0));
725+
fmt
726+
}
727+
Ok(fmt) => fmt,
728+
Err(mut err) => {
729+
let sugg_fmt = match args.len() {
730+
0 => "{}".to_string(),
731+
_ => format!("{}{{}}", "{}, ".repeat(args.len())),
732+
733+
};
734+
err.span_suggestion(
735+
fmt_sp.shrink_to_lo(),
736+
"you might be missing a string literal to format with",
737+
format!("\"{}\", ", sugg_fmt),
738+
);
739+
err.emit();
740+
return DummyResult::raw_expr(sp);
741+
},
710742
};
711743

712744
let mut cx = Context {
@@ -731,7 +763,7 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt,
731763
};
732764

733765
let fmt_str = &*fmt.node.0.as_str();
734-
let mut parser = parse::Parser::new(fmt_str);
766+
let mut parser = parse::Parser::new(fmt_str, fmt.node.1);
735767
let mut pieces = vec![];
736768

737769
while let Some(mut piece) = parser.next() {
@@ -818,7 +850,7 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt,
818850
errs.iter().map(|&(sp, _)| sp).collect::<Vec<Span>>(),
819851
"multiple unused formatting arguments"
820852
);
821-
diag.span_label(cx.fmtsp, "multiple unused arguments in this statement");
853+
diag.span_label(cx.fmtsp, "multiple missing formatting arguments");
822854
diag
823855
}
824856
};
@@ -861,8 +893,10 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt,
861893
}
862894

863895
if show_doc_note {
864-
diag.note(concat!(stringify!($kind), " formatting not supported; see \
865-
the documentation for `std::fmt`"));
896+
diag.note(concat!(
897+
stringify!($kind),
898+
" formatting not supported; see the documentation for `std::fmt`",
899+
));
866900
}
867901
}};
868902
}

0 commit comments

Comments
 (0)