use syntax::ast::*;
use syntax::token::{DelimToken, Token, TokenKind};
use syntax::source_map::{BytePos, Span};
use syntax::tokenstream::{TokenStream, TokenTree};
use crate::ast_manip::AstEquiv;
use crate::rewrite::base::{describe, rewrite_seq_comma_sep};
use crate::rewrite::strategy::print::PrintParse;
use crate::rewrite::{Rewrite, RewriteCtxtRef, TextRewrite};
fn span_empty(sp: Span) -> bool {
sp.lo() == sp.hi()
}
fn find_fn_header_arg_list(ts: TokenStream, generics_span: Span) -> Option<(TokenStream, Span)> {
ts.trees()
.filter_map(|tt| match tt {
TokenTree::Delimited(sp, delim, tts) => {
if delim == DelimToken::Paren && sp.open.lo() >= generics_span.hi() {
Some((tts, sp.open.between(sp.close)))
} else {
None
}
}
_ => None,
})
.next()
}
fn record_qualifier_rewrite(old_span: Span, new_span: Span, mut rcx: RewriteCtxtRef) {
let src_span = if span_empty(old_span) && !span_empty(new_span) {
new_span.with_hi(new_span.hi() + BytePos(1))
} else {
new_span
};
if span_empty(old_span) {
info!("INSERT (QUAL) {}", describe(rcx.session(), old_span));
info!(" AT (QUAL) {}", describe(rcx.session(), src_span));
} else if span_empty(new_span) {
info!("DELETE (QUAL) {}", describe(rcx.session(), old_span));
} else {
info!("REWRITE (QUAL) {}", describe(rcx.session(), old_span));
info!(" INTO (QUAL) {}", describe(rcx.session(), src_span));
}
rcx.record(TextRewrite::new(old_span, src_span));
}
fn rewrite_arg_list_with_tokens(
old: &[Param],
new: &[Param],
args_tokens: TokenStream,
args_span: Span,
rcx: RewriteCtxtRef,
) -> bool {
let mut comma_spans = Vec::with_capacity(old.len());
let mut tt_iter = args_tokens.into_trees();
for (i, old_arg) in old.iter().enumerate() {
assert!(old_arg.ty.span.hi() > old_arg.pat.span.hi()); let end_pos = old_arg.ty.span.hi();
let is_last = i == old.len() - 1;
let mut past_arg = false;
loop {
match tt_iter.next() {
Some(tt) => {
if !past_arg && tt.span().lo() >= end_pos {
past_arg = true;
}
if past_arg && matches!([tt] TokenTree::Token(Token {
kind: TokenKind::Comma,
..
})) {
comma_spans.push(tt.span());
break;
}
}
None => {
if is_last {
break;
} else {
panic!("not enough commas in arg list token stream");
}
}
}
}
}
assert!(comma_spans.len() == old.len() || comma_spans.len() + 1 == old.len());
let mut spans_with_commas = Vec::with_capacity(old.len());
for (i, arg) in old.iter().enumerate() {
let arg_span = arg.pat.span.to(arg.ty.span);
if let Some(&comma_span) = comma_spans.get(i) {
spans_with_commas.push(arg_span.to(comma_span));
} else {
spans_with_commas.push(arg_span);
}
}
let has_trailing_comma = comma_spans.len() == old.len();
rewrite_seq_comma_sep(
old,
new,
&spans_with_commas,
args_span,
has_trailing_comma,
rcx,
)
}
pub fn rewrite(old: &Item, new: &Item, mut rcx: RewriteCtxtRef) -> bool {
let &Item {
ident: ref ident1,
attrs: ref attrs1,
id: ref id1,
kind: ref kind1,
vis: ref vis1,
span: ref span1,
tokens: ref tokens1,
} = old;
let &Item {
ident: ref ident2,
attrs: ref attrs2,
id: ref id2,
kind: ref kind2,
vis: ref vis2,
span: ref span2,
tokens: ref _tokens2,
} = new;
if tokens1.is_none() {
return false;
}
match (kind1, kind2) {
(
&ItemKind::Fn(ref sig1, ref generics1, ref block1),
&ItemKind::Fn(ref sig2, ref generics2, ref block2),
) => {
let (old_args_tokens, old_args_span) =
find_fn_header_arg_list(tokens1.as_ref().unwrap().clone(), generics1.span)
.expect("failed to find arg list in item tokens");
let ok =
Rewrite::rewrite(attrs1, attrs2, rcx.borrow()) &&
Rewrite::rewrite(id1, id2, rcx.borrow()) &&
Rewrite::rewrite(span1, span2, rcx.borrow()) &&
Rewrite::rewrite(&sig1.header.unsafety, &sig2.header.unsafety, rcx.borrow()) &&
Rewrite::rewrite(&sig1.header.ext, &sig2.header.ext, rcx.borrow()) &&
Rewrite::rewrite(generics1, generics2, rcx.borrow()) &&
Rewrite::rewrite(block1, block2, rcx.borrow()) &&
rewrite_arg_list_with_tokens(
&sig1.decl.inputs, &sig2.decl.inputs, old_args_tokens, old_args_span, rcx.borrow()) &&
Rewrite::rewrite(&sig1.decl.output, &sig2.decl.output, rcx.borrow()) &&
true;
if !ok {
return false;
}
let src2: String = <Item as PrintParse>::to_string(new);
let reparsed = Item::parse(rcx.session(), &src2);
unpack!([&reparsed.kind] ItemKind::Fn(reparsed_sig, _generics, _block));
if !vis1.node.ast_equiv(&vis2.node) {
record_qualifier_rewrite(vis1.span, reparsed.vis.span, rcx.borrow());
}
if sig1.header.constness.node != sig2.header.constness.node {
record_qualifier_rewrite(sig1.header.constness.span, reparsed_sig.header.constness.span, rcx.borrow());
}
if ident1 != ident2 {
record_qualifier_rewrite(ident1.span, reparsed.ident.span, rcx.borrow());
}
true
}
(_, _) => {
let ok = Rewrite::rewrite(attrs1, attrs2, rcx.borrow())
&& Rewrite::rewrite(id1, id2, rcx.borrow())
&& Rewrite::rewrite(kind1, kind2, rcx.borrow())
&& Rewrite::rewrite(span1, span2, rcx.borrow())
&& true;
if !ok {
return false;
}
let src2: String = <Item as PrintParse>::to_string(new);
let reparsed = Item::parse(rcx.session(), &src2);
if !vis1.node.ast_equiv(&vis2.node) {
record_qualifier_rewrite(vis1.span, reparsed.vis.span, rcx.borrow());
}
if ident1 != ident2 {
record_qualifier_rewrite(ident1.span, reparsed.ident.span, rcx.borrow());
}
true
}
}
}