<commit_before>extern crate rand;
extern crate timely;
extern crate differential_dataflow;

use std::time::Instant;

use rand::{Rng, SeedableRng, StdRng};

use timely::dataflow::*;

use differential_dataflow::input::Input;
use differential_dataflow::Collection;
use differential_dataflow::operators::*;
use differential_dataflow::operators::arrange::ArrangeByKey;
use differential_dataflow::lattice::Lattice;

use differential_dataflow::trace::implementations::ord::OrdValSpine as DefaultValTrace;
use differential_dataflow::operators::arrange::TraceAgent;
use differential_dataflow::operators::arrange::Arranged;

type Arrange<G, K, V, R> = Arranged<G, K, V, R, TraceAgent<K, V, <G as ScopeParent>::Timestamp, R, DefaultValTrace<K, V, <G as ScopeParent>::Timestamp, R>>>;

type Node = u32;

fn main() {

    // snag a filename to use for the input graph.
    let filename = std::env::args().nth(1).unwrap();
    let rounds: usize = std::env::args().nth(2).unwrap().parse().unwrap();
    let batch: usize = std::env::args().nth(3).unwrap().parse().unwrap();
    let inspect = std::env::args().any(|x| x == "inspect");

    timely::execute_from_args(std::env::args().skip(1), move |worker| {

        let peers = worker.peers();
        let index = worker.index();

        let (mut input, mut query1, mut query2, mut query3, probe) = worker.dataflow(|scope| {

            let (input, graph) = scope.new_collection();

            let (query1_input, query1) = scope.new_collection();
            let (query2_input, query2) = scope.new_collection();
            let (query3_input, query3) = scope.new_collection();

            // each edge should exist in both directions.
            let graph = graph.arrange_by_key();

            let probe =
            interactive(&graph, query1, query2, query3)
                .filter(move |_| inspect)
                // .map(|_| ())
                .consolidate()
                .inspect(|x| println!("count: {:?}", x))
                .probe();

            (input, query1_input, query2_input, query3_input, probe)
        });

        let timer = Instant::now();

        let mut nodes = Vec::new();

        use std::io::{BufReader, BufRead};
        use std::fs::File;

        let file = BufReader::new(File::open(filename.clone()).unwrap());
        for (count, readline) in file.lines().enumerate() {
            let line = readline.ok().expect("read error");
            if count % peers == index && !line.starts_with('#') {
                let mut elts = line[..].split_whitespace();
                let src: u32 = elts.next().unwrap().parse().ok().expect("malformed src");
                let dst: u32 = elts.next().unwrap().parse().ok().expect("malformed dst");
                nodes.push(src);
                nodes.push(dst);
                input.insert((src, dst));
            }
        }

        nodes.sort();
        nodes.dedup();

        if index == 0 { println!("{:?}\tData ingested", timer.elapsed()); }

        // run until graph is loaded
        input.advance_to(1); input.flush();
        query1.advance_to(1); query1.flush();
        query2.advance_to(1); query2.flush();
        query3.advance_to(1); query3.flush();

        worker.step_while(|| probe.less_than(input.time()));

        if index == 0 { println!("{:?}\tData indexed", timer.elapsed()); }

        let seed: &[_] = &[1, 2, 3, index];
        let mut rng: StdRng = SeedableRng::from_seed(seed);    // rng for edge additions

        let worker_batch = batch / peers + if index < batch % peers { 1 } else { 0 };

        input.advance_to(1 + 1 * rounds); input.flush();
        query2.advance_to(1 + 1 * rounds); query2.flush();
        query3.advance_to(1 + 1 * rounds); query3.flush();

        // let mut latencies1 = Vec::with_capacity(rounds);

        for round in 1 .. (1 + 1 * rounds) {
            let timer = Instant::now();
            for _ in 0 .. worker_batch {
                query1.insert(*rng.choose(&nodes[..]).unwrap());
            }
            query1.advance_to(round);
            query1.flush();
            while probe.less_than(query1.time()) { worker.step(); }
            if index == 0 { println!("query1: {:?}", timer.elapsed()); }
        }

        if index == 0 { println!("{:?}\tRound 1 complete", timer.elapsed()); }

        input.advance_to(1 + 2 * rounds); input.flush();
        query1.advance_to(1 + 2 * rounds); query1.flush();
        query3.advance_to(1 + 2 * rounds); query3.flush();

        // let mut latencies2 = Vec::with_capacity(rounds);

        for round in (1 + 1 * rounds) .. (1 + 2 * rounds) {
            let timer = Instant::now();
            for _ in 0 .. worker_batch {
                query2.insert(*rng.choose(&nodes[..]).unwrap());
            }
            query2.advance_to(round);
            query2.flush();
            while probe.less_than(query2.time()) { worker.step(); }
            if index == 0 { println!("query2: {:?}", timer.elapsed()); }
        }

        if index == 0 { println!("{:?}\tRound 2 complete", timer.elapsed()); }

        input.advance_to(1 + 3 * rounds); input.flush();
        query1.advance_to(1 + 3 * rounds); query1.flush();
        query2.advance_to(1 + 3 * rounds); query2.flush();

        // let mut latencies3 = Vec::with_capacity(rounds);

        for round in (1 + 2 * rounds) .. (1 + 3 * rounds) {
            let timer = Instant::now();
            for _ in 0 .. worker_batch {
                query3.insert(*rng.choose(&nodes[..]).unwrap());
            }
            query3.advance_to(round);
            query3.flush();
            while probe.less_than(query3.time()) { worker.step(); }
            if index == 0 { println!("query3: {:?}", timer.elapsed()); }
        }

        if index == 0 { println!("{:?}\tRound 3 complete", timer.elapsed()); }

        query1.close();
        query2.close();
        query3.close();

        // let mut latencies4 = Vec::with_capacity(rounds);

        for round in (1 + 3 * rounds) .. (1 + 4 * rounds) {
            let timer = Instant::now();
            for _ in 0 .. worker_batch {
                input.insert((*rng.choose(&nodes[..]).unwrap(), *rng.choose(&nodes[..]).unwrap()));
            }
            input.advance_to(round);
            input.flush();
            while probe.less_than(input.time()) { worker.step(); }
            if index == 0 { println!("query4: {:?}", timer.elapsed()); }
        }

    }).unwrap();
}

fn interactive<G: Scope>(
    edges: &Arrange<G, Node, Node, isize>,
    tc_1: Collection<G, Node>,
    tc_2: Collection<G, Node>,
    sg_x: Collection<G, Node>
) -> Collection<G, Node>
where G::Timestamp: Lattice{

    // descendants of tc_1:
    let query1 =
    tc_1.map(|x| (x,x))
        .iterate(|inner|
            edges
                .enter(&inner.scope())
                .join_map(&inner, |_,&y,&q| (y,q))
                .concat(&tc_1.enter(&inner.scope()).map(|x| (x,x)))
                .distinct()
        )
        .map(|(x,q)| (q,x));

    // ancestors of tc_2:
    let query2 =
    tc_2.map(|x| (x,x))
        .iterate(|inner|
            edges
                .as_collection(|&k,&v| (v,k))
                .enter(&inner.scope())
                .join_map(&inner, |_,&y,&q| (y,q))
                .concat(&tc_2.enter(&inner.scope()).map(|x| (x,x)))
                .distinct()
        )
        .map(|(x,q)| (q,x));

    // Adapted from: http://ranger.uta.edu/~fegaras/cse6331/spring97/p25.html
    // sg(X,X) <- magic(X).
    // sg(X,Y) <- magic(X), par(X,Xp), par(Y,Yp), sg(Xp,Yp).

    // ancestors of sg_x:
    let magic =
    sg_x.iterate(|inner|
            edges
                .as_collection(|&k,&v| (v,k))
                .enter(&inner.scope())
                .semijoin(&inner)
                .map(|(_x,y)| y)
                .concat(&sg_x.enter(&inner.scope()))
                .distinct()
        );

    let magic_edges =
    edges
        .semijoin(&magic)
        .map(|(x,y)|(y,x))
        .semijoin(&magic)
        .map(|(x,y)|(y,x));

    let query3 =
    magic
        .map(|x| (x,x))   // for query q, sg(x,x)
        .iterate(|inner| {

            let edges = edges.enter(&inner.scope());
            let magic = magic.enter(&inner.scope());
            let magic_edges = magic_edges.enter(&inner.scope());

            let result =
            inner
                .join_map(&magic_edges, |_x,&y,&cx| (y,cx))
                .join_core(&edges, |_y,&cx,&cy| Some((cx,cy)))
                .concat(&magic.map(|x| (x,x)))
                .distinct();

            // result.map(|_| ()).consolidate().inspect(|x| println!("\t{:?}", x));
            result
        })
        .semijoin(&sg_x);

    query1.concat(&query2).concat(&query3).map(|(q,_)| q)
}<commit_msg>updated interactive<commit_after>#![feature(duration_as_u128)]

extern crate rand;
extern crate timely;
extern crate differential_dataflow;

use std::time::Instant;

use rand::{Rng, SeedableRng, StdRng};

use timely::dataflow::*;

use differential_dataflow::input::Input;
use differential_dataflow::Collection;
use differential_dataflow::operators::*;
use differential_dataflow::operators::arrange::ArrangeByKey;
use differential_dataflow::lattice::Lattice;

use differential_dataflow::trace::implementations::ord::OrdValSpine as DefaultValTrace;
use differential_dataflow::operators::arrange::TraceAgent;
use differential_dataflow::operators::arrange::Arranged;

type Arrange<G, K, V, R> = Arranged<G, K, V, R, TraceAgent<K, V, <G as ScopeParent>::Timestamp, R, DefaultValTrace<K, V, <G as ScopeParent>::Timestamp, R>>>;

type Node = u32;

