Skip to content

Commit 3d51086

Browse files
committed
Auto merge of #52394 - estebank:println, r=oli-obk
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 a57d5d7 + dc563d9 commit 3d51086

24 files changed

+351
-123
lines changed

src/libfmt_macros/lib.rs

+31-12
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,17 @@ pub struct Parser<'a> {
150150
pub errors: Vec<ParseError>,
151151
/// Current position of implicit positional argument pointer
152152
curarg: usize,
153+
/// `Some(raw count)` when the string is "raw", used to position spans correctly
154+
style: Option<usize>,
155+
/// How many newlines have been seen in the string so far, to adjust the error spans
156+
seen_newlines: usize,
153157
}
154158

155159
impl<'a> Iterator for Parser<'a> {
156160
type Item = Piece<'a>;
157161

158162
fn next(&mut self) -> Option<Piece<'a>> {
163+
let raw = self.style.map(|raw| raw + self.seen_newlines).unwrap_or(0);
159164
if let Some(&(pos, c)) = self.cur.peek() {
160165
match c {
161166
'{' => {
@@ -170,20 +175,24 @@ impl<'a> Iterator for Parser<'a> {
170175
}
171176
'}' => {
172177
self.cur.next();
173-
let pos = pos + 1;
174178
if self.consume('}') {
175-
Some(String(self.string(pos)))
179+
Some(String(self.string(pos + 1)))
176180
} else {
181+
let err_pos = pos + raw + 1;
177182
self.err_with_note(
178183
"unmatched `}` found",
179184
"unmatched `}`",
180185
"if you intended to print `}`, you can escape it using `}}`",
181-
pos,
182-
pos,
186+
err_pos,
187+
err_pos,
183188
);
184189
None
185190
}
186191
}
192+
'\n' => {
193+
self.seen_newlines += 1;
194+
Some(String(self.string(pos)))
195+
}
187196
_ => Some(String(self.string(pos))),
188197
}
189198
} else {
@@ -194,12 +203,14 @@ impl<'a> Iterator for Parser<'a> {
194203

195204
impl<'a> Parser<'a> {
196205
/// Creates a new parser for the given format string
197-
pub fn new(s: &'a str) -> Parser<'a> {
206+
pub fn new(s: &'a str, style: Option<usize>) -> Parser<'a> {
198207
Parser {
199208
input: s,
200209
cur: s.char_indices().peekable(),
201210
errors: vec![],
202211
curarg: 0,
212+
style,
213+
seen_newlines: 0,
203214
}
204215
}
205216

@@ -262,24 +273,32 @@ impl<'a> Parser<'a> {
262273
/// found, an error is emitted.
263274
fn must_consume(&mut self, c: char) {
264275
self.ws();
276+
let raw = self.style.unwrap_or(0);
277+
278+
let padding = raw + self.seen_newlines;
265279
if let Some(&(pos, maybe)) = self.cur.peek() {
266280
if c == maybe {
267281
self.cur.next();
268282
} else {
283+
let pos = pos + padding + 1;
269284
self.err(format!("expected `{:?}`, found `{:?}`", c, maybe),
270285
format!("expected `{}`", c),
271-
pos + 1,
272-
pos + 1);
286+
pos,
287+
pos);
273288
}
274289
} else {
275290
let msg = format!("expected `{:?}` but string was terminated", c);
276-
let pos = self.input.len() + 1; // point at closing `"`
291+
// point at closing `"`, unless the last char is `\n` to account for `println`
292+
let pos = match self.input.chars().last() {
293+
Some('\n') => self.input.len(),
294+
_ => self.input.len() + 1,
295+
};
277296
if c == '}' {
278297
self.err_with_note(msg,
279298
format!("expected `{:?}`", c),
280299
"if you intended to print `{`, you can escape it using `{{`",
281-
pos,
282-
pos);
300+
pos + padding,
301+
pos + padding);
283302
} else {
284303
self.err(msg, format!("expected `{:?}`", c), pos, pos);
285304
}
@@ -536,7 +555,7 @@ mod tests {
536555
use super::*;
537556

538557
fn same(fmt: &'static str, p: &[Piece<'static>]) {
539-
let parser = Parser::new(fmt);
558+
let parser = Parser::new(fmt, None);
540559
assert!(parser.collect::<Vec<Piece<'static>>>() == p);
541560
}
542561

@@ -552,7 +571,7 @@ mod tests {
552571
}
553572

554573
fn musterr(s: &str) {
555-
let mut p = Parser::new(s);
574+
let mut p = Parser::new(s, None);
556575
p.next();
557576
assert!(!p.errors.is_empty());
558577
}

src/librustc/traits/on_unimplemented.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -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, None);
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, None);
302302
parser.map(|p| {
303303
match p {
304304
Piece::String(s) => s,

src/libstd/macros.rs

+18-4
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.
@@ -210,10 +217,17 @@ macro_rules! eprint {
210217
/// ```
211218
#[macro_export]
212219
#[stable(feature = "eprint", since = "1.19.0")]
220+
#[allow_internal_unstable]
213221
macro_rules! eprintln {
214222
() => (eprint!("\n"));
215-
($fmt:expr) => (eprint!(concat!($fmt, "\n")));
216-
($fmt:expr, $($arg:tt)*) => (eprint!(concat!($fmt, "\n"), $($arg)*));
223+
($($arg:tt)*) => ({
224+
#[cfg(all(not(stage0), not(stage1)))] {
225+
($crate::io::_eprint(format_args_nl!($($arg)*)));
226+
}
227+
#[cfg(any(stage0, stage1))] {
228+
eprint!("{}\n", format_args!($($arg)*))
229+
}
230+
})
217231
}
218232

219233
#[macro_export]

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/expand.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1609,6 +1609,7 @@ impl<'feat> ExpansionConfig<'feat> {
16091609
fn enable_trace_macros = trace_macros,
16101610
fn enable_allow_internal_unstable = allow_internal_unstable,
16111611
fn enable_custom_derive = custom_derive,
1612+
fn enable_format_args_nl = format_args_nl,
16121613
fn use_extern_macros_enabled = use_extern_macros,
16131614
fn macros_in_extern_enabled = macros_in_extern,
16141615
fn proc_macro_mod = proc_macro_mod,

src/libsyntax/feature_gate.rs

+4
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ declare_features! (
129129
// rustc internal, for now:
130130
(active, intrinsics, "1.0.0", None, None),
131131
(active, lang_items, "1.0.0", None, None),
132+
(active, format_args_nl, "1.29.0", None, None),
132133

133134
(active, link_llvm_intrinsics, "1.0.0", Some(29602), None),
134135
(active, linkage, "1.0.0", Some(29603), None),
@@ -1320,6 +1321,9 @@ pub const EXPLAIN_LOG_SYNTAX: &'static str =
13201321
pub const EXPLAIN_CONCAT_IDENTS: &'static str =
13211322
"`concat_idents` is not stable enough for use and is subject to change";
13221323

1324+
pub const EXPLAIN_FORMAT_ARGS_NL: &'static str =
1325+
"`format_args_nl` is only for internal language use and is subject to change";
1326+
13231327
pub const EXPLAIN_TRACE_MACROS: &'static str =
13241328
"`trace_macros` is not stable enough for use and is subject to change";
13251329
pub const EXPLAIN_ALLOW_INTERNAL_UNSTABLE: &'static str =

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
}

0 commit comments

Comments
 (0)