Change:
I ran into an issue in where was getting expanded into instead of , which resulted in a compile error. We should add some tests that make sure we can still compile code after macros have been expanded.
To clarify: if I understand you correctly, you're saying that the expansion of error!("...") was being rendered by pprust as log(1,"..."), not that the expansion was actually incorrect. Is that correct? First off, that would obviously be a bug in the pretty-printer, which should be fixed. The larger issue of making sure that pretty-printed code compiles I would nominate for well-covered.
Yep, that was the problem, it's fixed now though. The larger issue still stands.
just a bug, removing milestone/nomination.
Fixed by !
Yay!

Discussion:
}; use crate::utils::{is_type_diagnostic_item, qpath_res, same_tys, sext, sugg}; use if_chain::if_chain; <del> use itertools::Itertools; </del> use rustc_ast::ast; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_errors::Applicability;

fn same_var<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &Expr<'_>, var: HirId) -> bool { if_chain! { <del> if let ExprKind::Path(ref qpath) = expr.kind; if let QPath::Resolved(None, ref path) = *qpath; </del> <ins> if let ExprKind::Path(qpath) = &expr.kind; if let QPath::Resolved(None, path) = qpath; </ins> if path.segments.len() == 1; if let Res::Local(local_id) = qpath_res(cx, qpath, expr.hir_id); // our variable! if local_id == var; then { <del> return true; </del> <ins> true } else { false </ins> } } <ins> } </ins> <del> false </del> <ins> #[derive(Clone, Copy)] enum OffsetSign { Positive, Negative, </ins> } struct Offset { value: String, <del> negate: bool, </del> <ins> sign: OffsetSign, </ins> } impl Offset { <del> fn negative(s: String) -> Self { Self { value: s, negate: true } </del> <ins> fn negative(value: String) -> Self { Self { value, sign: OffsetSign::Negative, } </ins> } <del> fn positive(s: String) -> Self { </del> <ins> fn positive(value: String) -> Self { </ins> Self { <del> value: s, negate: false, </del> <ins> value, sign: OffsetSign::Positive, </ins> } } }