fn main() {

    // snag a filename to use for the input graph.
    let filename = std::env::args().nth(1).unwrap();
    let rounds: usize = std::env::args().nth(2).unwrap().parse().unwrap();
    let batch: usize = std::env::args().nth(3).unwrap().parse().unwrap();
    let workers: usize = std::env::args().nth(4).unwrap().parse().unwrap();
    let inspect = std::env::args().any(|x| x == "inspect");

    use timely::communication::allocator::zero_copy::allocator_process::ProcessBuilder;
    let allocators = ProcessBuilder::new_vector(workers);
    timely::execute::execute_from(allocators, Box::new(()), move |worker| {

    // timely::execute_from_args(std::env::args().skip(1), move |worker| {

        let peers = worker.peers();
        let index = worker.index();

        let (mut input, mut query1, mut query2, mut query3, probe) = worker.dataflow(|scope| {

            let (input, graph) = scope.new_collection();

            let (query1_input, query1) = scope.new_collection();
            let (query2_input, query2) = scope.new_collection();
            let (query3_input, query3) = scope.new_collection();

            // each edge should exist in both directions.
            let graph = graph.arrange_by_key();

            let probe =
            interactive(&graph, query1, query2, query3)
                .filter(move |_| inspect)
                // .map(|_| ())
                .consolidate()
                .inspect(|x| println!("count: {:?}", x))
                .probe();

            (input, query1_input, query2_input, query3_input, probe)
        });

        let timer = Instant::now();

        let mut nodes = Vec::new();

        use std::io::{BufReader, BufRead};
        use std::fs::File;

        let file = BufReader::new(File::open(filename.clone()).unwrap());
        for (count, readline) in file.lines().enumerate() {
            let line = readline.ok().expect("read error");
            if count % peers == index && !line.starts_with('#') {
                let mut elts = line[..].split_whitespace();
                let src: u32 = elts.next().unwrap().parse().ok().expect("malformed src");
                let dst: u32 = elts.next().unwrap().parse().ok().expect("malformed dst");
                nodes.push(src);
                nodes.push(dst);
                input.insert((src, dst));
            }
        }

        nodes.sort();
        nodes.dedup();

        if index == 0 {

            println!("{:?}\tData ingested", timer.elapsed());

            let seed: &[_] = &[1, 2, 3, index];
            let mut rng1: StdRng = SeedableRng::from_seed(seed);   // rng for additions
            let mut rng2: StdRng = SeedableRng::from_seed(seed);   // rng for deletions
            let mut rng3: StdRng = SeedableRng::from_seed(seed);   // rng for additions
            let mut rng4: StdRng = SeedableRng::from_seed(seed);   // rng for deletions
            let mut rng5: StdRng = SeedableRng::from_seed(seed);   // rng for additions
            let mut rng6: StdRng = SeedableRng::from_seed(seed);   // rng for deletions

            // Pre-insert first queries, so we can remove them.
            let insert = *rng1.choose(&nodes[..]).unwrap();
            // println!("initializing {}", insert);
            query1.insert(insert);
            query2.insert(*rng3.choose(&nodes[..]).unwrap());
            query3.insert(*rng5.choose(&nodes[..]).unwrap());

            // run until graph is loaded
            input.advance_to(1); input.flush();
            query1.advance_to(1); query1.flush();
            query2.advance_to(1); query2.flush();
            query3.advance_to(1); query3.flush();

            worker.step_while(|| probe.less_than(input.time()));

            println!("{:?}\tData indexed", timer.elapsed());

            let worker_batch = batch;

            input.advance_to(1 + 1 * rounds); input.flush();
            query2.advance_to(1 + 1 * rounds); query2.flush();
            query3.advance_to(1 + 1 * rounds); query3.flush();

            let mut latencies1 = Vec::with_capacity(rounds);

            for round in 1 .. (1 + 1 * rounds) {
                let timer = Instant::now();
                for _ in 0 .. worker_batch {
                    let insert = *rng1.choose(&nodes[..]).unwrap();
                    let remove = *rng2.choose(&nodes[..]).unwrap();
                    // println!("replacing {} -> {}", remove, insert);
                    query1.insert(insert);
                    query1.remove(remove);
                }
                query1.advance_to(round);
                query1.flush();
                while probe.less_than(query1.time()) { worker.step(); }
                latencies1.push(timer.elapsed().as_nanos());
            }

            if index == 0 { println!("{:?}\tRound 1 complete", timer.elapsed()); }

            input.advance_to(1 + 2 * rounds); input.flush();
            query1.advance_to(1 + 2 * rounds); query1.flush();
            query3.advance_to(1 + 2 * rounds); query3.flush();

            let mut latencies2 = Vec::with_capacity(rounds);

            for round in (1 + 1 * rounds) .. (1 + 2 * rounds) {
                let timer = Instant::now();
                for _ in 0 .. worker_batch {
                    query2.insert(*rng3.choose(&nodes[..]).unwrap());
                    query2.remove(*rng4.choose(&nodes[..]).unwrap());
                }
                query2.advance_to(round);
                query2.flush();
                while probe.less_than(query2.time()) { worker.step(); }
                latencies2.push(timer.elapsed().as_nanos());
                // if index == 0 { println!("query2: {:?}", timer.elapsed()); }
            }

            if index == 0 { println!("{:?}\tRound 2 complete", timer.elapsed()); }

            input.advance_to(1 + 3 * rounds); input.flush();
            query1.advance_to(1 + 3 * rounds); query1.flush();
            query2.advance_to(1 + 3 * rounds); query2.flush();

            let mut latencies3 = Vec::with_capacity(rounds);

            for round in (1 + 2 * rounds) .. (1 + 3 * rounds) {
                let timer = Instant::now();
                for _ in 0 .. worker_batch {
                    query3.insert(*rng5.choose(&nodes[..]).unwrap());
                    query3.remove(*rng6.choose(&nodes[..]).unwrap());
                }
                query3.advance_to(round);
                query3.flush();
                while probe.less_than(query3.time()) { worker.step(); }
                latencies3.push(timer.elapsed().as_nanos());
                // if index == 0 { println!("query3: {:?}", timer.elapsed()); }
            }

            if index == 0 { println!("{:?}\tRound 3 complete", timer.elapsed()); }

            // query1.close();
            // query2.close();
            // query3.close();

            // // let mut latencies4 = Vec::with_capacity(rounds);

            // for round in (1 + 3 * rounds) .. (1 + 4 * rounds) {
            //     let timer = Instant::now();
            //     for _ in 0 .. worker_batch {
            //         input.insert((*rng.choose(&nodes[..]).unwrap(), *rng.choose(&nodes[..]).unwrap()));
            //     }
            //     input.advance_to(round);
            //     input.flush();
            //     while probe.less_than(input.time()) { worker.step(); }
            //     if index == 0 { println!("query4: {:?}", timer.elapsed()); }
            // }

            latencies1.sort();
            for x in latencies1 { println!("q1:\t{:?}", x); }
            latencies2.sort();
            for x in latencies2 { println!("q2:\t{:?}", x); }
            latencies3.sort();
            for x in latencies3 { println!("q3:\t{:?}", x); }
        }

    }).unwrap();
}

fn interactive<G: Scope>(
    edges: &Arrange<G, Node, Node, isize>,
    tc_1: Collection<G, Node>,
    tc_2: Collection<G, Node>,
    sg_x: Collection<G, Node>
) -> Collection<G, Node>
where G::Timestamp: Lattice{

    // descendants of tc_1:
    let query1 =
    tc_1.map(|x| (x,x))
        .iterate(|inner|
            edges
                .enter(&inner.scope())
                .join_map(&inner, |_,&y,&q| (y,q))
                .concat(&tc_1.enter(&inner.scope()).map(|x| (x,x)))
                .distinct()
        )
        .map(|(x,q)| (q,x));

    // ancestors of tc_2:
    let query2 =
    tc_2.map(|x| (x,x))
        .iterate(|inner|
            edges
                .as_collection(|&k,&v| (v,k))
                .enter(&inner.scope())
                .join_map(&inner, |_,&y,&q| (y,q))
                .concat(&tc_2.enter(&inner.scope()).map(|x| (x,x)))
                .distinct()
        )
        .map(|(x,q)| (q,x));

    // Adapted from: http://ranger.uta.edu/~fegaras/cse6331/spring97/p25.html
    // sg(X,X) <- magic(X).
    // sg(X,Y) <- magic(X), par(X,Xp), par(Y,Yp), sg(Xp,Yp).

    // ancestors of sg_x:
    let magic =
    sg_x.iterate(|inner|
            edges
                .as_collection(|&k,&v| (v,k))
                .enter(&inner.scope())
                .semijoin(&inner)
                .map(|(_x,y)| y)
                .concat(&sg_x.enter(&inner.scope()))
                .distinct()
        );

    let magic_edges =
    edges
        .semijoin(&magic)
        .map(|(x,y)|(y,x))
        .semijoin(&magic)
        .map(|(x,y)|(y,x));

    let query3 =
    magic
        .map(|x| (x,x))   // for query q, sg(x,x)
        .iterate(|inner| {

            let edges = edges.enter(&inner.scope());
            let magic = magic.enter(&inner.scope());
            let magic_edges = magic_edges.enter(&inner.scope());

            let result =
            inner
                .join_map(&magic_edges, |_x,&y,&cx| (y,cx))
                .join_core(&edges, |_y,&cx,&cy| Some((cx,cy)))
                .concat(&magic.map(|x| (x,x)))
                .distinct();

            // result.map(|_| ()).consolidate().inspect(|x| println!("\t{:?}", x));
            result
        })
        .semijoin(&sg_x);

    query1.concat(&query2).concat(&query3).map(|(q,_)| q)
}<|endoftext|>


<commit_before>use hir::{self, ModPath};
use ra_syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SmolStr, SyntaxNode};

use crate::{
    utils::{find_insert_use_container, insert_use_statement},
    AssistContext, AssistId, Assists,
};
use either::Either;

// Assist: replace_qualified_name_with_use
//
// Adds a use statement for a given fully-qualified name.
//
// ```
// fn process(map: std::collections::<|>HashMap<String, String>) {}
// ```
// ->
// ```
// use std::collections::HashMap;
//
// fn process(map: HashMap<String, String>) {}
// ```
pub(crate) fn replace_qualified_name_with_use(
    acc: &mut Assists,
    ctx: &AssistContext,
) -> Option<()> {
    let path: ast::Path = ctx.find_node_at_offset()?;
    // We don't want to mess with use statements
    if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() {
        return None;
    }

    let hir_path = ctx.sema.lower_path(&path)?;
    let segments = collect_hir_path_segments(&hir_path)?;
    if segments.len() < 2 {
        return None;
    }

    let target = path.syntax().text_range();
    acc.add(
        AssistId("replace_qualified_name_with_use"),
        "Replace qualified path with use",
        target,
        |builder| {
            let path_to_import = hir_path.mod_path().clone();
            let container = match find_insert_use_container(path.syntax(), ctx) {
                Some(c) => c,
                None => return,
            };
            insert_use_statement(path.syntax(), &path_to_import, ctx, builder.text_edit_builder());

            // Now that we've brought the name into scope, re-qualify all paths that could be
            // affected (that is, all paths inside the node we added the `use` to).
            let hir_path = match hir::Path::from_ast(path.clone()) {
                Some(p) => p,
                None => return,
            };
            let mut rewriter = SyntaxRewriter::default();
            match container {
                Either::Left(l) => {
                    shorten_paths(&mut rewriter, l.syntax().clone(), hir_path.mod_path());
                }
                Either::Right(r) => {
                    shorten_paths(&mut rewriter, r.syntax().clone(), hir_path.mod_path());
                }
            }
            builder.rewrite(rewriter);
        },
    )
}

fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> {
    let mut ps = Vec::<SmolStr>::with_capacity(10);
    match path.kind() {
        hir::PathKind::Abs => ps.push("".into()),
        hir::PathKind::Crate => ps.push("crate".into()),
        hir::PathKind::Plain => {}
        hir::PathKind::Super(0) => ps.push("self".into()),
        hir::PathKind::Super(lvl) => {
            let mut chain = "super".to_string();
            for _ in 0..*lvl {
                chain += "::super";
            }
            ps.push(chain.into());
        }
        hir::PathKind::DollarCrate(_) => return None,
    }
    ps.extend(path.segments().iter().map(|it| it.name.to_string().into()));
    Some(ps)
}

/// Adds replacements to `re` that shorten `path` in all descendants of `node`.
fn shorten_paths(re: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: &ModPath) {
    for child in node.children() {
        match_ast! {
            match child {
                // Don't modify `use` items, as this can break the `use` item when injecting a new
                // import into the use tree.
                ast::UseItem(_it) => continue,
                // Don't descend into submodules, they don't have the same `use` items in scope.
                ast::Module(_it) => continue,

                ast::Path(p) => {
                    match maybe_replace_path(re, &p, path) {
                        Some(()) => {},
                        None => shorten_paths(re, p.syntax().clone(), path),
                    }
                },
                _ => shorten_paths(re, child, path),
            }
        }
    }
}

fn maybe_replace_path(
    re: &mut SyntaxRewriter<'static>,
    p: &ast::Path,
    path: &ModPath,
) -> Option<()> {
    let hir_path = hir::Path::from_ast(p.clone())?;

    if hir_path.mod_path() != path {
        return None;
    }

    // Replace path with its last "plain" segment.
    let mut mod_path = hir_path.mod_path().clone();
    let last = mod_path.segments.len() - 1;
    mod_path.segments.swap(0, last);
    mod_path.segments.truncate(1);
    mod_path.kind = hir::PathKind::Plain;

    let mut new_path = crate::ast_transform::path_to_ast(mod_path);

    let type_args = p.segment().and_then(|s| s.type_arg_list());
    if let Some(type_args) = type_args {
        let last_segment = new_path.segment().unwrap();
        new_path = new_path.with_segment(last_segment.with_type_args(type_args));
    }

    re.replace(p.syntax(), new_path.syntax());
    Some(())
}

#[cfg(test)]
mod tests {
    use crate::tests::{check_assist, check_assist_not_applicable};

    use super::*;

    #[test]
    fn test_replace_add_use_no_anchor() {
        check_assist(
            replace_qualified_name_with_use,
            "
std::fmt::Debug<|>
    ",
            "
use std::fmt::Debug;

Debug
    ",
        );
    }
    #[test]
    fn test_replace_add_use_no_anchor_with_item_below() {
        check_assist(
            replace_qualified_name_with_use,
            "
std::fmt::Debug<|>

fn main() {
}
    ",
            "
use std::fmt::Debug;

Debug

fn main() {
}
    ",
        );
    }

    #[test]
    fn test_replace_add_use_no_anchor_with_item_above() {
        check_assist(
            replace_qualified_name_with_use,
            "
fn main() {
}

std::fmt::Debug<|>
    ",
            "
use std::fmt::Debug;

fn main() {
}

Debug
    ",
        );
    }

    #[test]
    fn test_replace_add_use_no_anchor_2seg() {
        check_assist(
            replace_qualified_name_with_use,
            "
std::fmt<|>::Debug
    ",
            "
use std::fmt;

fmt::Debug
    ",
        );
    }

    #[test]
    fn test_replace_add_use() {
        check_assist(
            replace_qualified_name_with_use,
            "
use stdx;

impl std::fmt::Debug<|> for Foo {
}
    ",
            "
use stdx;
use std::fmt::Debug;

impl Debug for Foo {
}
    ",
        );
    }

    #[test]
    fn test_replace_file_use_other_anchor() {
        check_assist(
            replace_qualified_name_with_use,
            "
impl std::fmt::Debug<|> for Foo {
}
    ",
            "
use std::fmt::Debug;

impl Debug for Foo {
}
    ",
        );
    }

    #[test]
    fn test_replace_add_use_other_anchor_indent() {
        check_assist(
            replace_qualified_name_with_use,
            "
    impl std::fmt::Debug<|> for Foo {
    }
    ",
            "
    use std::fmt::Debug;

    impl Debug for Foo {
    }
    ",
        );
    }

    #[test]
    fn test_replace_split_different() {
        check_assist(
            replace_qualified_name_with_use,
            "
use std::fmt;

impl std::io<|> for Foo {
}
    ",
            "
use std::{io, fmt};

impl io for Foo {
}
    ",
        );
    }

    #[test]
    fn test_replace_split_self_for_use() {
        check_assist(
            replace_qualified_name_with_use,
            "
use std::fmt;

impl std::fmt::Debug<|> for Foo {
}
    ",
            "
use std::fmt::{self, Debug, };

impl Debug for Foo {
}
    ",
        );
    }

    #[test]
    fn test_replace_split_self_for_target() {
        check_assist(
            replace_qualified_name_with_use,
            "
use std::fmt::Debug;

impl std::fmt<|> for Foo {
}
    ",
            "
use std::fmt::{self, Debug};

impl fmt for Foo {
}
    ",
        );
    }

    #[test]
    fn test_replace_add_to_nested_self_nested() {
        check_assist(
            replace_qualified_name_with_use,
            "
use std::fmt::{Debug, nested::{Display}};

impl std::fmt::nested<|> for Foo {
}
",
            "
use std::fmt::{Debug, nested::{Display, self}};

impl nested for Foo {
}
",
        );
    }

    #[test]
    fn test_replace_add_to_nested_self_already_included() {
        check_assist(
            replace_qualified_name_with_use,
            "
use std::fmt::{Debug, nested::{self, Display}};

impl std::fmt::nested<|> for Foo {
}
",
            "
use std::fmt::{Debug, nested::{self, Display}};

impl nested for Foo {
}
",
        );
    }

    #[test]
    fn test_replace_add_to_nested_nested() {
        check_assist(
            replace_qualified_name_with_use,
            "
use std::fmt::{Debug, nested::{Display}};

impl std::fmt::nested::Debug<|> for Foo {
}
",
            "
use std::fmt::{Debug, nested::{Display, Debug}};

impl Debug for Foo {
}
",
        );
    }

    #[test]
    fn test_replace_split_common_target_longer() {
        check_assist(
            replace_qualified_name_with_use,
            "
use std::fmt::Debug;

impl std::fmt::nested::Display<|> for Foo {
}
",
            "
use std::fmt::{nested::Display, Debug};

impl Display for Foo {
}
",
        );
    }

    #[test]
    fn test_replace_split_common_use_longer() {
        check_assist(
            replace_qualified_name_with_use,
            "
use std::fmt::nested::Debug;

impl std::fmt::Display<|> for Foo {
}
",
            "
use std::fmt::{Display, nested::Debug};

impl Display for Foo {
}
",
        );
    }

    #[test]
    fn test_replace_use_nested_import() {
        check_assist(
            replace_qualified_name_with_use,
            "
use crate::{
    ty::{Substs, Ty},
    AssocItem,
};

fn foo() { crate::ty::lower<|>::trait_env() }
",
            "
use crate::{
    ty::{Substs, Ty, lower},
    AssocItem,
};

fn foo() { lower::trait_env() }
",
        );
    }

    #[test]
    fn test_replace_alias() {
        check_assist(
            replace_qualified_name_with_use,
            "
use std::fmt as foo;

impl foo::Debug<|> for Foo {
}
",
            "
use std::fmt as foo;

impl Debug for Foo {
}
",
        );
    }

    #[test]
    fn test_replace_not_applicable_one_segment() {
        check_assist_not_applicable(
            replace_qualified_name_with_use,
            "
impl foo<|> for Foo {
}
",
        );
    }

    #[test]
    fn test_replace_not_applicable_in_use() {
        check_assist_not_applicable(
            replace_qualified_name_with_use,
            "
use std::fmt<|>;
",
        );
    }

    #[test]
    fn test_replace_add_use_no_anchor_in_mod_mod() {
        check_assist(
            replace_qualified_name_with_use,
            "
mod foo {
    mod bar {
        std::fmt::Debug<|>
    }
}
    ",
            "
mod foo {
    mod bar {
        use std::fmt::Debug;

        Debug
    }
}
    ",
        );
    }

    #[test]
    fn inserts_imports_after_inner_attributes() {
        check_assist(
            replace_qualified_name_with_use,
            "
#![allow(dead_code)]

fn main() {
    std::fmt::Debug<|>
}
    ",
            "
#![allow(dead_code)]
use std::fmt::Debug;

fn main() {
    Debug
}
    ",
        );
    }

    #[test]
    fn replaces_all_affected_paths() {
        check_assist(
            replace_qualified_name_with_use,
            "
fn main() {
    std::fmt::Debug<|>;
    let x: std::fmt::Debug = std::fmt::Debug;
}
    ",
            "
use std::fmt::Debug;

fn main() {
    Debug;
    let x: Debug = Debug;
}
    ",
        );
    }

