use smallvec::SmallVec;
use std::mem;
use syntax::ast::*;
use syntax::mut_visit::{self, MutVisitor};
use syntax::ptr::P;
use syntax::source_map::{Span, DUMMY_SP};
use crate::ast_manip::util::extend_span_attrs;
use crate::ast_manip::MutVisit;
struct FixFormat {
ctxt: FormatCtxt,
}
#[derive(Clone)]
struct FormatCtxt {
parent_span: Span,
in_format: bool,
in_match: bool,
}
impl FormatCtxt {
fn new(span: Span) -> Self {
FormatCtxt {
parent_span: span,
in_format: false,
in_match: false,
}
}
fn enter_span(&self, span: Span) -> Self {
FormatCtxt {
parent_span: span,
..*self
}
}
fn enter_format(&self, span: Span) -> Self {
FormatCtxt {
parent_span: span,
in_format: true,
..*self
}
}
fn enter_match(&self, span: Span) -> Self {
FormatCtxt {
parent_span: span,
in_match: true,
..*self
}
}
}
impl FixFormat {
fn descend<F, R>(&mut self, new_ctxt: FormatCtxt, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
let old_ctxt = mem::replace(&mut self.ctxt, new_ctxt);
let r = f(self);
self.ctxt = old_ctxt;
r
}
fn is_format_entry(&self, e: &Expr) -> bool {
if !e.span.from_expansion() {
return false;
}
if let ExprKind::Call(callee, _) = &e.kind {
if let ExprKind::Path(None, path) = &callee.kind {
let matches_fmt_args = path.segments.len() == 4 &&
path.segments[1].ident.as_str() == "fmt" &&
path.segments[2].ident.as_str() == "Arguments" &&
(path.segments[3].ident.as_str() == "new_v1" ||
path.segments[3].ident.as_str() == "new_v1_formatted");
return matches_fmt_args;
}
}
false
}
}
impl MutVisitor for FixFormat {
fn visit_expr(&mut self, e: &mut P<Expr>) {
if !e.span.from_expansion()
&& self.ctxt.in_match
&& matches!([e.kind] ExprKind::AddrOf(..))
{
trace!("EXITING format! at {:?}", e);
let mac_span = self.ctxt.parent_span;
let leave_ctxt = FormatCtxt::new(e.span);
self.descend(leave_ctxt, |this| {
mut_visit::noop_visit_expr(e, this);
e.span = mac_span;
})
} else if !e.span.from_expansion()
&& self.ctxt.in_format
&& !self.ctxt.in_match
{
trace!("Fixing format! string at {:?}", e);
let mac_span = self.ctxt.parent_span;
let new_ctxt = self.ctxt.enter_span(mac_span);
self.descend(new_ctxt, |this| {
mut_visit::noop_visit_expr(e, this);
e.span = mac_span;
})
} else if self.ctxt.in_format && matches!([e.kind] ExprKind::Match(..)) {
let new_ctxt = self.ctxt.enter_match(e.span);
self.descend(new_ctxt, |this| mut_visit::noop_visit_expr(e, this))
} else if !self.ctxt.in_format && self.is_format_entry(&e) {
trace!("ENTERING format! at {:?}", e);
let new_ctxt = self.ctxt.enter_format(e.span);
self.descend(new_ctxt, |this| mut_visit::noop_visit_expr(e, this))
} else {
let new_ctxt = self.ctxt.enter_span(e.span);
self.descend(new_ctxt, |this| mut_visit::noop_visit_expr(e, this))
}
}
fn visit_mac(&mut self, mac: &mut Mac) {
mut_visit::noop_visit_mac(mac, self)
}
}
struct FixAttrs;
impl MutVisitor for FixAttrs {
fn flat_map_item(&mut self, i: P<Item>) -> SmallVec<[P<Item>; 1]> {
let new_span = extend_span_attrs(i.span, &i.attrs);
let i = if new_span != i.span {
i.map(|i| Item {
span: new_span,
..i
})
} else {
i
};
mut_visit::noop_flat_map_item(i, self)
}
fn flat_map_foreign_item(&mut self, fi: ForeignItem) -> SmallVec<[ForeignItem; 1]> {
let new_span = extend_span_attrs(fi.span, &fi.attrs);
let fi = if new_span != fi.span {
ForeignItem {
span: new_span,
..fi
}
} else {
fi
};
mut_visit::noop_flat_map_foreign_item(fi, self)
}
fn visit_mac(&mut self, mac: &mut Mac) {
mut_visit::noop_visit_mac(mac, self)
}
}
#[cfg_attr(feature = "profile", flame)]
pub fn fix_format<T: MutVisit>(node: &mut T) {
let mut fix_format = FixFormat {
ctxt: FormatCtxt::new(DUMMY_SP),
};
node.visit(&mut fix_format)
}
#[cfg_attr(feature = "profile", flame)]
pub fn fix_attr_spans<T: MutVisit>(node: &mut T) {
node.visit(&mut FixAttrs)
}