is_slice || is_type_diagnostic_item(cx, ty, sym!(vec_type)) || is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) } <del> fn get_fixed_offset_var<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &Expr<'_>, var: HirId) -> Option<FixedOffsetVar> { </del> <ins> fn fetch_cloned_expr<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> { if_chain! { if let ExprKind::MethodCall(method, _, args) = expr.kind; if method.ident.name == sym!(clone); if args.len() == 1; if let Some(arg) = args.get(0); then { arg } else { expr } } } fn get_offset<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, idx: &Expr<'_>, var: HirId) -> Option<Offset> { </ins> fn extract_offset<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, e: &Expr<'_>, var: HirId) -> Option<String> { <del> match e.kind { ExprKind::Lit(ref l) => match l.node { </del> <ins> match &e.kind { ExprKind::Lit(l) => match l.node { </ins> ast::LitKind::Int(x, _ty) => Some(x.to_string()), _ => None, },

} } <del> if let ExprKind::Index(ref seqexpr, ref idx) = expr.kind { let ty = cx.tables.expr_ty(seqexpr); if !is_slice_like(cx, ty) { return None; } let offset = match idx.kind { ExprKind::Binary(op, ref lhs, ref rhs) => match op.node { BinOpKind::Add => { let offset_opt = if same_var(cx, lhs, var) { extract_offset(cx, rhs, var) } else if same_var(cx, rhs, var) { extract_offset(cx, lhs, var) } else { None }; offset_opt.map(Offset::positive) }, BinOpKind::Sub if same_var(cx, lhs, var) => extract_offset(cx, rhs, var).map(Offset::negative), _ => None, }, ExprKind::Path(..) => { if same_var(cx, idx, var) { Some(Offset::positive("0".into())) </del> <ins> match idx.kind { ExprKind::Binary(op, lhs, rhs) => match op.node { BinOpKind::Add => { let offset_opt = if same_var(cx, lhs, var) { extract_offset(cx, rhs, var) } else if same_var(cx, rhs, var) { extract_offset(cx, lhs, var) </ins> } else { None <del> } </del> <ins> }; offset_opt.map(Offset::positive) </ins> }, <ins> BinOpKind::Sub if same_var(cx, lhs, var) => extract_offset(cx, rhs, var).map(Offset::negative), </ins> _ => None, <del> }; offset.map(|o| FixedOffsetVar { var_name: snippet_opt(cx, seqexpr.span).unwrap_or_else(|| "???".into()), offset: o, }) } else { None } } fn fetch_cloned_fixed_offset_var<'a, 'tcx>( cx: &LateContext<'a, 'tcx>, expr: &Expr<'_>, var: HirId, ) -> Option<FixedOffsetVar> { if_chain! { if let ExprKind::MethodCall(ref method, _, ref args) = expr.kind; if method.ident.name == sym!(clone); if args.len() == 1; if let Some(arg) = args.get(0); then { return get_fixed_offset_var(cx, arg, var); } </del> <ins> }, ExprKind::Path(..) if same_var(cx, idx, var) => Some(Offset::positive("0".into())), _ => None, </ins> } <del> get_fixed_offset_var(cx, expr, var) </del> } <del> fn get_indexed_assignments<'a, 'tcx>( cx: &LateContext<'a, 'tcx>, body: &Expr<'_>, var: HirId, ) -> Vec<(FixedOffsetVar, FixedOffsetVar)> { fn get_assignment<'a, 'tcx>( cx: &LateContext<'a, 'tcx>, e: &Expr<'_>, var: HirId, ) -> Option<(FixedOffsetVar, FixedOffsetVar)> { if let ExprKind::Assign(ref lhs, ref rhs, _) = e.kind { match ( get_fixed_offset_var(cx, lhs, var), fetch_cloned_fixed_offset_var(cx, rhs, var), ) { (Some(offset_left), Some(offset_right)) => { // Source and destination must be different if offset_left.var_name == offset_right.var_name { None } else { Some((offset_left, offset_right)) } }, _ => None, } </del> <ins> fn get_assignments<'a, 'tcx>( body: &'tcx Expr<'tcx>, ) -> impl Iterator<Item = Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>> { fn get_assignment<'a, 'tcx>(e: &'tcx Expr<'tcx>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> { if let ExprKind::Assign(lhs, rhs, _) = e.kind { Some((lhs, rhs)) </ins> } else { None } } <del> if let ExprKind::Block(ref b, _) = body.kind { let Block { ref stmts, ref expr, .. } = **b; </del> <ins> // This is one of few ways to return different iterators // derived from: https://stackoverflow.com/questions/29760668/conditionally-iterate-over-one-of-several-possible-iterators/52064434#52064434 let mut iter_a = None; let mut iter_b = None; if let ExprKind::Block(b, _) = body.kind { let Block { stmts, expr, .. } = *b; </ins> <del> stmts </del> <ins> iter_a = stmts </ins> .iter() <del> .map(|stmt| match stmt.kind { </del> <ins> .filter_map(|stmt| match stmt.kind { </ins> StmtKind::Local(..) | StmtKind::Item(..) => None, <del> StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => Some(get_assignment(cx, e, var)), </del> <ins> StmtKind::Expr(e) | StmtKind::Semi(e) => Some(e), </ins> }) <del> .chain(expr.as_ref().into_iter().map(|e| Some(get_assignment(cx, &*e, var)))) .filter_map(|op| op) .collect::<Option<Vec<_>>>() .unwrap_or_default() </del> <ins> .chain(expr.into_iter()) .map(get_assignment) .into() </ins> } else { <del> get_assignment(cx, body, var).into_iter().collect() </del> <ins> iter_b = Some(get_assignment(body)) </ins> } <ins> iter_a.into_iter().flatten().chain(iter_b.into_iter()) </ins> } /// Checks for for loops that sequentially copy items from one slice-like

) { if let Some(higher::Range { start: Some(start), <del> ref end, </del> <ins> end: Some(end), </ins> limits, }) = higher::range(cx, arg) { // the var must be a single name if let PatKind::Binding(_, canonical_id, _, _) = pat.kind { <del> let print_sum = |arg1: &Offset, arg2: &Offset| -> String { match (&arg1.value[..], arg1.negate, &arg2.value[..], arg2.negate) { ("0", _, "0", _) => "".into(), ("0", _, x, false) | (x, false, "0", false) => x.into(), ("0", _, x, true) | (x, false, "0", true) => format!("-{}", x), (x, false, y, false) => format!("({} + {})", x, y), (x, false, y, true) => { </del> <ins> fn print_sum(arg1: &str, arg2: &Offset) -> String { match (arg1, &arg2.value[..], arg2.sign) { ("0", "0", _) => "0".into(), ("0", x, OffsetSign::Positive) | (x, "0", _) => x.into(), ("0", x, OffsetSign::Negative) => format!("-{}", x), (x, y, OffsetSign::Positive) => format!("({} + {})", x, y), (x, y, OffsetSign::Negative) => { </ins> if x == y { "0".into() } else { format!("({} - {})", x, y) } }, <del> (x, true, y, false) => { if x == y { "0".into() } else { format!("({} - {})", y, x) } }, (x, true, y, true) => format!("-({} + {})", x, y), </del> } <del> }; </del> <ins> } </ins> <del> let print_limit = |end: &Option<&Expr<'_>>, offset: Offset, var_name: &str| { if let Some(end) = *end { if_chain! { if let ExprKind::MethodCall(ref method, _, ref len_args) = end.kind; if method.ident.name == sym!(len); if len_args.len() == 1; if let Some(arg) = len_args.get(0); if snippet(cx, arg.span, "??") == var_name; then { return if offset.negate { format!("({} - {})", snippet(cx, end.span, "<src>.len()"), offset.value) } else { String::new() }; } } </del> <ins> fn print_offset(start_str: &str, inline_offset: &Offset) -> String { let offset = print_sum(start_str, inline_offset); if offset.as_str() == "0" { "".into() } else { offset } } </ins> <del> let end_str = match limits { ast::RangeLimits::Closed => { let end = sugg::Sugg::hir(cx, end, "<count>"); format!("{}", end + sugg::ONE) }, ast::RangeLimits::HalfOpen => format!("{}", snippet(cx, end.span, "..")), }; </del> <ins> let print_limit = |end: &Expr<'_>, offset: Offset, var_name: &str| { if_chain! { if let ExprKind::MethodCall(method, _, len_args) = end.kind; if method.ident.name == sym!(len); if len_args.len() == 1; if let Some(arg) = len_args.get(0); if snippet(cx, arg.span, "??") == var_name; then { match offset.sign { OffsetSign::Negative => format!("({} - {})", snippet(cx, end.span, "<src>.len()"), offset.value), OffsetSign::Positive => "".into(), } } else { let end_str = match limits { ast::RangeLimits::Closed => { let end = sugg::Sugg::hir(cx, end, "<count>"); format!("{}", end + sugg::ONE) }, ast::RangeLimits::HalfOpen => format!("{}", snippet(cx, end.span, "..")), }; </ins> <del> print_sum(&Offset::positive(end_str), &offset) } else { "..".into() </del> <ins> print_sum(&end_str, &offset) } </ins> } }; // The only statements in the for loops can be indexed assignments from // indexed retrievals. <del> let manual_copies = get_indexed_assignments(cx, body, canonical_id); let big_sugg = manual_copies .into_iter() .map(|(dst_var, src_var)| { let start_str = Offset::positive(snippet(cx, start.span, "").to_string()); let dst_offset = print_sum(&start_str, &dst_var.offset); let dst_limit = print_limit(end, dst_var.offset, &dst_var.var_name); let src_offset = print_sum(&start_str, &src_var.offset); let src_limit = print_limit(end, src_var.offset, &src_var.var_name); let dst = if dst_offset == "" && dst_limit == "" { dst_var.var_name } else { format!("{}[{}..{}]", dst_var.var_name, dst_offset, dst_limit) }; </del> <ins> let big_sugg = get_assignments(body) .map(|o| { o.and_then(|(lhs, rhs)| { let rhs = fetch_cloned_expr(rhs); if_chain! { if let ExprKind::Index(seqexpr_left, idx_left) = lhs.kind; if let ExprKind::Index(seqexpr_right, idx_right) = rhs.kind; if is_slice_like(cx, cx.tables.expr_ty(seqexpr_left)) && is_slice_like(cx, cx.tables.expr_ty(seqexpr_right)); if let Some(offset_left) = get_offset(cx, &idx_left, canonical_id); if let Some(offset_right) = get_offset(cx, &idx_right, canonical_id); let var_name_left = snippet_opt(cx, seqexpr_left.span).unwrap_or_else(|| "???".into()); let var_name_right = snippet_opt(cx, seqexpr_right.span).unwrap_or_else(|| "???".into()); // Source and destination must be different if var_name_left != var_name_right; then { Some((FixedOffsetVar { var_name: var_name_left, offset: offset_left }, FixedOffsetVar { var_name: var_name_right, offset: offset_right })) } else { None } } }) }) .map(|o| { o.map(|(dst_var, src_var)| { let start_str = snippet(cx, start.span, "").to_string(); let dst_offset = print_offset(&start_str, &dst_var.offset); let dst_limit = print_limit(end, dst_var.offset, &dst_var.var_name); let src_offset = print_offset(&start_str, &src_var.offset); let src_limit = print_limit(end, src_var.offset, &src_var.var_name); let dst = if dst_offset == "" && dst_limit == "" { dst_var.var_name } else { format!("{}[{}..{}]", dst_var.var_name, dst_offset, dst_limit) }; </ins> <del> format!( "{}.clone_from_slice(&{}[{}..{}])", dst, src_var.var_name, src_offset, src_limit ) </del> <ins> format!( "{}.clone_from_slice(&{}[{}..{}])", dst, src_var.var_name, src_offset, src_limit ) }) </ins> }) <del> .join("n    "); </del> <ins> .collect::<Option<Vec<_>>>() .filter(|v| !v.is_empty()) .map(|v| v.join("n    ")); </ins> <del> if !big_sugg.is_empty() { </del> <ins> if let Some(big_sugg) = big_sugg { </ins> span_lint_and_sugg( cx, MANUAL_MEMCPY,

for i in from..from + 3 { dst[i] = src[i - from]; } <ins> // `RangeTo` `for` loop - don't trigger lint for i in 0.. { dst[i] = src[i]; } </ins> } #[warn(clippy::needless_range_loop, clippy::manual_memcpy)]

--> $DIR/manual_memcpy.rs:94:14 | LL |     for i in from..from + src.len() { <del> |              ^^^^^^^^^^^^^^^^^^^^^^ help: try replacing the loop by: `dst[from..from + src.len()].clone_from_slice(&src[0..(from + src.len() - from)])` </del> <ins> |              ^^^^^^^^^^^^^^^^^^^^^^ help: try replacing the loop by: `dst[from..from + src.len()].clone_from_slice(&src[..(from + src.len() - from)])` </ins> error: it looks like you're manually copying between slices --> $DIR/manual_memcpy.rs:98:14 | LL |     for i in from..from + 3 { <del> |              ^^^^^^^^^^^^^^ help: try replacing the loop by: `dst[from..from + 3].clone_from_slice(&src[0..(from + 3 - from)])` </del> <ins> |              ^^^^^^^^^^^^^^ help: try replacing the loop by: `dst[from..from + 3].clone_from_slice(&src[..(from + 3 - from)])` </ins> error: it looks like you're manually copying between slices <del> --> $DIR/manual_memcpy.rs:105:14 </del> <ins> --> $DIR/manual_memcpy.rs:110:14 </ins> | LL |     for i in 0..src.len() { |              ^^^^^^^^^^^^ help: try replacing the loop by: `dst[..src.len()].clone_from_slice(&src[..])`