    #[test]
    fn replaces_all_affected_paths_mod() {
        check_assist(
            replace_qualified_name_with_use,
            "
mod m {
    fn f() {
        std::fmt::Debug<|>;
        let x: std::fmt::Debug = std::fmt::Debug;
    }
    fn g() {
        std::fmt::Debug;
    }
}

fn f() {
    std::fmt::Debug;
}
    ",
            "
mod m {
    use std::fmt::Debug;

    fn f() {
        Debug;
        let x: Debug = Debug;
    }
    fn g() {
        Debug;
    }
}

fn f() {
    std::fmt::Debug;
}
    ",
        );
    }

    #[test]
    fn does_not_replace_in_submodules() {
        check_assist(
            replace_qualified_name_with_use,
            "
fn main() {
    std::fmt::Debug<|>;
}

mod sub {
    fn f() {
        std::fmt::Debug;
    }
}
    ",
            "
use std::fmt::Debug;

fn main() {
    Debug;
}

mod sub {
    fn f() {
        std::fmt::Debug;
    }
}
    ",
        );
    }

    #[test]
    fn does_not_replace_in_use() {
        check_assist(
            replace_qualified_name_with_use,
            "
use std::fmt::Display;

fn main() {
    std::fmt<|>;
}
    ",
            "
use std::fmt::{self, Display};

fn main() {
    fmt;
}
    ",
        );
    }
}
<commit_msg>It's fookin' raw<commit_after>use hir::{self, ModPath};
use ra_syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SmolStr, SyntaxNode};

use crate::{
    utils::{find_insert_use_container, insert_use_statement},
    AssistContext, AssistId, Assists,
};
use either::Either;

// Assist: replace_qualified_name_with_use
//
// Adds a use statement for a given fully-qualified name.
//
// ```
// fn process(map: std::collections::<|>HashMap<String, String>) {}
// ```
// ->
// ```
// use std::collections::HashMap;
//
// fn process(map: HashMap<String, String>) {}
// ```
pub(crate) fn replace_qualified_name_with_use(
    acc: &mut Assists,
    ctx: &AssistContext,
) -> Option<()> {
    let path: ast::Path = ctx.find_node_at_offset()?;
    // We don't want to mess with use statements
    if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() {
        return None;
    }

    let hir_path = ctx.sema.lower_path(&path)?;
    let segments = collect_hir_path_segments(&hir_path)?;
    if segments.len() < 2 {
        return None;
    }

    let target = path.syntax().text_range();
    acc.add(
        AssistId("replace_qualified_name_with_use"),
        "Replace qualified path with use",
        target,
        |builder| {
            let path_to_import = hir_path.mod_path().clone();
            let container = match find_insert_use_container(path.syntax(), ctx) {
                Some(c) => c,
                None => return,
            };
            insert_use_statement(path.syntax(), &path_to_import, ctx, builder.text_edit_builder());

            // Now that we've brought the name into scope, re-qualify all paths that could be
            // affected (that is, all paths inside the node we added the `use` to).
            let hir_path = match hir::Path::from_ast(path.clone()) {
                Some(p) => p,
                None => return,
            };
            let mut rewriter = SyntaxRewriter::default();
            match container {
                Either::Left(l) => {
                    shorten_paths(&mut rewriter, l.syntax().clone(), hir_path.mod_path());
                }
                Either::Right(r) => {
                    shorten_paths(&mut rewriter, r.syntax().clone(), hir_path.mod_path());
                }
            }
            builder.rewrite(rewriter);
        },
    )
}

fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> {
    let mut ps = Vec::<SmolStr>::with_capacity(10);
    match path.kind() {
        hir::PathKind::Abs => ps.push("".into()),
        hir::PathKind::Crate => ps.push("crate".into()),
        hir::PathKind::Plain => {}
        hir::PathKind::Super(0) => ps.push("self".into()),
        hir::PathKind::Super(lvl) => {
            let mut chain = "super".to_string();
            for _ in 0..*lvl {
                chain += "::super";
            }
            ps.push(chain.into());
        }
        hir::PathKind::DollarCrate(_) => return None,
    }
    ps.extend(path.segments().iter().map(|it| it.name.to_string().into()));
    Some(ps)
}

/// Adds replacements to `re` that shorten `path` in all descendants of `node`.
fn shorten_paths(re: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: &ModPath) {
    for child in node.children() {
        match_ast! {
            match child {
                // Don't modify `use` items, as this can break the `use` item when injecting a new
                // import into the use tree.
                ast::UseItem(_it) => continue,
                // Don't descend into submodules, they don't have the same `use` items in scope.
                ast::Module(_it) => continue,

                ast::Path(p) => {
                    match maybe_replace_path(re, &p, path) {
                        Some(()) => {},
                        None => shorten_paths(re, p.syntax().clone(), path),
                    }
                },
                _ => shorten_paths(re, child, path),
            }
        }
    }
}

fn maybe_replace_path(
    re: &mut SyntaxRewriter<'static>,
    p: &ast::Path,
    path: &ModPath,
) -> Option<()> {
    let hir_path = hir::Path::from_ast(p.clone())?;

    if hir_path.mod_path() != path {
        return None;
    }

    // Replace path with its last "plain" segment.
    let mut mod_path = hir_path.mod_path().clone();
    let last = mod_path.segments.len() - 1;
    mod_path.segments.swap(0, last);
    mod_path.segments.truncate(1);
    mod_path.kind = hir::PathKind::Plain;

    let mut new_path = crate::ast_transform::path_to_ast(mod_path);

    let type_args = p.segment().and_then(|s| s.type_arg_list());
    if let Some(type_args) = type_args {
        let last_segment = new_path.segment().unwrap();
        new_path = new_path.with_segment(last_segment.with_type_args(type_args));
    }

    re.replace(p.syntax(), new_path.syntax());
    Some(())
}

#[cfg(test)]
mod tests {
    use crate::tests::{check_assist, check_assist_not_applicable};

    use super::*;

    #[test]
    fn test_replace_add_use_no_anchor() {
        check_assist(
            replace_qualified_name_with_use,
            r"
std::fmt::Debug<|>
    ",
            r"
use std::fmt::Debug;

Debug
    ",
        );
    }
    #[test]
    fn test_replace_add_use_no_anchor_with_item_below() {
        check_assist(
            replace_qualified_name_with_use,
            r"
std::fmt::Debug<|>

fn main() {
}
    ",
            r"
use std::fmt::Debug;

Debug

fn main() {
}
    ",
        );
    }

    #[test]
    fn test_replace_add_use_no_anchor_with_item_above() {
        check_assist(
            replace_qualified_name_with_use,
            r"
fn main() {
}

std::fmt::Debug<|>
    ",
            r"
use std::fmt::Debug;

fn main() {
}

Debug
    ",
        );
    }

    #[test]
    fn test_replace_add_use_no_anchor_2seg() {
        check_assist(
            replace_qualified_name_with_use,
            r"
std::fmt<|>::Debug
    ",
            r"
use std::fmt;

fmt::Debug
    ",
        );
    }

    #[test]
    fn test_replace_add_use() {
        check_assist(
            replace_qualified_name_with_use,
            r"
use stdx;

impl std::fmt::Debug<|> for Foo {
}
    ",
            r"
use stdx;
use std::fmt::Debug;

impl Debug for Foo {
}
    ",
        );
    }

    #[test]
    fn test_replace_file_use_other_anchor() {
        check_assist(
            replace_qualified_name_with_use,
            r"
impl std::fmt::Debug<|> for Foo {
}
    ",
            r"
use std::fmt::Debug;

impl Debug for Foo {
}
    ",
        );
    }

    #[test]
    fn test_replace_add_use_other_anchor_indent() {
        check_assist(
            replace_qualified_name_with_use,
            r"
    impl std::fmt::Debug<|> for Foo {
    }
    ",
            r"
    use std::fmt::Debug;

    impl Debug for Foo {
    }
    ",
        );
    }

    #[test]
    fn test_replace_split_different() {
        check_assist(
            replace_qualified_name_with_use,
            r"
use std::fmt;

impl std::io<|> for Foo {
}
    ",
            r"
use std::{io, fmt};

impl io for Foo {
}
    ",
        );
    }

    #[test]
    fn test_replace_split_self_for_use() {
        check_assist(
            replace_qualified_name_with_use,
            r"
use std::fmt;

impl std::fmt::Debug<|> for Foo {
}
    ",
            r"
use std::fmt::{self, Debug, };

impl Debug for Foo {
}
    ",
        );
    }

    #[test]
    fn test_replace_split_self_for_target() {
        check_assist(
            replace_qualified_name_with_use,
            r"
use std::fmt::Debug;

impl std::fmt<|> for Foo {
}
    ",
            r"
use std::fmt::{self, Debug};

impl fmt for Foo {
}
    ",
        );
    }

    #[test]
    fn test_replace_add_to_nested_self_nested() {
        check_assist(
            replace_qualified_name_with_use,
            r"
use std::fmt::{Debug, nested::{Display}};

impl std::fmt::nested<|> for Foo {
}
",
            r"
use std::fmt::{Debug, nested::{Display, self}};

impl nested for Foo {
}
",
        );
    }

    #[test]
    fn test_replace_add_to_nested_self_already_included() {
        check_assist(
            replace_qualified_name_with_use,
            r"
use std::fmt::{Debug, nested::{self, Display}};

impl std::fmt::nested<|> for Foo {
}
",
            r"
use std::fmt::{Debug, nested::{self, Display}};

impl nested for Foo {
}
",
        );
    }

    #[test]
    fn test_replace_add_to_nested_nested() {
        check_assist(
            replace_qualified_name_with_use,
            r"
use std::fmt::{Debug, nested::{Display}};

impl std::fmt::nested::Debug<|> for Foo {
}
",
            r"
use std::fmt::{Debug, nested::{Display, Debug}};

impl Debug for Foo {
}
",
        );
    }

    #[test]
    fn test_replace_split_common_target_longer() {
        check_assist(
            replace_qualified_name_with_use,
            r"
use std::fmt::Debug;

impl std::fmt::nested::Display<|> for Foo {
}
",
            r"
use std::fmt::{nested::Display, Debug};

impl Display for Foo {
}
",
        );
    }

    #[test]
    fn test_replace_split_common_use_longer() {
        check_assist(
            replace_qualified_name_with_use,
            r"
use std::fmt::nested::Debug;

impl std::fmt::Display<|> for Foo {
}
",
            r"
use std::fmt::{Display, nested::Debug};

impl Display for Foo {
}
",
        );
    }

    #[test]
    fn test_replace_use_nested_import() {
        check_assist(
            replace_qualified_name_with_use,
            r"
use crate::{
    ty::{Substs, Ty},
    AssocItem,
};

fn foo() { crate::ty::lower<|>::trait_env() }
",
            r"
use crate::{
    ty::{Substs, Ty, lower},
    AssocItem,
};

fn foo() { lower::trait_env() }
",
        );
    }

    #[test]
    fn test_replace_alias() {
        check_assist(
            replace_qualified_name_with_use,
            r"
use std::fmt as foo;

impl foo::Debug<|> for Foo {
}
",
            r"
use std::fmt as foo;

impl Debug for Foo {
}
",
        );
    }

    #[test]
    fn test_replace_not_applicable_one_segment() {
        check_assist_not_applicable(
            replace_qualified_name_with_use,
            r"
impl foo<|> for Foo {
}
",
        );
    }

    #[test]
    fn test_replace_not_applicable_in_use() {
        check_assist_not_applicable(
            replace_qualified_name_with_use,
            r"
use std::fmt<|>;
",
        );
    }

    #[test]
    fn test_replace_add_use_no_anchor_in_mod_mod() {
        check_assist(
            replace_qualified_name_with_use,
            r"
mod foo {
    mod bar {
        std::fmt::Debug<|>
    }
}
    ",
            r"
mod foo {
    mod bar {
        use std::fmt::Debug;

        Debug
    }
}
    ",
        );
    }

    #[test]
    fn inserts_imports_after_inner_attributes() {
        check_assist(
            replace_qualified_name_with_use,
            r"
#![allow(dead_code)]

fn main() {
    std::fmt::Debug<|>
}
    ",
            r"
#![allow(dead_code)]
use std::fmt::Debug;

fn main() {
    Debug
}
    ",
        );
    }

    #[test]
    fn replaces_all_affected_paths() {
        check_assist(
            replace_qualified_name_with_use,
            r"
fn main() {
    std::fmt::Debug<|>;
    let x: std::fmt::Debug = std::fmt::Debug;
}
    ",
            r"
use std::fmt::Debug;

fn main() {
    Debug;
    let x: Debug = Debug;
}
    ",
        );
    }

    #[test]
    fn replaces_all_affected_paths_mod() {
        check_assist(
            replace_qualified_name_with_use,
            r"
mod m {
    fn f() {
        std::fmt::Debug<|>;
        let x: std::fmt::Debug = std::fmt::Debug;
    }
    fn g() {
        std::fmt::Debug;
    }
}

fn f() {
    std::fmt::Debug;
}
    ",
            r"
mod m {
    use std::fmt::Debug;

    fn f() {
        Debug;
        let x: Debug = Debug;
    }
    fn g() {
        Debug;
    }
}

fn f() {
    std::fmt::Debug;
}
    ",
        );
    }

    #[test]
    fn does_not_replace_in_submodules() {
        check_assist(
            replace_qualified_name_with_use,
            r"
fn main() {
    std::fmt::Debug<|>;
}

mod sub {
    fn f() {
        std::fmt::Debug;
    }
}
    ",
            r"
use std::fmt::Debug;

fn main() {
    Debug;
}

mod sub {
    fn f() {
        std::fmt::Debug;
    }
}
    ",
        );
    }

    #[test]
    fn does_not_replace_in_use() {
        check_assist(
            replace_qualified_name_with_use,
            r"
use std::fmt::Display;

fn main() {
    std::fmt<|>;
}
    ",
            r"
use std::fmt::{self, Display};

fn main() {
    fmt;
}
    ",
        );
    }
}
<|endoftext|>


<commit_before>use crate::config::Config;
use crate::err::Result;
use crate::file::{FileType, SaveDir, SaveFile};
use anime::remote::{RemoteService, SeriesEntry, SeriesInfo, Status};
use chrono::{Local, NaiveDate};
use serde_derive::{Deserialize, Serialize};
use std::borrow::Cow;

#[derive(Debug, Deserialize, Serialize)]
pub struct EntryState {
    entry: SeriesEntry,
    needs_sync: bool,
}

impl EntryState {
    pub fn new(entry: SeriesEntry) -> EntryState {
        EntryState {
            entry,
            needs_sync: false,
        }
    }

    pub fn force_sync_changes_to_remote<R, S>(&mut self, remote: &R, name: S) -> Result<()>
    where
        R: RemoteService + ?Sized,
        S: AsRef<str>,
    {
        if remote.is_offline() {
            self.save_with_id(self.entry.id, name.as_ref())?;
            return Ok(());
        }

        remote.update_list_entry(&self.entry)?;

        self.needs_sync = false;
        self.save_with_id(self.entry.id, name.as_ref())?;

        Ok(())
    }

    pub fn sync_changes_to_remote<R, S>(&mut self, remote: &R, name: S) -> Result<()>
    where
        R: RemoteService + ?Sized,
        S: AsRef<str>,
    {
        if !self.needs_sync {
            return Ok(());
        }

        self.force_sync_changes_to_remote(remote, name)
    }

    pub fn force_sync_changes_from_remote<R, S>(&mut self, remote: &R, name: S) -> Result<()>
    where
        R: RemoteService + ?Sized,
        S: AsRef<str>,
    {
        let entry = match remote.get_list_entry(self.entry.id)? {
            Some(entry) => entry,
            None => SeriesEntry::new(self.entry.id),
        };

        self.entry = entry;
        self.needs_sync = false;
        self.save_with_id(self.entry.id, name.as_ref())?;

        Ok(())
    }

    pub fn sync_changes_from_remote<R, S>(&mut self, remote: &R, name: S) -> Result<()>
    where
        R: RemoteService + ?Sized,
        S: AsRef<str>,
    {
        if self.needs_sync || remote.is_offline() {
            return Ok(());
        }

        self.force_sync_changes_from_remote(remote, name)
    }

    pub fn mark_as_dropped(&mut self, config: &Config) {
        if self.end_date().is_none()
            || (self.status() == Status::Rewatching && config.reset_dates_on_rewatch)
        {
            self.entry.end_date = Some(Local::today().naive_local());
        }

        self.entry.status = Status::Dropped;
        self.needs_sync = true;
    }

    pub fn mark_as_on_hold(&mut self) {
        self.entry.status = Status::OnHold;
        self.needs_sync = true;
    }

    #[inline(always)]
    pub fn needs_sync(&self) -> bool {
        self.needs_sync
    }

    #[inline(always)]
    pub fn watched_eps(&self) -> u32 {
        self.entry.watched_eps
    }

    #[inline(always)]
    pub fn set_watched_eps(&mut self, watched_eps: u32) {
        self.entry.watched_eps = watched_eps;
        self.needs_sync = true;
    }

    #[inline(always)]
    pub fn score(&self) -> Option<u8> {
        self.entry.score
    }

    #[inline(always)]
    pub fn set_score(&mut self, score: Option<u8>) {
        self.entry.score = score;
        self.needs_sync = true;
    }

    #[inline(always)]
    pub fn status(&self) -> Status {
        self.entry.status
    }

    #[inline(always)]
    pub fn set_status(&mut self, status: Status) {
        self.entry.status = status;
        self.needs_sync = true;
    }

    #[inline(always)]
    pub fn times_rewatched(&self) -> u32 {
        self.entry.times_rewatched
    }

    #[inline(always)]
    pub fn set_times_rewatched(&mut self, times_rewatched: u32) {
        self.entry.times_rewatched = times_rewatched;
        self.needs_sync = true;
    }

    #[inline(always)]
    pub fn start_date(&self) -> Option<NaiveDate> {
        self.entry.start_date
    }

    #[inline(always)]
    pub fn set_start_date(&mut self, start_date: Option<NaiveDate>) {
        self.entry.start_date = start_date;
        self.needs_sync = true;
    }

    #[inline(always)]
    pub fn end_date(&self) -> Option<NaiveDate> {
        self.entry.end_date
    }

    #[inline(always)]
    pub fn set_end_date(&mut self, end_date: Option<NaiveDate>) {
        self.entry.end_date = end_date;
        self.needs_sync = true;
    }
}

impl SaveFile for EntryState {
    fn filename() -> &'static str {
        "entry_state.mpack"
    }

    fn save_dir() -> SaveDir {
        SaveDir::LocalData
    }

    fn file_type() -> FileType {
        FileType::MessagePack
    }
}

impl From<u32> for EntryState {
    fn from(id: u32) -> EntryState {
        let entry = SeriesEntry::new(id);
        EntryState::new(entry)
    }
}

#[derive(Debug)]
pub struct SeriesTracker<'a> {
    pub info: Cow<'a, SeriesInfo>,
    pub state: EntryState,
    pub name: String,
}

impl<'a> SeriesTracker<'a> {
    pub fn init<I, S>(info: I, name: S) -> Result<SeriesTracker<'a>>
    where
        I: Into<Cow<'a, SeriesInfo>>,
        S: Into<String>,
    {
        let info = info.into();
        let name = name.into();

        let state = match EntryState::load_with_id(info.id, name.as_ref()) {
            Ok(state) => state,
            Err(ref err) if err.is_file_nonexistant() => EntryState::from(info.id),
            Err(err) => return Err(err),
        };

        Ok(SeriesTracker { info, state, name })
    }

    pub fn begin_watching<R>(&mut self, remote: &R, config: &Config) -> Result<()>
    where
        R: RemoteService + ?Sized,
    {
        let state = &mut self.state;
        state.sync_changes_from_remote(remote, &self.name)?;

        let last_status = state.status();

        match last_status {
            Status::Watching | Status::Rewatching => {
                // There is an edge case where all episodes have been watched, but the status
                // is still set to watching / rewatching. Here we just start a rewatch
                if state.watched_eps() >= self.info.episodes {
                    state.set_status(Status::Rewatching);
                    state.set_watched_eps(0);

                    if last_status == Status::Rewatching {
                        state.set_times_rewatched(state.times_rewatched() + 1);
                    }
                }
            }
            Status::Completed => {
                state.set_status(Status::Rewatching);
                state.set_watched_eps(0);
            }
            Status::PlanToWatch => state.set_status(Status::Watching),
            Status::OnHold | Status::Dropped => {
                state.set_status(Status::Watching);
                state.set_watched_eps(0);
            }
        }

        if state.start_date().is_none()
            || (last_status == Status::Completed
                && state.status() == Status::Rewatching
                && config.reset_dates_on_rewatch)
        {
            state.set_start_date(Some(Local::today().naive_local()));
        }

        state.sync_changes_to_remote(remote, &self.name)?;

        Ok(())
    }

    pub fn episode_completed<R>(&mut self, remote: &R, config: &Config) -> Result<()>
    where
        R: RemoteService + ?Sized,
    {
        let state = &mut self.state;
        state.set_watched_eps(state.watched_eps() + 1);

        if state.watched_eps() >= self.info.episodes {
            return self.series_complete(remote, config);
        }

        state.sync_changes_to_remote(remote, &self.name)?;

        Ok(())
    }

    pub fn series_complete<R>(&mut self, remote: &R, config: &Config) -> Result<()>
    where
        R: RemoteService + ?Sized,
    {
        let state = &mut self.state;

        // A rewatch is typically only counted once the series is completed again
        if state.status() == Status::Rewatching {
            state.set_times_rewatched(state.times_rewatched() + 1);
        }

        if state.end_date().is_none()
            || (state.status() == Status::Rewatching && config.reset_dates_on_rewatch)
        {
            state.set_end_date(Some(Local::today().naive_local()));
        }

        state.set_status(Status::Completed);
        state.sync_changes_to_remote(remote, &self.name)?;

        Ok(())
    }
}
<commit_msg>[anup] Exit early if the remote is offline in force_sync_changes_from_remote<commit_after>use crate::config::Config;
use crate::err::Result;
use crate::file::{FileType, SaveDir, SaveFile};
use anime::remote::{RemoteService, SeriesEntry, SeriesInfo, Status};
use chrono::{Local, NaiveDate};
use serde_derive::{Deserialize, Serialize};
use std::borrow::Cow;

#[derive(Debug, Deserialize, Serialize)]
pub struct EntryState {
    entry: SeriesEntry,
    needs_sync: bool,
}

impl EntryState {
    pub fn new(entry: SeriesEntry) -> EntryState {
        EntryState {
            entry,
            needs_sync: false,
        }
    }

    pub fn force_sync_changes_to_remote<R, S>(&mut self, remote: &R, name: S) -> Result<()>
    where
        R: RemoteService + ?Sized,
        S: AsRef<str>,
    {
        if remote.is_offline() {
            self.save_with_id(self.entry.id, name.as_ref())?;
            return Ok(());
        }

        remote.update_list_entry(&self.entry)?;

        self.needs_sync = false;
        self.save_with_id(self.entry.id, name.as_ref())?;

        Ok(())
    }

    pub fn sync_changes_to_remote<R, S>(&mut self, remote: &R, name: S) -> Result<()>
    where
        R: RemoteService + ?Sized,
        S: AsRef<str>,
    {
        if !self.needs_sync {
            return Ok(());
        }

        self.force_sync_changes_to_remote(remote, name)
    }

    pub fn force_sync_changes_from_remote<R, S>(&mut self, remote: &R, name: S) -> Result<()>
    where
        R: RemoteService + ?Sized,
        S: AsRef<str>,
    {
        if remote.is_offline() {
            return Ok(());
        }

        let entry = match remote.get_list_entry(self.entry.id)? {
            Some(entry) => entry,
            None => SeriesEntry::new(self.entry.id),
        };

        self.entry = entry;
        self.needs_sync = false;
        self.save_with_id(self.entry.id, name.as_ref())?;

        Ok(())
    }

    pub fn sync_changes_from_remote<R, S>(&mut self, remote: &R, name: S) -> Result<()>
    where
        R: RemoteService + ?Sized,
        S: AsRef<str>,
    {
        if self.needs_sync {
            return Ok(());
        }

        self.force_sync_changes_from_remote(remote, name)
    }

    pub fn mark_as_dropped(&mut self, config: &Config) {
        if self.end_date().is_none()
            || (self.status() == Status::Rewatching && config.reset_dates_on_rewatch)
        {
            self.entry.end_date = Some(Local::today().naive_local());
        }

        self.entry.status = Status::Dropped;
        self.needs_sync = true;
    }

    pub fn mark_as_on_hold(&mut self) {
        self.entry.status = Status::OnHold;
        self.needs_sync = true;
    }

    #[inline(always)]
    pub fn needs_sync(&self) -> bool {
        self.needs_sync
    }

    #[inline(always)]
    pub fn watched_eps(&self) -> u32 {
        self.entry.watched_eps
    }

    #[inline(always)]
    pub fn set_watched_eps(&mut self, watched_eps: u32) {
        self.entry.watched_eps = watched_eps;
        self.needs_sync = true;
    }

    #[inline(always)]
    pub fn score(&self) -> Option<u8> {
        self.entry.score
    }

    #[inline(always)]
    pub fn set_score(&mut self, score: Option<u8>) {
        self.entry.score = score;
        self.needs_sync = true;
    }

    #[inline(always)]
    pub fn status(&self) -> Status {
        self.entry.status
    }

    #[inline(always)]
    pub fn set_status(&mut self, status: Status) {
        self.entry.status = status;
        self.needs_sync = true;
    }

    #[inline(always)]
    pub fn times_rewatched(&self) -> u32 {
        self.entry.times_rewatched
    }

    #[inline(always)]
    pub fn set_times_rewatched(&mut self, times_rewatched: u32) {
        self.entry.times_rewatched = times_rewatched;
        self.needs_sync = true;
    }

    #[inline(always)]
    pub fn start_date(&self) -> Option<NaiveDate> {
        self.entry.start_date
    }

    #[inline(always)]
    pub fn set_start_date(&mut self, start_date: Option<NaiveDate>) {
        self.entry.start_date = start_date;
        self.needs_sync = true;
    }

    #[inline(always)]
    pub fn end_date(&self) -> Option<NaiveDate> {
        self.entry.end_date
    }

    #[inline(always)]
    pub fn set_end_date(&mut self, end_date: Option<NaiveDate>) {
        self.entry.end_date = end_date;
        self.needs_sync = true;
    }
}

impl SaveFile for EntryState {
    fn filename() -> &'static str {
        "entry_state.mpack"
    }

    fn save_dir() -> SaveDir {
        SaveDir::LocalData
    }

    fn file_type() -> FileType {
        FileType::MessagePack
    }
}

impl From<u32> for EntryState {
    fn from(id: u32) -> EntryState {
        let entry = SeriesEntry::new(id);
        EntryState::new(entry)
    }
}

#[derive(Debug)]
pub struct SeriesTracker<'a> {
    pub info: Cow<'a, SeriesInfo>,
    pub state: EntryState,
    pub name: String,
}

impl<'a> SeriesTracker<'a> {
    pub fn init<I, S>(info: I, name: S) -> Result<SeriesTracker<'a>>
    where
        I: Into<Cow<'a, SeriesInfo>>,
        S: Into<String>,
    {
        let info = info.into();
        let name = name.into();

        let state = match EntryState::load_with_id(info.id, name.as_ref()) {
            Ok(state) => state,
            Err(ref err) if err.is_file_nonexistant() => EntryState::from(info.id),
            Err(err) => return Err(err),
        };

        Ok(SeriesTracker { info, state, name })
    }

    pub fn begin_watching<R>(&mut self, remote: &R, config: &Config) -> Result<()>
    where
        R: RemoteService + ?Sized,
    {
        let state = &mut self.state;
        state.sync_changes_from_remote(remote, &self.name)?;

        let last_status = state.status();

        match last_status {
            Status::Watching | Status::Rewatching => {
                // There is an edge case where all episodes have been watched, but the status
                // is still set to watching / rewatching. Here we just start a rewatch
                if state.watched_eps() >= self.info.episodes {
                    state.set_status(Status::Rewatching);
                    state.set_watched_eps(0);

                    if last_status == Status::Rewatching {
                        state.set_times_rewatched(state.times_rewatched() + 1);
                    }
                }
            }
            Status::Completed => {
                state.set_status(Status::Rewatching);
                state.set_watched_eps(0);
            }
            Status::PlanToWatch => state.set_status(Status::Watching),
            Status::OnHold | Status::Dropped => {
                state.set_status(Status::Watching);
                state.set_watched_eps(0);
            }
        }

        if state.start_date().is_none()
            || (last_status == Status::Completed
                && state.status() == Status::Rewatching
                && config.reset_dates_on_rewatch)
        {
            state.set_start_date(Some(Local::today().naive_local()));
        }

        state.sync_changes_to_remote(remote, &self.name)?;

        Ok(())
    }

    pub fn episode_completed<R>(&mut self, remote: &R, config: &Config) -> Result<()>
    where
        R: RemoteService + ?Sized,
    {
        let state = &mut self.state;
        state.set_watched_eps(state.watched_eps() + 1);

        if state.watched_eps() >= self.info.episodes {
            return self.series_complete(remote, config);
        }

        state.sync_changes_to_remote(remote, &self.name)?;

        Ok(())
    }

    pub fn series_complete<R>(&mut self, remote: &R, config: &Config) -> Result<()>
    where
        R: RemoteService + ?Sized,
    {
        let state = &mut self.state;

        // A rewatch is typically only counted once the series is completed again
        if state.status() == Status::Rewatching {
            state.set_times_rewatched(state.times_rewatched() + 1);
        }

        if state.end_date().is_none()
            || (state.status() == Status::Rewatching && config.reset_dates_on_rewatch)
        {
            state.set_end_date(Some(Local::today().naive_local()));
        }

        state.set_status(Status::Completed);
        state.sync_changes_to_remote(remote, &self.name)?;

        Ok(())
    }
}
<|endoftext|>


<commit_before>// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::collections::HashSet;

use itertools::Itertools;
use syn::{
    Expr, ExprAssign, ExprAssignOp, ExprAwait, ExprBinary, ExprBox, ExprBreak, ExprCast, ExprField,
    ExprGroup, ExprLet, ExprParen, ExprReference, ExprTry, ExprType, ExprUnary, ImplItem, Item,
    Path, Stmt, TraitItem,
};

#[derive(Default)]
pub(super) struct CppList(pub(super) HashSet<String>);

impl CppList {
    pub(super) fn search_item(&mut self, item: &Item) {
        match item {
            Item::Fn(fun) => {
                for stmt in &fun.block.stmts {
                    self.search_stmt(stmt)
                }
            }
            Item::Impl(imp) => {
                for item in &imp.items {
                    self.search_impl_item(item)
                }
            }
            Item::Mod(md) => {
                if let Some((_, items)) = &md.content {
                    for item in items {
                        self.search_item(item)
                    }
                }
            }
            Item::Trait(tr) => {
                for item in &tr.items {
                    self.search_trait_item(item)
                }
            }
            _ => {}
        }
    }

    fn consider_path(&mut self, path: &Path) {
        let mut seg_iter = path.segments.iter();
        if let Some(first_seg) = seg_iter.next() {
            if first_seg.ident == "ffi" {
                self.0
                    .insert(seg_iter.map(|seg| seg.ident.to_string()).join("::"));
            }
        }
    }

    fn search_trait_item(&mut self, itm: &TraitItem) {
        if let TraitItem::Method(itm) = itm {
            if let Some(block) = &itm.default {
                self.search_stmts(block.stmts.iter())
            }
        }
    }

    fn search_stmts<'a>(&mut self, stmts: impl Iterator<Item = &'a Stmt>) {
        for stmt in stmts {
            self.search_stmt(stmt)
        }
    }

    fn search_stmt(&mut self, stmt: &Stmt) {
        match stmt {
            Stmt::Local(lcl) => {
                if let Some((_, expr)) = &lcl.init {
                    self.search_expr(expr)
                }
            }
            Stmt::Item(itm) => self.search_item(itm),
            Stmt::Expr(exp) | Stmt::Semi(exp, _) => self.search_expr(exp),
        }
    }

    fn search_expr(&mut self, expr: &Expr) {
        match expr {
            Expr::Path(exp) => {
                self.consider_path(&exp.path);
            }
            Expr::Macro(_) => {}
            Expr::Array(array) => self.search_exprs(array.elems.iter()),
            Expr::Assign(ExprAssign { left, right, .. })
            | Expr::AssignOp(ExprAssignOp { left, right, .. })
            | Expr::Binary(ExprBinary { left, right, .. }) => {
                self.search_expr(left);
                self.search_expr(right);
            }
            Expr::Async(ass) => self.search_stmts(ass.block.stmts.iter()),
            Expr::Await(ExprAwait { base, .. }) | Expr::Field(ExprField { base, .. }) => {
                self.search_expr(base)
            }
            Expr::Block(blck) => self.search_stmts(blck.block.stmts.iter()),
            Expr::Box(ExprBox { expr, .. })
            | Expr::Break(ExprBreak {
                expr: Some(expr), ..
            })
            | Expr::Cast(ExprCast { expr, .. })
            | Expr::Group(ExprGroup { expr, .. })
            | Expr::Let(ExprLet { expr, .. })
            | Expr::Paren(ExprParen { expr, .. })
            | Expr::Reference(ExprReference { expr, .. })
            | Expr::Try(ExprTry { expr, .. })
            | Expr::Type(ExprType { expr, .. })
            | Expr::Unary(ExprUnary { expr, .. }) => self.search_expr(expr),
            Expr::Call(exc) => {
                self.search_expr(&exc.func);
                self.search_exprs(exc.args.iter());
            }
            Expr::Closure(cls) => self.search_expr(&cls.body),
            Expr::Continue(_)
            | Expr::Lit(_)
            | Expr::Break(ExprBreak { expr: None, .. })
            | Expr::Verbatim(_) => {}
            Expr::ForLoop(fl) => {
                self.search_expr(&fl.expr);
                self.search_stmts(fl.body.stmts.iter());
            }
            Expr::If(exif) => {
                self.search_expr(&exif.cond);
                self.search_stmts(exif.then_branch.stmts.iter());
                if let Some((_, else_branch)) = &exif.else_branch {
                    self.search_expr(else_branch);
                }
            }
            Expr::Index(exidx) => {
                self.search_expr(&exidx.expr);
                self.search_expr(&exidx.index);
            }
            Expr::Loop(exloo) => self.search_stmts(exloo.body.stmts.iter()),
            Expr::Match(exm) => {
                self.search_expr(&exm.expr);
                for a in &exm.arms {
                    self.search_expr(&a.body);
                    if let Some((_, guard)) = &a.guard {
                        self.search_expr(guard);
                    }
                }
            }
            Expr::MethodCall(mtc) => {
                self.search_expr(&mtc.receiver);
                self.search_exprs(mtc.args.iter());
            }
            Expr::Range(exr) => {
                self.search_option_expr(&exr.from);
                self.search_option_expr(&exr.to);
            }
            Expr::Repeat(exr) => {
                self.search_expr(&exr.expr);
                self.search_expr(&exr.len);
            }
            Expr::Return(exret) => {
                if let Some(expr) = &exret.expr {
                    self.search_expr(expr);
                }
            }
            Expr::Struct(exst) => {
                for f in &exst.fields {
                    self.search_expr(&f.expr);
                }
                self.search_option_expr(&exst.rest);
            }
            Expr::TryBlock(extb) => self.search_stmts(extb.block.stmts.iter()),
            Expr::Tuple(ext) => self.search_exprs(ext.elems.iter()),
            Expr::Unsafe(exs) => self.search_stmts(exs.block.stmts.iter()),
            Expr::While(exw) => {
                self.search_expr(&exw.cond);
                self.search_stmts(exw.body.stmts.iter());
            }
            Expr::Yield(exy) => self.search_option_expr(&exy.expr),
            Expr::__TestExhaustive(_) => {}
        }
    }

    fn search_option_expr(&mut self, expr: &Option<Box<Expr>>) {
        if let Some(expr) = &expr {
            self.search_expr(expr);
        }
    }

    fn search_exprs<'a>(&mut self, exprs: impl Iterator<Item = &'a Expr>) {
        for e in exprs {
            self.search_expr(e);
        }
    }

    fn search_impl_item(&mut self, impl_item: &ImplItem) {
        if let ImplItem::Method(itm) = impl_item {
            for stmt in &itm.block.stmts {
                self.search_stmt(stmt)
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use syn::{parse_quote, Item};

    use super::CppList;

    fn assert_found(cpp_list: &CppList) {
        assert!(!cpp_list.0.is_empty());
        assert!(cpp_list.0.iter().next().unwrap() == "xxx");
    }

    #[test]
    fn test_mod_plain_call() {
        let mut cpplist = CppList::default();
        let itm = Item::Mod(parse_quote! {
            mod foo {
                fn bar() {
                    ffi::xxx()
                }
            }
        });
        cpplist.search_item(&itm);
        assert_found(&cpplist);
    }

    #[test]
    fn test_plain_call() {
        let mut cpplist = CppList::default();
        let itm = Item::Fn(parse_quote! {
            fn bar() {
                ffi::xxx()
            }
        });
        cpplist.search_item(&itm);
        assert_found(&cpplist);
    }

    #[test]
    fn test_plain_call_with_semi() {
        let mut cpplist = CppList::default();
        let itm = Item::Fn(parse_quote! {
            fn bar() {
                ffi::xxx();
            }
        });
        cpplist.search_item(&itm);
        assert_found(&cpplist);
    }

    #[test]
    fn test_deep_nested_thingy() {
        let mut cpplist = CppList::default();
        let itm = Item::Fn(parse_quote! {
            fn bar() {
                a + 3 * foo(ffi::xxx());
            }
        });
        cpplist.search_item(&itm);
        assert_found(&cpplist);
    }
}
<commit_msg>Search for types too.<commit_after>// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::collections::HashSet;

use itertools::Itertools;
use syn::{
    punctuated::Punctuated, Binding, Expr, ExprAssign, ExprAssignOp, ExprAwait, ExprBinary,
    ExprBox, ExprBreak, ExprCast, ExprField, ExprGroup, ExprLet, ExprParen, ExprReference, ExprTry,
    ExprType, ExprUnary, ImplItem, Item, Pat, PatBox, PatReference, PatSlice, PatTuple, Path,
    ReturnType, Stmt, TraitItem, Type, TypeArray, TypeGroup, TypeParamBound, TypeParen, TypePtr,
    TypeReference, TypeSlice,
};

#[derive(Default)]
pub(super) struct CppList(pub(super) HashSet<String>);

impl CppList {
    pub(super) fn search_item(&mut self, item: &Item) {
        match item {
            Item::Fn(fun) => {
                for stmt in &fun.block.stmts {
                    self.search_stmt(stmt)
                }
                self.search_return_type(&fun.sig.output);
                for i in &fun.sig.inputs {
                    match i {
                        syn::FnArg::Receiver(_) => {}
                        syn::FnArg::Typed(pt) => {
                            self.search_pat(&pt.pat);
                            self.search_type(&pt.ty);
                        }
                    }
                }
            }
            Item::Impl(imp) => {
                for item in &imp.items {
                    self.search_impl_item(item)
                }
            }
            Item::Mod(md) => {
                if let Some((_, items)) = &md.content {
                    for item in items {
                        self.search_item(item)
                    }
                }
            }
            Item::Trait(tr) => {
                for item in &tr.items {
                    self.search_trait_item(item)
                }
            }
            _ => {}
        }
    }

    fn search_path(&mut self, path: &Path) {
        let mut seg_iter = path.segments.iter();
        if let Some(first_seg) = seg_iter.next() {
            if first_seg.ident == "ffi" {
                self.0
                    .insert(seg_iter.map(|seg| seg.ident.to_string()).join("::"));
            }
        }
        for seg in path.segments.iter() {
            self.search_path_arguments(&seg.arguments);
        }
    }

    fn search_trait_item(&mut self, itm: &TraitItem) {
        if let TraitItem::Method(itm) = itm {
            if let Some(block) = &itm.default {
                self.search_stmts(block.stmts.iter())
            }
        }
    }

    fn search_stmts<'a>(&mut self, stmts: impl Iterator<Item = &'a Stmt>) {
        for stmt in stmts {
            self.search_stmt(stmt)
        }
    }

    fn search_stmt(&mut self, stmt: &Stmt) {
        match stmt {
            Stmt::Local(lcl) => {
                if let Some((_, expr)) = &lcl.init {
                    self.search_expr(expr)
                }
                self.search_pat(&lcl.pat);
            }
            Stmt::Item(itm) => self.search_item(itm),
            Stmt::Expr(exp) | Stmt::Semi(exp, _) => self.search_expr(exp),
        }
    }

    fn search_expr(&mut self, expr: &Expr) {
        match expr {
            Expr::Path(exp) => {
                self.search_path(&exp.path);
            }
            Expr::Macro(_) => {}
            Expr::Array(array) => self.search_exprs(array.elems.iter()),
            Expr::Assign(ExprAssign { left, right, .. })
            | Expr::AssignOp(ExprAssignOp { left, right, .. })
            | Expr::Binary(ExprBinary { left, right, .. }) => {
                self.search_expr(left);
                self.search_expr(right);
            }
            Expr::Async(ass) => self.search_stmts(ass.block.stmts.iter()),
            Expr::Await(ExprAwait { base, .. }) | Expr::Field(ExprField { base, .. }) => {
                self.search_expr(base)
            }
            Expr::Block(blck) => self.search_stmts(blck.block.stmts.iter()),
            Expr::Box(ExprBox { expr, .. })
            | Expr::Break(ExprBreak {
                expr: Some(expr), ..
            })
            | Expr::Cast(ExprCast { expr, .. })
            | Expr::Group(ExprGroup { expr, .. })
            | Expr::Paren(ExprParen { expr, .. })
            | Expr::Reference(ExprReference { expr, .. })
            | Expr::Try(ExprTry { expr, .. })
            | Expr::Type(ExprType { expr, .. })
            | Expr::Unary(ExprUnary { expr, .. }) => self.search_expr(expr),
            Expr::Call(exc) => {
                self.search_expr(&exc.func);
                self.search_exprs(exc.args.iter());
            }
            Expr::Closure(cls) => self.search_expr(&cls.body),
            Expr::Continue(_)
            | Expr::Lit(_)
            | Expr::Break(ExprBreak { expr: None, .. })
            | Expr::Verbatim(_) => {}
            Expr::ForLoop(fl) => {
                self.search_expr(&fl.expr);
                self.search_stmts(fl.body.stmts.iter());
            }
            Expr::If(exif) => {
                self.search_expr(&exif.cond);
                self.search_stmts(exif.then_branch.stmts.iter());
                if let Some((_, else_branch)) = &exif.else_branch {
                    self.search_expr(else_branch);
                }
            }
            Expr::Index(exidx) => {
                self.search_expr(&exidx.expr);
                self.search_expr(&exidx.index);
            }
            Expr::Let(ExprLet { expr, pat, .. }) => {
                self.search_expr(expr);
                self.search_pat(pat);
            }
            Expr::Loop(exloo) => self.search_stmts(exloo.body.stmts.iter()),
            Expr::Match(exm) => {
                self.search_expr(&exm.expr);
                for a in &exm.arms {
                    self.search_expr(&a.body);
                    if let Some((_, guard)) = &a.guard {
                        self.search_expr(guard);
                    }
                }
            }
            Expr::MethodCall(mtc) => {
                self.search_expr(&mtc.receiver);
                self.search_exprs(mtc.args.iter());
            }
            Expr::Range(exr) => {
                self.search_option_expr(&exr.from);
                self.search_option_expr(&exr.to);
            }
            Expr::Repeat(exr) => {
                self.search_expr(&exr.expr);
                self.search_expr(&exr.len);
            }
            Expr::Return(exret) => {
                if let Some(expr) = &exret.expr {
                    self.search_expr(expr);
                }
            }
            Expr::Struct(exst) => {
                for f in &exst.fields {
                    self.search_expr(&f.expr);
                }
                self.search_option_expr(&exst.rest);
            }
            Expr::TryBlock(extb) => self.search_stmts(extb.block.stmts.iter()),
            Expr::Tuple(ext) => self.search_exprs(ext.elems.iter()),
            Expr::Unsafe(exs) => self.search_stmts(exs.block.stmts.iter()),
            Expr::While(exw) => {
                self.search_expr(&exw.cond);
                self.search_stmts(exw.body.stmts.iter());
            }
            Expr::Yield(exy) => self.search_option_expr(&exy.expr),
            Expr::__TestExhaustive(_) => {}
        }
    }

    fn search_option_expr(&mut self, expr: &Option<Box<Expr>>) {
        if let Some(expr) = &expr {
            self.search_expr(expr);
        }
    }

    fn search_exprs<'a>(&mut self, exprs: impl Iterator<Item = &'a Expr>) {
        for e in exprs {
            self.search_expr(e);
        }
    }

    fn search_impl_item(&mut self, impl_item: &ImplItem) {
        if let ImplItem::Method(itm) = impl_item {
            for stmt in &itm.block.stmts {
                self.search_stmt(stmt)
            }
        }
    }

    fn search_pat(&mut self, pat: &Pat) {
        match pat {
            Pat::Box(PatBox { pat, .. }) | Pat::Reference(PatReference { pat, .. }) => {
                self.search_pat(pat)
            }
            Pat::Ident(_) | Pat::Lit(_) | Pat::Macro(_) | Pat::Range(_) | Pat::Rest(_) => {}
            Pat::Or(pator) => {
                for case in &pator.cases {
                    self.search_pat(case);
                }
            }
            Pat::Path(pp) => self.search_path(&pp.path),
            Pat::Slice(PatSlice { elems, .. }) | Pat::Tuple(PatTuple { elems, .. }) => {
                for case in elems {
                    self.search_pat(case);
                }
            }
            Pat::Struct(ps) => {
                self.search_path(&ps.path);
                for f in &ps.fields {
                    self.search_pat(&f.pat);
                }
            }
            Pat::TupleStruct(tps) => {
                self.search_path(&tps.path);
                for f in &tps.pat.elems {
                    self.search_pat(f);
                }
            }
            Pat::Type(pt) => {
                self.search_pat(&pt.pat);
                self.search_type(&pt.ty);
            }
            _ => {}
        }
    }

    fn search_type(&mut self, ty: &Type) {
        match ty {
            Type::Array(TypeArray { elem, .. })
            | Type::Group(TypeGroup { elem, .. })
            | Type::Paren(TypeParen { elem, .. })
            | Type::Ptr(TypePtr { elem, .. })
            | Type::Reference(TypeReference { elem, .. })
            | Type::Slice(TypeSlice { elem, .. }) => self.search_type(elem),
            Type::BareFn(tf) => {
                for input in &tf.inputs {
                    self.search_type(&input.ty);
                }
                self.search_return_type(&tf.output);
            }
            Type::ImplTrait(tyit) => {
                for b in &tyit.bounds {
                    if let syn::TypeParamBound::Trait(tyt) = b {
                        self.search_path(&tyt.path)
                    }
                }
            }
            Type::Infer(_) | Type::Macro(_) | Type::Never(_) => {}
            Type::Path(typ) => self.search_path(&typ.path),
            Type::TraitObject(tto) => self.search_type_param_bounds(&tto.bounds),
            Type::Tuple(tt) => {
                for e in &tt.elems {
                    self.search_type(e)
                }
            }
            _ => {}
        }
    }

    fn search_type_param_bounds(&mut self, bounds: &Punctuated<TypeParamBound, syn::token::Add>) {
        for b in bounds {
            if let syn::TypeParamBound::Trait(tpbt) = b {
                self.search_path(&tpbt.path)
            }
        }
    }

    fn search_return_type(&mut self, output: &ReturnType) {
        if let ReturnType::Type(_, ty) = &output {
            self.search_type(ty)
        }
    }

    fn search_path_arguments(&mut self, arguments: &syn::PathArguments) {
        match arguments {
            syn::PathArguments::None => {}
            syn::PathArguments::AngleBracketed(paab) => {
                for arg in &paab.args {
                    match arg {
                        syn::GenericArgument::Lifetime(_) => {}
                        syn::GenericArgument::Type(ty)
                        | syn::GenericArgument::Binding(Binding { ty, .. }) => self.search_type(ty),
                        syn::GenericArgument::Constraint(c) => {
                            self.search_type_param_bounds(&c.bounds)
                        }
                        syn::GenericArgument::Const(c) => self.search_expr(c),
                    }
                }
            }
            syn::PathArguments::Parenthesized(pas) => {
                self.search_return_type(&pas.output);
                for t in &pas.inputs {
                    self.search_type(t);
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use syn::{parse_quote, Item};

    use super::CppList;

    fn assert_found(cpp_list: &CppList) {
        assert!(!cpp_list.0.is_empty());
        assert!(cpp_list.0.iter().next().unwrap() == "xxx");
    }

    #[test]
    fn test_mod_plain_call() {
        let mut cpplist = CppList::default();
        let itm = Item::Mod(parse_quote! {
            mod foo {
                fn bar() {
                    ffi::xxx()
                }
            }
        });
        cpplist.search_item(&itm);
        assert_found(&cpplist);
    }

    #[test]
    fn test_plain_call() {
        let mut cpplist = CppList::default();
        let itm = Item::Fn(parse_quote! {
            fn bar() {
                ffi::xxx()
            }
        });
        cpplist.search_item(&itm);
        assert_found(&cpplist);
    }

    #[test]
    fn test_plain_call_with_semi() {
        let mut cpplist = CppList::default();
        let itm = Item::Fn(parse_quote! {
            fn bar() {
                ffi::xxx();
            }
        });
        cpplist.search_item(&itm);
        assert_found(&cpplist);
    }

    #[test]
    fn test_deep_nested_thingy() {
        let mut cpplist = CppList::default();
        let itm = Item::Fn(parse_quote! {
            fn bar() {
                a + 3 * foo(ffi::xxx());
            }
        });
        cpplist.search_item(&itm);
        assert_found(&cpplist);
    }

    #[test]
    fn test_ty_in_let() {
        let mut cpplist = CppList::default();
        let itm = Item::Fn(parse_quote! {
            fn bar() {
                let foo: ffi::xxx = bar();
            }
        });
        cpplist.search_item(&itm);
        assert_found(&cpplist);
    }

    #[test]
    fn test_ty_in_fn() {
        let mut cpplist = CppList::default();
        let itm = Item::Fn(parse_quote! {
            fn bar(a: &mut ffi::xxx) {
            }
        });
        cpplist.search_item(&itm);
        assert_found(&cpplist);
    }

    #[test]
    fn test_ty_in_fn_up() {
        let mut cpplist = CppList::default();
        let itm = Item::Fn(parse_quote! {
            fn bar(a: cxx::UniquePtr<ffi::xxx>) {
            }
        });
        cpplist.search_item(&itm);
        assert_found(&cpplist);
    }
}
<|endoftext|>