<commit_before>/// Comparisons against SCCS.
///
/// The weave algorithm used comes from the the SCCS program.  This can be installed on most Linux
/// distros by installing the package "cssc".

extern crate env_logger;
#[macro_use] extern crate log;
extern crate rand;
extern crate tempdir;
extern crate weave;

use rand::{Rng, SeedableRng, StdRng};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::env;
use std::fs::{File, remove_file};
use std::io::{BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus, Stdio};
use std::rc::Rc;
use tempdir::TempDir;
use weave::{DeltaWriter, NewWeave, Parser, SimpleNaming, Sink, Result};

/// Number of iterations to make.  Note that the default check is greater than O(n^2) so the test
/// will run very long if this is increased too much.
const ITERATION_COUNT: usize = 100;

/// Number of lines in the files.  Affects how convoluted the diffs are.
const FILE_SIZE: usize = 100;

/// Set to true to verify all previous deltas, not just the most recent.
const VERIFY_ALL_DELTAS: bool = true;

#[test]
fn sccs() {
    let _ = env_logger::init();

    if !has_sccs() {
        return;
    }

    let tdir = TempDir::new("sccstest").unwrap();
    let mut gen = Gen::new(tdir.path()).unwrap();

    // For debugging, this will cause the directory to not be removed.
    if env::var("KEEPTEMP").is_ok() {
        tdir.into_path();
    }

    gen.new_sccs();
    gen.new_weave();
    gen.weave_check();

    for i in 0 .. ITERATION_COUNT {
        gen.shuffle();
        gen.add_sccs_delta();
        gen.add_weave_delta(i + 1);

        // Checking with sccs is very slow.  Do we want to do it?
        // gen.sccs_check();
        gen.weave_check();
    }
}

/// Determine if we have the sccs command available.  If not, show an error, and return false.
fn has_sccs() -> bool {
    match Command::new("sccs").arg("-V").output() {
        Ok(_) => true,
        Err(_) => {
            error!("'sccs' not found in path, skipping tests, install 'cssc' to fix");
            false
        }
    }
}

/// Gen synthesizes a series of deltas, and can add them using SCCS to make a weave file, and later
/// to this weave implementation to compare the results.
struct Gen {
    /// The directory to write the files into.
    tdir: PathBuf,

    /// The name of the plain file related to it.
    sccs_plain: PathBuf,

    /// The current lines.
    nums: Vec<usize>,

    /// Each delta.  Sccs numbers the deltas from 1, so these are off by one.
    deltas: Vec<Vec<usize>>,

    /// A Rng for generating the shuffles.
    rand: StdRng,
}

impl Gen {
    fn new<P: AsRef<Path>>(tdir: P) -> Result<Gen> {
        let tdir = tdir.as_ref();
        let seed: &[_] = &[1, 2, 3, 4];
        Ok(Gen {
            tdir: tdir.to_owned(),
            sccs_plain: tdir.join("tfile"),
            nums: (1..FILE_SIZE+1).collect(),
            rand: SeedableRng::from_seed(seed),
            deltas: vec![],
        })
    }

    /// Perform a somewhat random modification of the data.  Choose some range of the numbers and
    /// reverse them.
    fn shuffle(&mut self) {
        let a = self.rand.gen_range(0, self.nums.len());
        let b = self.rand.gen_range(0, self.nums.len());

        let (a, b) = if a <= b { (a, b) } else { (b, a) };
        self.nums[a..b].reverse();
    }

    /// Write to a new sccs file, resulting in delta 1.
    fn new_sccs(&mut self) {
        self.emit_to(&self.sccs_plain);
        Command::new("sccs").args(&["admin", "-itfile", "-n", "s.tfile"])
            .current_dir(&self.tdir)
            .status()
            .expect("Unable to run sccs admin")
            .expect_success("Sccs command returned error");
        remove_file(&self.sccs_plain).expect("Unable to remove data file");

        /// Consider the deltas as canonical by the SCCS command, so store the first delta.
        self.deltas.push(self.nums.clone());
    }

    /// Add a new delta to the sccs file.
    fn add_sccs_delta(&mut self) {
        Command::new("sccs").args(&["get", "-e", "s.tfile"])
            .current_dir(&self.tdir)
            .stderr(Stdio::null())
            .stdout(Stdio::null())
            .status()
            .expect("Unable to run sccs get")
            .expect_success("sccs get failed");
        self.emit_to(&self.sccs_plain);
        Command::new("sccs").args(&["delta", "-yMessage", "s.tfile"])
            .current_dir(&self.tdir)
            .stderr(Stdio::null())
            .stdout(Stdio::null())
            .status()
            .expect("Unable to run sccs delta")
            .expect_success("sccs delta failed");

        self.deltas.push(self.nums.clone());
    }

    /// Emit the current numbers to the given name (in the temp dir).
    fn emit_to<P: AsRef<Path>>(&self, name: P) {
        let mut fd = File::create(self.tdir.join(name)).unwrap();
        for i in &self.nums {
            writeln!(&mut fd, "{}", i).unwrap();
        }
    }

    /// Check the output of "sccs get".  This is more of a sanity check.
    #[allow(dead_code)]
    fn sccs_check(&self) {
        for (i, del) in self.deltas.iter().enumerate() {
            self.sccs_check_one(i, del);
        }
    }

    #[allow(dead_code)]
    fn sccs_check_one(&self, num: usize, data: &[usize]) {
        let out = Command::new("sccs").args(&["get", &format!("-r1.{}", num+1), "-p", "s.tfile"])
            .current_dir(&self.tdir)
            .output()
            .expect("Unable to run sccs get");
        out.status.expect_success("Error running sccs get");
        let mut onums: Vec<usize> = vec![];
        for line in BufReader::new(&out.stdout[..]).lines() {
            let line = line.unwrap();
            onums.push(line.as_str().parse::<usize>().unwrap());
        }

        assert_eq!(data, &onums[..]);
    }

    /// Check that weave decodes all of the sccs files properly.
    fn weave_check(&self) {
        if VERIFY_ALL_DELTAS {
            // Verify all of the previous deltas.
            for (i, del) in self.deltas.iter().enumerate() {
                self.weave_sccs_check_one(i, del);
                self.weave_check_one(i, del);
            }
        } else {
            // This only checks the last delta for each one.  It will miss any bugs that result in
            // earlier deltas being unreadable.
            let del = self.deltas.iter().last().unwrap();
            self.weave_sccs_check_one(self.deltas.len() - 1, del);
            self.weave_check_one(self.deltas.len() - 1, del);
        }
    }

    fn weave_sccs_check_one(&self, num: usize, data: &[usize]) {
        let fd = File::open(self.tdir.join("s.tfile")).unwrap();
        let lines = BufReader::new(fd).lines();
        let dsink = Rc::new(RefCell::new(DeltaSink { nums: vec![] }));
        {
            let mut parser = Parser::new(lines, dsink.clone(), num + 1);
            match parser.parse_to(0) {
                Ok(0) => (),
                Ok(_) => panic!("Unexpected stop of parser"),
                Err(e) => panic!("Parser error: {:?}", e),
            }
        }

        assert_eq!(data, &dsink.borrow().nums[..]);
    }

    fn weave_check_one(&self, num: usize, data: &[usize]) {
        let fd = File::open(self.tdir.join("sample.weave")).unwrap();
        let lines = BufReader::new(fd).lines();
        let dsink = Rc::new(RefCell::new(DeltaSink { nums: vec![] }));
        {
            let mut parser = Parser::new(lines, dsink.clone(), num + 1);
            match parser.parse_to(0) {
                Ok(0) => (),
                Ok(_) => panic!("Unexpected stop of parser"),
                Err(e) => panic!("Parser error: {:?}", e),
            }
        }

        assert_eq!(data, &dsink.borrow().nums[..]);
    }

    fn new_weave(&mut self) {
        let mut tags = BTreeMap::new();
        tags.insert("name", "initial");
        let nc = SimpleNaming::new(&self.tdir, "sample", "weave", false);
        let mut nw = NewWeave::new(&nc, tags.into_iter()).unwrap();
        for i in &self.nums {
            writeln!(&mut nw, "{}", i).unwrap();
        }
        nw.close().unwrap();
    }

    fn add_weave_delta(&mut self, base: usize) {
        let name_value = format!("{}", base + 1);
        let mut tags = BTreeMap::new();
        tags.insert("name", name_value.as_str());
        let nc = SimpleNaming::new(&self.tdir, "sample", "weave", false);
        let mut delta = DeltaWriter::new(&nc, tags.into_iter(), base).unwrap();
        for i in &self.nums {
            writeln!(&mut delta, "{}", i).unwrap();
        }
        delta.close().unwrap();
    }
}

/// A Weave Sink that just collects the numbers in the given delta.
struct DeltaSink {
    nums: Vec<usize>,
}

impl Sink for DeltaSink {
    fn plain(&mut self, text: &str, keep: bool) -> Result<()> {
        if !keep {
            return Ok(())
        }

        self.nums.push(text.parse::<usize>()?);
        Ok(())
    }
}

/// A small utility to make asserting success easier.
trait Successful {
    fn expect_success(&self, msg: &str);
}

impl Successful for ExitStatus {
    fn expect_success(&self, msg: &str) {
        if !self.success() {
            panic!(msg.to_string());
        }
    }
}
<commit_msg>weave: Better handle sccs not present<commit_after>/// Comparisons against SCCS.
///
/// The weave algorithm used comes from the the SCCS program.  This can be installed on most Linux
/// distros by installing the package "cssc".

extern crate env_logger;
#[macro_use] extern crate log;
extern crate rand;
extern crate tempdir;
extern crate weave;

use rand::{Rng, SeedableRng, StdRng};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::env;
use std::fs::{File, remove_file};
use std::io::{BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus, Stdio};
use std::rc::Rc;
use tempdir::TempDir;
use weave::{DeltaWriter, NewWeave, Parser, SimpleNaming, Sink, Result};

/// Number of iterations to make.  Note that the default check is greater than O(n^2) so the test
/// will run very long if this is increased too much.
const ITERATION_COUNT: usize = 100;

/// Number of lines in the files.  Affects how convoluted the diffs are.
const FILE_SIZE: usize = 100;

/// Set to true to verify all previous deltas, not just the most recent.
const VERIFY_ALL_DELTAS: bool = true;

#[test]
fn sccs() {
    let _ = env_logger::init();

    // Normally, detect the SCCS command being present, and use it for additional tests.  It can be
    // ignored by setting NO_SCCS=1 in the environment.
    let use_sccs = has_sccs() && env::var("NO_SCCS").is_err();

    let tdir = TempDir::new("sccstest").unwrap();
    let mut gen = Gen::new(tdir.path(), use_sccs).unwrap();

    // For debugging, this will cause the directory to not be removed.
    if env::var("KEEPTEMP").is_ok() {
        tdir.into_path();
    }

    gen.new_sccs();
    gen.new_weave();
    gen.next_delta();
    gen.weave_check();

    for i in 0 .. ITERATION_COUNT {
        gen.shuffle();
        gen.add_sccs_delta();
        gen.add_weave_delta(i + 1);
        gen.next_delta();

        // Checking with sccs is very slow.  Do we want to do it?
        // gen.sccs_check();
        gen.weave_check();
    }
}

/// Determine if we have the sccs command available.  If not, show an error, and return false.
fn has_sccs() -> bool {
    match Command::new("sccs").arg("-V").output() {
        Ok(_) => true,
        Err(_) => {
            error!("'sccs' not found in path, skipping some tests, install 'cssc' to fix");
            false
        }
    }
}

/// Gen synthesizes a series of deltas, and can add them using SCCS to make a weave file, and later
/// to this weave implementation to compare the results.
struct Gen {
    /// The directory to write the files into.
    tdir: PathBuf,

    /// The name of the plain file related to it.
    sccs_plain: PathBuf,

    /// The current lines.
    nums: Vec<usize>,

    /// Each delta.  Sccs numbers the deltas from 1, so these are off by one.
    deltas: Vec<Vec<usize>>,

    /// A Rng for generating the shuffles.
    rand: StdRng,

    /// Is sccs enabled.
    use_sccs: bool,
}

impl Gen {
    fn new<P: AsRef<Path>>(tdir: P, use_sccs: bool) -> Result<Gen> {
        let tdir = tdir.as_ref();
        let seed: &[_] = &[1, 2, 3, 4];
        Ok(Gen {
            tdir: tdir.to_owned(),
            sccs_plain: tdir.join("tfile"),
            nums: (1..FILE_SIZE+1).collect(),
            rand: SeedableRng::from_seed(seed),
            deltas: vec![],
            use_sccs: use_sccs,
        })
    }

    /// Perform a somewhat random modification of the data.  Choose some range of the numbers and
    /// reverse them.
    fn shuffle(&mut self) {
        let a = self.rand.gen_range(0, self.nums.len());
        let b = self.rand.gen_range(0, self.nums.len());

        let (a, b) = if a <= b { (a, b) } else { (b, a) };
        self.nums[a..b].reverse();
    }

    fn next_delta(&mut self) {
        self.deltas.push(self.nums.clone())
    }

    /// Write to a new sccs file, resulting in delta 1.
    fn new_sccs(&mut self) {
        if !self.use_sccs {
            return;
        }

        self.emit_to(&self.sccs_plain);
        Command::new("sccs").args(&["admin", "-itfile", "-n", "s.tfile"])
            .current_dir(&self.tdir)
            .status()
            .expect("Unable to run sccs admin")
            .expect_success("Sccs command returned error");
        remove_file(&self.sccs_plain).expect("Unable to remove data file");
    }

    /// Add a new delta to the sccs file.
    fn add_sccs_delta(&mut self) {
        if !self.use_sccs {
            return;
        }

        Command::new("sccs").args(&["get", "-e", "s.tfile"])
            .current_dir(&self.tdir)
            .stderr(Stdio::null())
            .stdout(Stdio::null())
            .status()
            .expect("Unable to run sccs get")
            .expect_success("sccs get failed");
        self.emit_to(&self.sccs_plain);
        Command::new("sccs").args(&["delta", "-yMessage", "s.tfile"])
            .current_dir(&self.tdir)
            .stderr(Stdio::null())
            .stdout(Stdio::null())
            .status()
            .expect("Unable to run sccs delta")
            .expect_success("sccs delta failed");
    }

    /// Emit the current numbers to the given name (in the temp dir).
    fn emit_to<P: AsRef<Path>>(&self, name: P) {
        let mut fd = File::create(self.tdir.join(name)).unwrap();
        for i in &self.nums {
            writeln!(&mut fd, "{}", i).unwrap();
        }
    }

    /// Check the output of "sccs get".  This is more of a sanity check.
    #[allow(dead_code)]
    fn sccs_check(&self) {
        for (i, del) in self.deltas.iter().enumerate() {
            self.sccs_check_one(i, del);
        }
    }

    #[allow(dead_code)]
    fn sccs_check_one(&self, num: usize, data: &[usize]) {
        if !self.use_sccs {
            return;
        }

        let out = Command::new("sccs").args(&["get", &format!("-r1.{}", num+1), "-p", "s.tfile"])
            .current_dir(&self.tdir)
            .output()
            .expect("Unable to run sccs get");
        out.status.expect_success("Error running sccs get");
        let mut onums: Vec<usize> = vec![];
        for line in BufReader::new(&out.stdout[..]).lines() {
            let line = line.unwrap();
            onums.push(line.as_str().parse::<usize>().unwrap());
        }

        assert_eq!(data, &onums[..]);
    }

    /// Check that weave decodes all of the sccs files properly.
    fn weave_check(&self) {
        if VERIFY_ALL_DELTAS {
            // Verify all of the previous deltas.
            for (i, del) in self.deltas.iter().enumerate() {
                self.weave_sccs_check_one(i, del);
                self.weave_check_one(i, del);
            }
        } else {
            // This only checks the last delta for each one.  It will miss any bugs that result in
            // earlier deltas being unreadable.
            let del = self.deltas.iter().last().unwrap();
            self.weave_sccs_check_one(self.deltas.len() - 1, del);
            self.weave_check_one(self.deltas.len() - 1, del);
        }
    }

    fn weave_sccs_check_one(&self, num: usize, data: &[usize]) {
        if !self.use_sccs {
            return;
        }

        let fd = File::open(self.tdir.join("s.tfile")).unwrap();
        let lines = BufReader::new(fd).lines();
        let dsink = Rc::new(RefCell::new(DeltaSink { nums: vec![] }));
        {
            let mut parser = Parser::new(lines, dsink.clone(), num + 1);
            match parser.parse_to(0) {
                Ok(0) => (),
                Ok(_) => panic!("Unexpected stop of parser"),
                Err(e) => panic!("Parser error: {:?}", e),
            }
        }

        assert_eq!(data, &dsink.borrow().nums[..]);
    }

    fn weave_check_one(&self, num: usize, data: &[usize]) {
        let fd = File::open(self.tdir.join("sample.weave")).unwrap();
        let lines = BufReader::new(fd).lines();
        let dsink = Rc::new(RefCell::new(DeltaSink { nums: vec![] }));
        {
            let mut parser = Parser::new(lines, dsink.clone(), num + 1);
            match parser.parse_to(0) {
                Ok(0) => (),
                Ok(_) => panic!("Unexpected stop of parser"),
                Err(e) => panic!("Parser error: {:?}", e),
            }
        }

        assert_eq!(data, &dsink.borrow().nums[..]);
    }

    fn new_weave(&mut self) {
        let mut tags = BTreeMap::new();
        tags.insert("name", "initial");
        let nc = SimpleNaming::new(&self.tdir, "sample", "weave", false);
        let mut nw = NewWeave::new(&nc, tags.into_iter()).unwrap();
        for i in &self.nums {
            writeln!(&mut nw, "{}", i).unwrap();
        }
        nw.close().unwrap();
    }

    fn add_weave_delta(&mut self, base: usize) {
        let name_value = format!("{}", base + 1);
        let mut tags = BTreeMap::new();
        tags.insert("name", name_value.as_str());
        let nc = SimpleNaming::new(&self.tdir, "sample", "weave", false);
        let mut delta = DeltaWriter::new(&nc, tags.into_iter(), base).unwrap();
        for i in &self.nums {
            writeln!(&mut delta, "{}", i).unwrap();
        }
        delta.close().unwrap();
    }
}

/// A Weave Sink that just collects the numbers in the given delta.
struct DeltaSink {
    nums: Vec<usize>,
}

impl Sink for DeltaSink {
    fn plain(&mut self, text: &str, keep: bool) -> Result<()> {
        if !keep {
            return Ok(())
        }

        self.nums.push(text.parse::<usize>()?);
        Ok(())
    }
}

/// A small utility to make asserting success easier.
trait Successful {
    fn expect_success(&self, msg: &str);
}

impl Successful for ExitStatus {
    fn expect_success(&self, msg: &str) {
        if !self.success() {
            panic!(msg.to_string());
        }
    }
}
<|endoftext|>


<commit_before>use postgres::{Connection, SslMode};
use postgis as geom;
use core::grid::{Extent,EPSG_3857};

pub struct PostgisInput {
    pub connection_url: &'static str
}

impl PostgisInput {
    pub fn detect_layers(&self) -> String {
        let conn = Connection::connect(self.connection_url, SslMode::None).unwrap();
        let stmt = conn.prepare("SELECT * FROM geometry_columns").unwrap();
        for row in &stmt.query(&[]).unwrap() {
            let table_name: String = row.get("f_table_name");
            println!("{}", table_name);
        }
        let rows = conn.query("SELECT * FROM geometry_columns LIMIT 1", &[]).unwrap();
        let table_name: String = rows.get(0).get("f_table_name");
        table_name
    }
    pub fn get_features(&self, layer: &str, extent: &Extent) -> geom::Point<EPSG_3857> {
        geom::Point::<EPSG_3857>::new(960000.0, 6002729.0)
    }
}

#[cfg(feature = "dbtest")]
#[test]
pub fn test_detect_layers() {
    let pg = PostgisInput {connection_url: "postgresql://pi@%2Frun%2Fpostgresql/osm2vectortiles"};
    //"postgresql://pi@localhost/osm2vectortiles";
    let table_name = pg.detect_layers();
    assert_eq!(table_name, "osm_admin_linestring");
}
<commit_msg>select_geometries test code<commit_after>use postgres::{Connection, SslMode};
use postgis as geom;
use core::grid::{Extent,EPSG_3857};

pub struct PostgisInput {
    pub connection_url: &'static str
}

impl PostgisInput {
    pub fn detect_layers(&self) -> String {
        let conn = Connection::connect(self.connection_url, SslMode::None).unwrap();
        let stmt = conn.prepare("SELECT * FROM geometry_columns").unwrap();
        for row in &stmt.query(&[]).unwrap() {
            let table_name: String = row.get("f_table_name");
            println!("{}", table_name);
        }
        let rows = conn.query("SELECT * FROM geometry_columns LIMIT 1", &[]).unwrap();
        let table_name: String = rows.get(0).get("f_table_name");
        table_name
    }
    pub fn select_geometries(&self) {
        let conn = Connection::connect(self.connection_url, SslMode::None).unwrap();
        let stmt = conn.prepare("SELECT geometry FROM osm_place_point LIMIT 2").unwrap();
        for row in &stmt.query(&[]).unwrap() {
            println!(">>>>>> {}", row.get::<_, geom::Point<EPSG_3857>>("geometry"));
        }
        let stmt = conn.prepare("SELECT geometry FROM osm_water_linestring LIMIT 2").unwrap();
        for row in &stmt.query(&[]).unwrap() {
            println!(">>>>>> {}", row.get::<_, geom::LineString<geom::Point<EPSG_3857>>>("geometry"));
        }
    }
    pub fn get_features(&self, layer: &str, extent: &Extent) -> geom::Point<EPSG_3857> {
        geom::Point::<EPSG_3857>::new(960000.0, 6002729.0)
    }
}

#[cfg(feature = "dbtest")]
#[test]
pub fn test_geometry_types() {
    let pg = PostgisInput {connection_url: "postgresql://pi@%2Frun%2Fpostgresql/osm2vectortiles"};
    pg.select_geometries();
}

#[cfg(feature = "dbtest")]
#[test]
pub fn test_detect_layers() {
    let pg = PostgisInput {connection_url: "postgresql://pi@%2Frun%2Fpostgresql/osm2vectortiles"};
    //"postgresql://pi@localhost/osm2vectortiles";
    let table_name = pg.detect_layers();
    assert_eq!(table_name, "osm_admin_linestring");
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#![warn(
    clippy::all,
    clippy::restriction,
    clippy::pedantic,
    clippy::nursery,
    clippy::cargo
)]

mod error;
mod types;

use anyhow::{bail, Context, Result};
use clap::value_t_or_exit;
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
use std::collections::BTreeMap;
use std::fs::File;
use std::io::{BufReader, BufWriter, Read, Write};
use std::path;
use types::{PackType, Platform};

const DEFAULT_PACKLIST: &str = include_str!("packlist.yaml");
// This is to ensure that all progress bar prefixes are aligned.
const PROGRESS_PREFIX_LEN: usize = 24;

type PackListPlatform = BTreeMap<PackType, Vec<path::PathBuf>>;

#[derive(Debug, serde::Deserialize)]
struct PackList(pub BTreeMap<Platform, PackListPlatform>);

#[derive(Debug, serde::Serialize)]
struct HashSum(String);

#[derive(Debug, serde::Serialize)]
struct PackManifest {
    files: BTreeMap<PackType, HashSum>,
}

fn default_progress_bar(len: u64) -> indicatif::ProgressBar {
    let pb = indicatif::ProgressBar::new(len as u64 * 2);
    pb.set_style(
        indicatif::ProgressStyle::default_bar()
            .template("{prefix:.bold}▕{bar:.magenta}▏{msg}")
            .progress_chars("█▓▒░  "),
    );
    pb
}

fn pack(
    platform: &Platform,
    dist_dir: &std::path::PathBuf,
    pack_list: &PackList,
    output_directory: &std::path::PathBuf,
) -> Result<Vec<(PackType, path::PathBuf)>> {
    let pb = default_progress_bar(pack_list.0.len() as u64 * 2 - 1);
    pb.set_prefix(&format!(
        "{:width$}",
        "Packing archives",
        width = PROGRESS_PREFIX_LEN
    ));
    let packtype_paths = pack_list
        .0
        .get(platform)
        .ok_or_else(|| error::Error::MissingPlatformDefinition(*platform))?;
    let res = packtype_paths
        .into_par_iter()
        .map(|(pack_type, pack_files)| {
            let output_path = path::Path::new(output_directory).join(format!("{}.tar", pack_type));
            let mut tar = tar::Builder::new(File::create(&output_path).with_context(|| {
                format!(
                    "Couldn't open file for writing: {}",
                    &output_path.to_string_lossy()
                )
            })?);
            // MacOS uses symlinks for bundling multiple framework versions and pointing
            // to the "Current" one.
            tar.follow_symlinks(false);
            pack_platform(platform, dist_dir, pack_files, pack_type, &mut tar)?;
            pb.inc(1);
            tar.finish()?;
            pb.inc(1);

            Ok((*pack_type, output_path))
        })
        .collect();

    pb.finish();
    res
}

fn pack_platform(
    platform: &Platform,
    dist_dir: &std::path::PathBuf,
    pack_files: &[path::PathBuf],
    pack_type: &PackType,
    tar_builder: &mut tar::Builder<File>,
) -> Result<()> {
    let base_dir = match platform {
        Platform::Mac => path::Path::new(dist_dir).join("mac"),
        // TODO: Verify this.
        Platform::Linux => path::Path::new(dist_dir).join("linux-unpacked"),
        Platform::Windows => path::Path::new(dist_dir).join("win-unpacked"),
    };

    for f in pack_files {
        let full_path = path::Path::new(&base_dir).join(f);
        if !full_path.exists() {
            bail!(error::Error::MissingPackFile(
                *platform, *pack_type, full_path,
            ));
        }
        if full_path.is_file() {
            tar_builder.append_path_with_name(full_path, f)?;
        } else if full_path.is_dir() {
            tar_builder.append_dir_all(f, full_path)?;
        }
    }

    Ok(())
}

fn sha256_digest<R: Read>(mut reader: R) -> Result<HashSum> {
    use sha2::{Digest, Sha256};

    let mut sha256 = Sha256::new();
    std::io::copy(&mut reader, &mut sha256)?;
    let hash = sha256.result();

    Ok(HashSum(data_encoding::HEXLOWER.encode(&hash)))
}

fn main() -> Result<(), anyhow::Error> {
    // Ensure to define all env vars used here in the BUCK env, too.
    let args = clap::App::new(env!("CARGO_PKG_NAME"))
        .version(env!("CARGO_PKG_VERSION"))
        .author(env!("CARGO_PKG_AUTHORS"))
        .about("Split the Flipper distribution into smaller, cacheable artifacts")
        .arg(
            clap::Arg::from_usage("-o, --output [DIRECTORY] 'Directory to write output files to.'")
                .default_value("."),
        )
        .arg(
            clap::Arg::from_usage("-d, --dist [DIRECTORY] 'Flipper dist directory to read from.'")
                .default_value("~/fbsource/xplat/sonar/dist"),
        )
        .arg(clap::Arg::from_usage(
            "-p, --packlist=packlist.yaml 'Custom list of files to pack.'",
        ))
        .arg(clap::Arg::from_usage(
            "--no-compression 'Skip compressing the archives (for debugging)'",
        ))
        .arg(
            clap::Arg::from_usage("[PLATFORM] 'Platform to build for'")
                .case_insensitive(true)
                .required(true)
                .possible_values(&Platform::variants()),
        )
        .get_matches();

    let platform = value_t_or_exit!(args.value_of("PLATFORM"), Platform);
    let dist_dir = path::PathBuf::from(
        shellexpand::tilde(args.value_of("dist").expect("argument has default")).to_string(),
    );
    let compress = !args.is_present("no-compression");
    let pack_list_str = args
        .value_of("packlist")
        .map(|f| {
            std::fs::read_to_string(f)
                .unwrap_or_else(|e| panic!("Failed to open packfile {}: {}", f, e))
        })
        .unwrap_or_else(|| DEFAULT_PACKLIST.to_string());
    let pack_list: PackList =
        serde_yaml::from_str(&pack_list_str).expect("Failed to deserialize YAML packlist.");
    let output_directory =
        &path::PathBuf::from(args.value_of("output").expect("argument has default"));
    std::fs::create_dir_all(output_directory).with_context(|| {
        format!(
            "Failed to create output directory '{}'.",
            output_directory.to_string_lossy()
        )
    })?;
    let archive_paths = pack(&platform, &dist_dir, &pack_list, output_directory)?;
    let compressed_archive_paths = if compress {
        compress_paths(&archive_paths)?
    } else {
        archive_paths
    };
    manifest(&compressed_archive_paths, &output_directory)?;

    Ok(())
}

/// Takes a list of archive paths, compresses them with LZMA and returns
/// the updated paths.
/// TODO: Remove compressed artifacts.
fn compress_paths(
    archive_paths: &[(PackType, path::PathBuf)],
) -> Result<Vec<(PackType, path::PathBuf)>> {
    let pb = default_progress_bar(archive_paths.len() as u64 - 1);
    pb.set_prefix(&format!(
        "{:width$}",
        "Compressing archives",
        width = PROGRESS_PREFIX_LEN
    ));
    let res = archive_paths
        .into_par_iter()
        .map(|(pack_type, path)| {
            let input_file = File::open(&path).with_context(|| {
                format!("Failed to open archive '{}'.", &path.to_string_lossy())
            })?;
            let mut reader = BufReader::new(input_file);
            let mut output_path = path::PathBuf::from(path);
            output_path.set_extension("tar.xz");
            let output_file: File = File::create(&output_path).with_context(|| {
                format!(
                    "Failed opening compressed archive '{}' for writing.",
                    &output_path.to_string_lossy()
                )
            })?;
            let writer = BufWriter::new(output_file);
            let mut encoder = xz2::write::XzEncoder::new(writer, 9);
            std::io::copy(&mut reader, &mut encoder)?;
            pb.inc(1);
            Ok((*pack_type, output_path))
        })
        .collect::<Result<Vec<(PackType, path::PathBuf)>>>()?;
    pb.finish();

    Ok(res)
}

fn manifest(
    archive_paths: &[(PackType, path::PathBuf)],
    output_directory: &path::PathBuf,
) -> Result<path::PathBuf> {
    let archive_manifest = gen_manifest(&archive_paths)?;
    write_manifest(&output_directory, &archive_manifest)
}

fn write_manifest(
    output_directory: &path::PathBuf,
    archive_manifest: &PackManifest,
) -> Result<path::PathBuf> {
    let path = path::PathBuf::from(output_directory).join("manifest.json");
    let mut file = File::create(&path)
        .with_context(|| format!("Failed to write manifest to {}", &path.to_string_lossy()))?;
    file.write_all(&serde_json::to_string_pretty(archive_manifest)?.as_bytes())?;
    Ok(path)
}

fn gen_manifest(archive_paths: &[(PackType, path::PathBuf)]) -> Result<PackManifest> {
    Ok(PackManifest {
        files: gen_manifest_files(archive_paths)?,
    })
}

fn gen_manifest_files(
    archive_paths: &[(PackType, path::PathBuf)],
) -> Result<BTreeMap<PackType, HashSum>> {
    let pb = default_progress_bar(archive_paths.len() as u64 - 1);
    pb.set_prefix(&format!(
        "{:width$}",
        "Computing manifest",
        width = PROGRESS_PREFIX_LEN
    ));
    let res = archive_paths
        .into_par_iter()
        .map(|(pack_type, path)| {
            let reader = BufReader::new(File::open(path)?);
            let hash = sha256_digest(reader)?;
            pb.inc(1);
            Ok((*pack_type, hash))
        })
        .collect::<Result<Vec<_>>>()?
        .into_iter()
        .fold(
            BTreeMap::new(),
            |mut acc: BTreeMap<PackType, HashSum>, (pack_type, hash_sum)| {
                acc.insert(pack_type, hash_sum);
                acc
            },
        );
    pb.finish();
    Ok(res)
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_included_packlist_parses() {
        let res: PackList =
            serde_yaml::from_str(DEFAULT_PACKLIST).expect("Default packlist doesn't deserialize");
        assert_eq!(res.0.len(), 3);
    }

    #[test]
    fn test_manifest() -> anyhow::Result<()> {
        let tmp_dir = tempdir::TempDir::new("manifest_test")?;
        let artifact_path = path::PathBuf::from(tmp_dir.path()).join("core.tar");
        let mut artifact = File::create(&artifact_path)?;
        artifact.write_all("Hello World.".as_bytes())?;

        let archive_paths = &[(PackType::Core, artifact_path)];
        let path = manifest(archive_paths, &tmp_dir.path().to_path_buf())?;

        let manifest_content = std::fs::read_to_string(&path)?;

        assert_eq!(manifest_content, "{\n  \"files\": {\n    \"core\": \"f4bb1975bf1f81f76ce824f7536c1e101a8060a632a52289d530a6f600d52c92\"\n  }\n}");

        Ok(())
    }
}
<commit_msg>Prepare for rustfmt 2.0<commit_after>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#![warn(
    clippy::all,
    clippy::restriction,
    clippy::pedantic,
    clippy::nursery,
    clippy::cargo
)]

mod error;
mod types;

use anyhow::{bail, Context, Result};
use clap::value_t_or_exit;
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
use std::collections::BTreeMap;
use std::fs::File;
use std::io::{BufReader, BufWriter, Read, Write};
use std::path;
use types::{PackType, Platform};

const DEFAULT_PACKLIST: &str = include_str!("packlist.yaml");
// This is to ensure that all progress bar prefixes are aligned.
const PROGRESS_PREFIX_LEN: usize = 24;

type PackListPlatform = BTreeMap<PackType, Vec<path::PathBuf>>;

#[derive(Debug, serde::Deserialize)]
struct PackList(pub BTreeMap<Platform, PackListPlatform>);

#[derive(Debug, serde::Serialize)]
struct HashSum(String);

#[derive(Debug, serde::Serialize)]
struct PackManifest {
    files: BTreeMap<PackType, HashSum>,
}

fn default_progress_bar(len: u64) -> indicatif::ProgressBar {
    let pb = indicatif::ProgressBar::new(len as u64 * 2);
    pb.set_style(
        indicatif::ProgressStyle::default_bar()
            .template("{prefix:.bold}▕{bar:.magenta}▏{msg}")
            .progress_chars("█▓▒░  "),
    );
    pb
}

fn pack(
    platform: &Platform,
    dist_dir: &std::path::PathBuf,
    pack_list: &PackList,
    output_directory: &std::path::PathBuf,
) -> Result<Vec<(PackType, path::PathBuf)>> {
    let pb = default_progress_bar(pack_list.0.len() as u64 * 2 - 1);
    pb.set_prefix(&format!(
        "{:width$}",
        "Packing archives",
        width = PROGRESS_PREFIX_LEN
    ));
    let packtype_paths = pack_list
        .0
        .get(platform)
        .ok_or_else(|| error::Error::MissingPlatformDefinition(*platform))?;
    let res = packtype_paths
        .into_par_iter()
        .map(|(pack_type, pack_files)| {
            let output_path = path::Path::new(output_directory).join(format!("{}.tar", pack_type));
            let mut tar = tar::Builder::new(File::create(&output_path).with_context(|| {
                format!(
                    "Couldn't open file for writing: {}",
                    &output_path.to_string_lossy()
                )
            })?);
            // MacOS uses symlinks for bundling multiple framework versions and pointing
            // to the "Current" one.
            tar.follow_symlinks(false);
            pack_platform(platform, dist_dir, pack_files, pack_type, &mut tar)?;
            pb.inc(1);
            tar.finish()?;
            pb.inc(1);

            Ok((*pack_type, output_path))
        })
        .collect();

    pb.finish();
    res
}

fn pack_platform(
    platform: &Platform,
    dist_dir: &std::path::PathBuf,
    pack_files: &[path::PathBuf],
    pack_type: &PackType,
    tar_builder: &mut tar::Builder<File>,
) -> Result<()> {
    let base_dir = match platform {
        Platform::Mac => path::Path::new(dist_dir).join("mac"),
        // TODO: Verify this.
        Platform::Linux => path::Path::new(dist_dir).join("linux-unpacked"),
        Platform::Windows => path::Path::new(dist_dir).join("win-unpacked"),
    };

    for f in pack_files {
        let full_path = path::Path::new(&base_dir).join(f);
        if !full_path.exists() {
            bail!(error::Error::MissingPackFile(
                *platform, *pack_type, full_path,
            ));
        }
        if full_path.is_file() {
            tar_builder.append_path_with_name(full_path, f)?;
        } else if full_path.is_dir() {
            tar_builder.append_dir_all(f, full_path)?;
        }
    }

    Ok(())
}

fn sha256_digest<R: Read>(mut reader: R) -> Result<HashSum> {
    use sha2::{Digest, Sha256};

    let mut sha256 = Sha256::new();
    std::io::copy(&mut reader, &mut sha256)?;
    let hash = sha256.result();

    Ok(HashSum(data_encoding::HEXLOWER.encode(&hash)))
}

fn main() -> Result<(), anyhow::Error> {
    // Ensure to define all env vars used here in the BUCK env, too.
    let args = clap::App::new(env!("CARGO_PKG_NAME"))
        .version(env!("CARGO_PKG_VERSION"))
        .author(env!("CARGO_PKG_AUTHORS"))
        .about("Split the Flipper distribution into smaller, cacheable artifacts")
        .arg(
            clap::Arg::from_usage("-o, --output [DIRECTORY] 'Directory to write output files to.'")
                .default_value("."),
        )
        .arg(
            clap::Arg::from_usage("-d, --dist [DIRECTORY] 'Flipper dist directory to read from.'")
                .default_value("~/fbsource/xplat/sonar/dist"),
        )
        .arg(clap::Arg::from_usage(
            "-p, --packlist=packlist.yaml 'Custom list of files to pack.'",
        ))
        .arg(clap::Arg::from_usage(
            "--no-compression 'Skip compressing the archives (for debugging)'",
        ))
        .arg(
            clap::Arg::from_usage("[PLATFORM] 'Platform to build for'")
                .case_insensitive(true)
                .required(true)
                .possible_values(&Platform::variants()),
        )
        .get_matches();

    let platform = value_t_or_exit!(args.value_of("PLATFORM"), Platform);
    let dist_dir = path::PathBuf::from(
        shellexpand::tilde(args.value_of("dist").expect("argument has default")).to_string(),
    );
    let compress = !args.is_present("no-compression");
    let pack_list_str = args
        .value_of("packlist")
        .map(|f| {
            std::fs::read_to_string(f)
                .unwrap_or_else(|e| panic!("Failed to open packfile {}: {}", f, e))
        })
        .unwrap_or_else(|| DEFAULT_PACKLIST.to_string());
    let pack_list: PackList =
        serde_yaml::from_str(&pack_list_str).expect("Failed to deserialize YAML packlist.");
    let output_directory =
        &path::PathBuf::from(args.value_of("output").expect("argument has default"));
    std::fs::create_dir_all(output_directory).with_context(|| {
        format!(
            "Failed to create output directory '{}'.",
            output_directory.to_string_lossy()
        )
    })?;
    let archive_paths = pack(&platform, &dist_dir, &pack_list, output_directory)?;
    let compressed_archive_paths = if compress {
        compress_paths(&archive_paths)?
    } else {
        archive_paths
    };
    manifest(&compressed_archive_paths, &output_directory)?;

    Ok(())
}

/// Takes a list of archive paths, compresses them with LZMA and returns
/// the updated paths.
/// TODO: Remove compressed artifacts.
fn compress_paths(
    archive_paths: &[(PackType, path::PathBuf)],
) -> Result<Vec<(PackType, path::PathBuf)>> {
    let pb = default_progress_bar(archive_paths.len() as u64 - 1);
    pb.set_prefix(&format!(
        "{:width$}",
        "Compressing archives",
        width = PROGRESS_PREFIX_LEN
    ));
    let res = archive_paths
        .into_par_iter()
        .map(|(pack_type, path)| {
            let input_file = File::open(&path).with_context(|| {
                format!("Failed to open archive '{}'.", &path.to_string_lossy())
            })?;
            let mut reader = BufReader::new(input_file);
            let mut output_path = path::PathBuf::from(path);
            output_path.set_extension("tar.xz");
            let output_file: File = File::create(&output_path).with_context(|| {
                format!(
                    "Failed opening compressed archive '{}' for writing.",
                    &output_path.to_string_lossy()
                )
            })?;
            let writer = BufWriter::new(output_file);
            let mut encoder = xz2::write::XzEncoder::new(writer, 9);
            std::io::copy(&mut reader, &mut encoder)?;
            pb.inc(1);
            Ok((*pack_type, output_path))
        })
        .collect::<Result<Vec<(PackType, path::PathBuf)>>>()?;
    pb.finish();

    Ok(res)
}

fn manifest(
    archive_paths: &[(PackType, path::PathBuf)],
    output_directory: &path::PathBuf,
) -> Result<path::PathBuf> {
    let archive_manifest = gen_manifest(&archive_paths)?;
    write_manifest(&output_directory, &archive_manifest)
}

fn write_manifest(
    output_directory: &path::PathBuf,
    archive_manifest: &PackManifest,
) -> Result<path::PathBuf> {
    let path = path::PathBuf::from(output_directory).join("manifest.json");
    let mut file = File::create(&path)
        .with_context(|| format!("Failed to write manifest to {}", &path.to_string_lossy()))?;
    file.write_all(&serde_json::to_string_pretty(archive_manifest)?.as_bytes())?;
    Ok(path)
}

fn gen_manifest(archive_paths: &[(PackType, path::PathBuf)]) -> Result<PackManifest> {
    Ok(PackManifest {
        files: gen_manifest_files(archive_paths)?,
    })
}

fn gen_manifest_files(
    archive_paths: &[(PackType, path::PathBuf)],
) -> Result<BTreeMap<PackType, HashSum>> {
    let pb = default_progress_bar(archive_paths.len() as u64 - 1);
    pb.set_prefix(&format!(
        "{:width$}",
        "Computing manifest",
        width = PROGRESS_PREFIX_LEN
    ));
    let res = archive_paths
        .into_par_iter()
        .map(|(pack_type, path)| {
            let reader = BufReader::new(File::open(path)?);
            let hash = sha256_digest(reader)?;
            pb.inc(1);
            Ok((*pack_type, hash))
        })
        .collect::<Result<Vec<_>>>()?
        .into_iter()
        .fold(
            BTreeMap::new(),
            |mut acc: BTreeMap<PackType, HashSum>, (pack_type, hash_sum)| {
                acc.insert(pack_type, hash_sum);
                acc
            },
        );
    pb.finish();
    Ok(res)
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_included_packlist_parses() {
        let res: PackList =
            serde_yaml::from_str(DEFAULT_PACKLIST).expect("Default packlist doesn't deserialize");
        assert_eq!(res.0.len(), 3);
    }

    #[test]
    fn test_manifest() -> anyhow::Result<()> {
        let tmp_dir = tempdir::TempDir::new("manifest_test")?;
        let artifact_path = path::PathBuf::from(tmp_dir.path()).join("core.tar");
        let mut artifact = File::create(&artifact_path)?;
        artifact.write_all("Hello World.".as_bytes())?;

        let archive_paths = &[(PackType::Core, artifact_path)];
        let path = manifest(archive_paths, &tmp_dir.path().to_path_buf())?;

        let manifest_content = std::fs::read_to_string(&path)?;

        assert_eq!(
            manifest_content,
            "{\n  \"files\": {\n    \"core\": \"f4bb1975bf1f81f76ce824f7536c1e101a8060a632a52289d530a6f600d52c92\"\n  }\n}"
        );

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


<commit_before>//! Diagnostics produced by `hir_def`.

use std::any::Any;
use stdx::format_to;

use cfg::{CfgExpr, CfgOptions, DnfExpr};
use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
use hir_expand::{HirFileId, InFile};
use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};

use crate::{db::DefDatabase, DefWithBodyId};

pub fn validate_body(db: &dyn DefDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) {
    let source_map = db.body_with_source_map(owner).1;
    source_map.add_diagnostics(db, sink);
}

// Diagnostic: unresolved-module
//
// This diagnostic is triggered if rust-analyzer is unable to discover referred module.
#[derive(Debug)]
pub struct UnresolvedModule {
    pub file: HirFileId,
    pub decl: AstPtr<ast::Module>,
    pub candidate: String,
}

impl Diagnostic for UnresolvedModule {
    fn code(&self) -> DiagnosticCode {
        DiagnosticCode("unresolved-module")
    }
    fn message(&self) -> String {
        "unresolved module".to_string()
    }
    fn display_source(&self) -> InFile<SyntaxNodePtr> {
        InFile::new(self.file, self.decl.clone().into())
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

// Diagnostic: unresolved-extern-crate
//
// This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate.
#[derive(Debug)]
pub struct UnresolvedExternCrate {
    pub file: HirFileId,
    pub item: AstPtr<ast::ExternCrate>,
}

impl Diagnostic for UnresolvedExternCrate {
    fn code(&self) -> DiagnosticCode {
        DiagnosticCode("unresolved-extern-crate")
    }
    fn message(&self) -> String {
        "unresolved extern crate".to_string()
    }
    fn display_source(&self) -> InFile<SyntaxNodePtr> {
        InFile::new(self.file, self.item.clone().into())
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

// Diagnostic: unresolved-import
//
// This diagnostic is triggered if rust-analyzer is unable to discover imported module.
#[derive(Debug)]
pub struct UnresolvedImport {
    pub file: HirFileId,
    pub node: AstPtr<ast::UseTree>,
}

impl Diagnostic for UnresolvedImport {
    fn code(&self) -> DiagnosticCode {
        DiagnosticCode("unresolved-import")
    }
    fn message(&self) -> String {
        "unresolved import".to_string()
    }
    fn display_source(&self) -> InFile<SyntaxNodePtr> {
        InFile::new(self.file, self.node.clone().into())
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
    fn is_experimental(&self) -> bool {
        // This currently results in false positives in the following cases:
        // - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)
        // - `core::arch` (we don't handle `#[path = "../<path>"]` correctly)
        // - proc macros and/or proc macro generated code
        true
    }
}

// Diagnostic: unresolved-macro-call
//
// This diagnostic is triggered if rust-analyzer is unable to resolove path to a
// macro in a macro invocation.
#[derive(Debug)]
pub struct UnresolvedMacroCall {
    pub file: HirFileId,
    pub node: AstPtr<ast::MacroCall>,
}

impl Diagnostic for UnresolvedMacroCall {
    fn code(&self) -> DiagnosticCode {
        DiagnosticCode("unresolved-macro-call")
    }
    fn message(&self) -> String {
        "unresolved macro call".to_string()
    }
    fn display_source(&self) -> InFile<SyntaxNodePtr> {
        InFile::new(self.file, self.node.clone().into())
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
    fn is_experimental(&self) -> bool {
        true
    }
}

// Diagnostic: inactive-code
//
// This diagnostic is shown for code with inactive `#[cfg]` attributes.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct InactiveCode {
    pub file: HirFileId,
    pub node: SyntaxNodePtr,
    pub cfg: CfgExpr,
    pub opts: CfgOptions,
}

impl Diagnostic for InactiveCode {
    fn code(&self) -> DiagnosticCode {
        DiagnosticCode("inactive-code")
    }
    fn message(&self) -> String {
        let inactive = DnfExpr::new(self.cfg.clone()).why_inactive(&self.opts);
        let mut buf = "code is inactive due to #[cfg] directives".to_string();

        if let Some(inactive) = inactive {
            format_to!(buf, ": {}", inactive);
        }

        buf
    }
    fn display_source(&self) -> InFile<SyntaxNodePtr> {
        InFile::new(self.file, self.node.clone())
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

// Diagnostic: unresolved-proc-macro
//
// This diagnostic is shown when a procedural macro can not be found. This usually means that
// procedural macro support is simply disabled (and hence is only a weak hint instead of an error),
// but can also indicate project setup problems.
//
// If you are seeing a lot of "proc macro not expanded" warnings, you can add this option to the
// `rust-analyzer.diagnostics.disabled` list to prevent them from showing. Alternatively you can
// enable support for procedural macros (see `rust-analyzer.procMacro.enable`).
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct UnresolvedProcMacro {
    pub file: HirFileId,
    pub node: SyntaxNodePtr,
    /// If the diagnostic can be pinpointed more accurately than via `node`, this is the `TextRange`
    /// to use instead.
    pub precise_location: Option<TextRange>,
    pub macro_name: Option<String>,
}

impl Diagnostic for UnresolvedProcMacro {
    fn code(&self) -> DiagnosticCode {
        DiagnosticCode("unresolved-proc-macro")
    }

    fn message(&self) -> String {
        match &self.macro_name {
            Some(name) => format!("proc macro `{}` not expanded", name),
            None => "proc macro not expanded".to_string(),
        }
    }

    fn display_source(&self) -> InFile<SyntaxNodePtr> {
        InFile::new(self.file, self.node.clone())
    }

    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

// Diagnostic: macro-error
//
// This diagnostic is shown for macro expansion errors.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct MacroError {
    pub file: HirFileId,
    pub node: SyntaxNodePtr,
    pub message: String,
}

impl Diagnostic for MacroError {
    fn code(&self) -> DiagnosticCode {
        DiagnosticCode("macro-error")
    }
    fn message(&self) -> String {
        self.message.clone()
    }
    fn display_source(&self) -> InFile<SyntaxNodePtr> {
        InFile::new(self.file, self.node.clone())
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
    fn is_experimental(&self) -> bool {
        // Newly added and not very well-tested, might contain false positives.
        true
    }
}
<commit_msg>Fix spelling error<commit_after>//! Diagnostics produced by `hir_def`.

use std::any::Any;
use stdx::format_to;

use cfg::{CfgExpr, CfgOptions, DnfExpr};
use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
use hir_expand::{HirFileId, InFile};
use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};

use crate::{db::DefDatabase, DefWithBodyId};

pub fn validate_body(db: &dyn DefDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) {
    let source_map = db.body_with_source_map(owner).1;
    source_map.add_diagnostics(db, sink);
}

// Diagnostic: unresolved-module
//
// This diagnostic is triggered if rust-analyzer is unable to discover referred module.
#[derive(Debug)]
pub struct UnresolvedModule {
    pub file: HirFileId,
    pub decl: AstPtr<ast::Module>,
    pub candidate: String,
}

impl Diagnostic for UnresolvedModule {
    fn code(&self) -> DiagnosticCode {
        DiagnosticCode("unresolved-module")
    }
    fn message(&self) -> String {
        "unresolved module".to_string()
    }
    fn display_source(&self) -> InFile<SyntaxNodePtr> {
        InFile::new(self.file, self.decl.clone().into())
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

// Diagnostic: unresolved-extern-crate
//
// This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate.
#[derive(Debug)]
pub struct UnresolvedExternCrate {
    pub file: HirFileId,
    pub item: AstPtr<ast::ExternCrate>,
}

impl Diagnostic for UnresolvedExternCrate {
    fn code(&self) -> DiagnosticCode {
        DiagnosticCode("unresolved-extern-crate")
    }
    fn message(&self) -> String {
        "unresolved extern crate".to_string()
    }
    fn display_source(&self) -> InFile<SyntaxNodePtr> {
        InFile::new(self.file, self.item.clone().into())
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

// Diagnostic: unresolved-import
//
// This diagnostic is triggered if rust-analyzer is unable to discover imported module.
#[derive(Debug)]
pub struct UnresolvedImport {
    pub file: HirFileId,
    pub node: AstPtr<ast::UseTree>,
}

impl Diagnostic for UnresolvedImport {
    fn code(&self) -> DiagnosticCode {
        DiagnosticCode("unresolved-import")
    }
    fn message(&self) -> String {
        "unresolved import".to_string()
    }
    fn display_source(&self) -> InFile<SyntaxNodePtr> {
        InFile::new(self.file, self.node.clone().into())
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
    fn is_experimental(&self) -> bool {
        // This currently results in false positives in the following cases:
        // - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)
        // - `core::arch` (we don't handle `#[path = "../<path>"]` correctly)
        // - proc macros and/or proc macro generated code
        true
    }
}

// Diagnostic: unresolved-macro-call
//
// This diagnostic is triggered if rust-analyzer is unable to resolve the path to a
// macro in a macro invocation.
#[derive(Debug)]
pub struct UnresolvedMacroCall {
    pub file: HirFileId,
    pub node: AstPtr<ast::MacroCall>,
}

impl Diagnostic for UnresolvedMacroCall {
    fn code(&self) -> DiagnosticCode {
        DiagnosticCode("unresolved-macro-call")
    }
    fn message(&self) -> String {
        "unresolved macro call".to_string()
    }
    fn display_source(&self) -> InFile<SyntaxNodePtr> {
        InFile::new(self.file, self.node.clone().into())
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
    fn is_experimental(&self) -> bool {
        true
    }
}

// Diagnostic: inactive-code
//
// This diagnostic is shown for code with inactive `#[cfg]` attributes.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct InactiveCode {
    pub file: HirFileId,
    pub node: SyntaxNodePtr,
    pub cfg: CfgExpr,
    pub opts: CfgOptions,
}

impl Diagnostic for InactiveCode {
    fn code(&self) -> DiagnosticCode {
        DiagnosticCode("inactive-code")
    }
    fn message(&self) -> String {
        let inactive = DnfExpr::new(self.cfg.clone()).why_inactive(&self.opts);
        let mut buf = "code is inactive due to #[cfg] directives".to_string();

        if let Some(inactive) = inactive {
            format_to!(buf, ": {}", inactive);
        }

        buf
    }
    fn display_source(&self) -> InFile<SyntaxNodePtr> {
        InFile::new(self.file, self.node.clone())
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

// Diagnostic: unresolved-proc-macro
//
// This diagnostic is shown when a procedural macro can not be found. This usually means that
// procedural macro support is simply disabled (and hence is only a weak hint instead of an error),
// but can also indicate project setup problems.
//
// If you are seeing a lot of "proc macro not expanded" warnings, you can add this option to the
// `rust-analyzer.diagnostics.disabled` list to prevent them from showing. Alternatively you can
// enable support for procedural macros (see `rust-analyzer.procMacro.enable`).
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct UnresolvedProcMacro {
    pub file: HirFileId,
    pub node: SyntaxNodePtr,
    /// If the diagnostic can be pinpointed more accurately than via `node`, this is the `TextRange`
    /// to use instead.
    pub precise_location: Option<TextRange>,
    pub macro_name: Option<String>,
}

impl Diagnostic for UnresolvedProcMacro {
    fn code(&self) -> DiagnosticCode {
        DiagnosticCode("unresolved-proc-macro")
    }

    fn message(&self) -> String {
        match &self.macro_name {
            Some(name) => format!("proc macro `{}` not expanded", name),
            None => "proc macro not expanded".to_string(),
        }
    }

    fn display_source(&self) -> InFile<SyntaxNodePtr> {
        InFile::new(self.file, self.node.clone())
    }

    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
}

// Diagnostic: macro-error
//
// This diagnostic is shown for macro expansion errors.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct MacroError {
    pub file: HirFileId,
    pub node: SyntaxNodePtr,
    pub message: String,
}

impl Diagnostic for MacroError {
    fn code(&self) -> DiagnosticCode {
        DiagnosticCode("macro-error")
    }
    fn message(&self) -> String {
        self.message.clone()
    }
    fn display_source(&self) -> InFile<SyntaxNodePtr> {
        InFile::new(self.file, self.node.clone())
    }
    fn as_any(&self) -> &(dyn Any + Send + 'static) {
        self
    }
    fn is_experimental(&self) -> bool {
        // Newly added and not very well-tested, might contain false positives.
        true
    }
}
<|endoftext|>


<commit_before>use read_write::*;
use service::*;
use libc::*;
//use std::os::raw;
use menu_service::{MenuFuncs, CMenuFuncs1};
use std::mem::transmute;

pub static BACKEND_API_VERSION: &'static [u8] = b"ProDBG Backend 1\0";

pub trait Backend {
    fn new(service: &Service) -> Self;
    fn update(&mut self, action: i32, reader: &mut Reader, writer: &mut Writer);
    fn register_menu(&mut self, menu_funcs: &mut MenuFuncs) -> *mut c_void;
}

pub type ServiceFunc = extern "C" fn(service: *const c_uchar) -> *mut c_void;

#[repr(C)]
pub struct CBackendCallbacks {
    pub name: *const c_uchar,
    pub create_instance: Option<fn(service_func: ServiceFunc) -> *mut c_void>,
    pub destroy_instance: Option<fn(*mut c_void)>,
    pub register_menu: Option<fn(ptr: *mut c_void, menu_funcs: *mut c_void) -> *mut c_void>,
    pub update: Option<fn(ptr: *mut c_void,
                          a: c_int,
                          ra: *mut c_void,
                          wa: *mut c_void)>,
}

unsafe impl Sync for CBackendCallbacks {}

pub fn create_backend_instance<T: Backend>(service_func: extern "C" fn(service: *const c_uchar)
                                                                       -> *mut c_void)
                                           -> *mut c_void {
    let service = Service { service_func: service_func };
    let instance = unsafe { transmute(Box::new(T::new(&service))) };
    println!("Lets create instance!");
    instance
}

pub fn destroy_backend_instance<T: Backend>(ptr: *mut c_void) {
    println!("rust: backend: destroy");
    let _: Box<T> = unsafe { transmute(ptr) };
    // implicitly dropped
}

pub fn update_backend_instance<T: Backend>(ptr: *mut c_void,
                                           action: c_int,
                                           reader_api: *mut c_void,
                                           writer_api: *mut c_void) {
    let backend: &mut T = unsafe { &mut *(ptr as *mut T) };
    let c_reader: &mut CPDReaderAPI = unsafe { &mut *(reader_api as *mut CPDReaderAPI) };
    let c_writer: &mut CPDWriterAPI = unsafe { &mut *(writer_api as *mut CPDWriterAPI) };
    let mut reader = Reader {
        api: c_reader,
        it: 0,
    };

    let mut writer = Writer { api: c_writer };

    backend.update(action as i32, &mut reader, &mut writer);
}

pub fn register_backend_menu<T: Backend>(ptr: *mut c_void,
                                         menu_api: *mut c_void) -> *mut c_void {
    let backend: &mut T = unsafe { &mut *(ptr as *mut T) };

    let mut menu_funcs = MenuFuncs {
        api: menu_api as *mut CMenuFuncs1,
    };

    backend.register_menu(&mut menu_funcs)
}

#[macro_export]
macro_rules! define_backend_plugin {
    ($p_name:ident, $name:expr, $x:ty) => {
        static $p_name: CBackendCallbacks = CBackendCallbacks {
            name: $name as *const u8,
            create_instance: Some(prodbg_api::backend::create_backend_instance::<$x>),
            destroy_instance: Some(prodbg_api::backend::destroy_backend_instance::<$x>),
            register_menu: Some(prodbg_api::backend::register_backend_menu::<$x>),
            update: Some(prodbg_api::backend::update_backend_instance::<$x>)
        };
    }
}
<commit_msg>Added action constants<commit_after>use read_write::*;
use service::*;
use libc::*;
//use std::os::raw;
use menu_service::{MenuFuncs, CMenuFuncs1};
use std::mem::transmute;

pub const ACTION_NONE: i32 = 0;
pub const ACTION_STOP: i32 = 1;
pub const ACTION_BREAK: i32 = 2;
pub const ACTION_RUN: i32 = 3;
pub const ACTION_STEP: i32 = 4;
pub const ACTION_STEP_OUT: i32 = 5;
pub const ACTION_STEP_OVER: i32 = 6;

pub static BACKEND_API_VERSION: &'static [u8] = b"ProDBG Backend 1\0";

pub trait Backend {
    fn new(service: &Service) -> Self;
    fn update(&mut self, action: i32, reader: &mut Reader, writer: &mut Writer);
    fn register_menu(&mut self, menu_funcs: &mut MenuFuncs) -> *mut c_void;
}

pub type ServiceFunc = extern "C" fn(service: *const c_uchar) -> *mut c_void;

#[repr(C)]
pub struct CBackendCallbacks {
    pub name: *const c_uchar,
    pub create_instance: Option<fn(service_func: ServiceFunc) -> *mut c_void>,
    pub destroy_instance: Option<fn(*mut c_void)>,
    pub register_menu: Option<fn(ptr: *mut c_void, menu_funcs: *mut c_void) -> *mut c_void>,
    pub update: Option<fn(ptr: *mut c_void,
                          a: c_int,
                          ra: *mut c_void,
                          wa: *mut c_void)>,
}

unsafe impl Sync for CBackendCallbacks {}

pub fn create_backend_instance<T: Backend>(service_func: extern "C" fn(service: *const c_uchar)
                                                                       -> *mut c_void)
                                           -> *mut c_void {
    let service = Service { service_func: service_func };
    let instance = unsafe { transmute(Box::new(T::new(&service))) };
    println!("Lets create instance!");
    instance
}

pub fn destroy_backend_instance<T: Backend>(ptr: *mut c_void) {
    println!("rust: backend: destroy");
    let _: Box<T> = unsafe { transmute(ptr) };
    // implicitly dropped
}

pub fn update_backend_instance<T: Backend>(ptr: *mut c_void,
                                           action: c_int,
                                           reader_api: *mut c_void,
                                           writer_api: *mut c_void) {
    let backend: &mut T = unsafe { &mut *(ptr as *mut T) };
    let c_reader: &mut CPDReaderAPI = unsafe { &mut *(reader_api as *mut CPDReaderAPI) };
    let c_writer: &mut CPDWriterAPI = unsafe { &mut *(writer_api as *mut CPDWriterAPI) };
    let mut reader = Reader {
        api: c_reader,
        it: 0,
    };

    let mut writer = Writer { api: c_writer };

    backend.update(action as i32, &mut reader, &mut writer);
}

pub fn register_backend_menu<T: Backend>(ptr: *mut c_void,
                                         menu_api: *mut c_void) -> *mut c_void {
    let backend: &mut T = unsafe { &mut *(ptr as *mut T) };

    let mut menu_funcs = MenuFuncs {
        api: menu_api as *mut CMenuFuncs1,
    };

    backend.register_menu(&mut menu_funcs)
}

#[macro_export]
macro_rules! define_backend_plugin {
    ($p_name:ident, $name:expr, $x:ty) => {
        static $p_name: CBackendCallbacks = CBackendCallbacks {
            name: $name as *const u8,
            create_instance: Some(prodbg_api::backend::create_backend_instance::<$x>),
            destroy_instance: Some(prodbg_api::backend::destroy_backend_instance::<$x>),
            register_menu: Some(prodbg_api::backend::register_backend_menu::<$x>),
            update: Some(prodbg_api::backend::update_backend_instance::<$x>)
        };
    }
}
<|endoftext|>


<commit_before>//! Functionality for obtaining data related to traits from the DB.

use crate::RootDatabase;
use hir::Semantics;
use rustc_hash::FxHashSet;
use syntax::{
    ast::{self, HasName},
    AstNode,
};

/// Given the `impl` block, attempts to find the trait this `impl` corresponds to.
pub fn resolve_target_trait(
    sema: &Semantics<RootDatabase>,
    impl_def: &ast::Impl,
) -> Option<hir::Trait> {
    let ast_path =
        impl_def.trait_().map(|it| it.syntax().clone()).and_then(ast::PathType::cast)?.path()?;

    match sema.resolve_path(&ast_path) {
        Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def),
        _ => None,
    }
}

/// Given the `impl` block, returns the list of associated items (e.g. functions or types) that are
/// missing in this `impl` block.
pub fn get_missing_assoc_items(
    sema: &Semantics<RootDatabase>,
    impl_def: &ast::Impl,
) -> Vec<hir::AssocItem> {
    // Names must be unique between constants and functions. However, type aliases
    // may share the same name as a function or constant.
    let mut impl_fns_consts = FxHashSet::default();
    let mut impl_type = FxHashSet::default();

    if let Some(item_list) = impl_def.assoc_item_list() {
        for item in item_list.assoc_items() {
            match item {
                ast::AssocItem::Fn(f) => {
                    if let Some(n) = f.name() {
                        impl_fns_consts.insert(n.syntax().to_string());
                    }
                }

                ast::AssocItem::TypeAlias(t) => {
                    if let Some(n) = t.name() {
                        impl_type.insert(n.syntax().to_string());
                    }
                }

                ast::AssocItem::Const(c) => {
                    if let Some(n) = c.name() {
                        impl_fns_consts.insert(n.syntax().to_string());
                    }
                }
                ast::AssocItem::MacroCall(_) => (),
            }
        }
    }

    resolve_target_trait(sema, impl_def).map_or(vec![], |target_trait| {
        target_trait
            .items(sema.db)
            .into_iter()
            .filter(|i| match i {
                hir::AssocItem::Function(f) => {
                    !impl_fns_consts.contains(&f.name(sema.db).to_string())
                }
                hir::AssocItem::TypeAlias(t) => !impl_type.contains(&t.name(sema.db).to_string()),
                hir::AssocItem::Const(c) => c
                    .name(sema.db)
                    .map(|n| !impl_fns_consts.contains(&n.to_string()))
                    .unwrap_or_default(),
            })
            .collect()
    })
}

#[cfg(test)]
mod tests {
    use base_db::{fixture::ChangeFixture, FilePosition};
    use expect_test::{expect, Expect};
    use hir::Semantics;
    use syntax::ast::{self, AstNode};

    use crate::RootDatabase;

    /// Creates analysis from a multi-file fixture, returns positions marked with $0.
    pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
        let change_fixture = ChangeFixture::parse(ra_fixture);
        let mut database = RootDatabase::default();
        database.apply_change(change_fixture.change);
        let (file_id, range_or_offset) =
            change_fixture.file_position.expect("expected a marker ($0)");
        let offset = range_or_offset.expect_offset();
        (database, FilePosition { file_id, offset })
    }

    fn check_trait(ra_fixture: &str, expect: Expect) {
        let (db, position) = position(ra_fixture);
        let sema = Semantics::new(&db);
        let file = sema.parse(position.file_id);
        let impl_block: ast::Impl =
            sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
        let trait_ = crate::traits::resolve_target_trait(&sema, &impl_block);
        let actual = match trait_ {
            Some(trait_) => trait_.name(&db).to_string(),
            None => String::new(),
        };
        expect.assert_eq(&actual);
    }

    fn check_missing_assoc(ra_fixture: &str, expect: Expect) {
        let (db, position) = position(ra_fixture);
        let sema = Semantics::new(&db);
        let file = sema.parse(position.file_id);
        let impl_block: ast::Impl =
            sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
        let items = crate::traits::get_missing_assoc_items(&sema, &impl_block);
        let actual = items
            .into_iter()
            .map(|item| item.name(&db).unwrap().to_string())
            .collect::<Vec<_>>()
            .join("\n");
        expect.assert_eq(&actual);
    }

    #[test]
    fn resolve_trait() {
        check_trait(
            r#"
pub trait Foo {
    fn bar();
}
impl Foo for u8 {
    $0
}
            "#,
            expect![["Foo"]],
        );
        check_trait(
            r#"
pub trait Foo {
    fn bar();
}
impl Foo for u8 {
    fn bar() {
        fn baz() {
            $0
        }
        baz();
    }
}
            "#,
            expect![["Foo"]],
        );
        check_trait(
            r#"
pub trait Foo {
    fn bar();
}
pub struct Bar;
impl Bar {
    $0
}
            "#,
            expect![[""]],
        );
    }

    #[test]
    fn missing_assoc_items() {
        check_missing_assoc(
            r#"
pub trait Foo {
    const FOO: u8;
    fn bar();
}
impl Foo for u8 {
    $0
}"#,
            expect![[r#"
                FOO
                bar"#]],
        );

        check_missing_assoc(
            r#"
pub trait Foo {
    const FOO: u8;
    fn bar();
}
impl Foo for u8 {
    const FOO: u8 = 10;
    $0
}"#,
            expect![[r#"
                bar"#]],
        );

        check_missing_assoc(
            r#"
pub trait Foo {
    const FOO: u8;
    fn bar();
}
impl Foo for u8 {
    const FOO: u8 = 10;
    fn bar() {$0}
}"#,
            expect![[r#""#]],
        );

        check_missing_assoc(
            r#"
pub struct Foo;
impl Foo {
    fn bar() {$0}
}"#,
            expect![[r#""#]],
        );
    }
}
<commit_msg>Account for macros in `get_missing_assoc_items`<commit_after>//! Functionality for obtaining data related to traits from the DB.

use crate::RootDatabase;
use hir::Semantics;
use rustc_hash::FxHashSet;
use syntax::{ast, AstNode};

/// Given the `impl` block, attempts to find the trait this `impl` corresponds to.
pub fn resolve_target_trait(
    sema: &Semantics<RootDatabase>,
    impl_def: &ast::Impl,
) -> Option<hir::Trait> {
    let ast_path =
        impl_def.trait_().map(|it| it.syntax().clone()).and_then(ast::PathType::cast)?.path()?;

    match sema.resolve_path(&ast_path) {
        Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def),
        _ => None,
    }
}

/// Given the `impl` block, returns the list of associated items (e.g. functions or types) that are
/// missing in this `impl` block.
pub fn get_missing_assoc_items(
    sema: &Semantics<RootDatabase>,
    impl_def: &ast::Impl,
) -> Vec<hir::AssocItem> {
    let imp = match sema.to_def(impl_def) {
        Some(it) => it,
        None => return vec![],
    };

    // Names must be unique between constants and functions. However, type aliases
    // may share the same name as a function or constant.
    let mut impl_fns_consts = FxHashSet::default();
    let mut impl_type = FxHashSet::default();

    for item in imp.items(sema.db) {
        match item {
            hir::AssocItem::Function(it) => {
                impl_fns_consts.insert(it.name(sema.db).to_string());
            }
            hir::AssocItem::Const(it) => {
                if let Some(name) = it.name(sema.db) {
                    impl_fns_consts.insert(name.to_string());
                }
            }
            hir::AssocItem::TypeAlias(it) => {
                impl_type.insert(it.name(sema.db).to_string());
            }
        }
    }

    resolve_target_trait(sema, impl_def).map_or(vec![], |target_trait| {
        target_trait
            .items(sema.db)
            .into_iter()
            .filter(|i| match i {
                hir::AssocItem::Function(f) => {
                    !impl_fns_consts.contains(&f.name(sema.db).to_string())
                }
                hir::AssocItem::TypeAlias(t) => !impl_type.contains(&t.name(sema.db).to_string()),
                hir::AssocItem::Const(c) => c
                    .name(sema.db)
                    .map(|n| !impl_fns_consts.contains(&n.to_string()))
                    .unwrap_or_default(),
            })
            .collect()
    })
}

#[cfg(test)]
mod tests {
    use base_db::{fixture::ChangeFixture, FilePosition};
    use expect_test::{expect, Expect};
    use hir::Semantics;
    use syntax::ast::{self, AstNode};

    use crate::RootDatabase;

    /// Creates analysis from a multi-file fixture, returns positions marked with $0.
    pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
        let change_fixture = ChangeFixture::parse(ra_fixture);
        let mut database = RootDatabase::default();
        database.apply_change(change_fixture.change);
        let (file_id, range_or_offset) =
            change_fixture.file_position.expect("expected a marker ($0)");
        let offset = range_or_offset.expect_offset();
        (database, FilePosition { file_id, offset })
    }

    fn check_trait(ra_fixture: &str, expect: Expect) {
        let (db, position) = position(ra_fixture);
        let sema = Semantics::new(&db);
        let file = sema.parse(position.file_id);
        let impl_block: ast::Impl =
            sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
        let trait_ = crate::traits::resolve_target_trait(&sema, &impl_block);
        let actual = match trait_ {
            Some(trait_) => trait_.name(&db).to_string(),
            None => String::new(),
        };
        expect.assert_eq(&actual);
    }

    fn check_missing_assoc(ra_fixture: &str, expect: Expect) {
        let (db, position) = position(ra_fixture);
        let sema = Semantics::new(&db);
        let file = sema.parse(position.file_id);
        let impl_block: ast::Impl =
            sema.find_node_at_offset_with_descend(file.syntax(), position.offset).unwrap();
        let items = crate::traits::get_missing_assoc_items(&sema, &impl_block);
        let actual = items
            .into_iter()
            .map(|item| item.name(&db).unwrap().to_string())
            .collect::<Vec<_>>()
            .join("\n");
        expect.assert_eq(&actual);
    }

    #[test]
    fn resolve_trait() {
        check_trait(
            r#"
pub trait Foo {
    fn bar();
}
impl Foo for u8 {
    $0
}
            "#,
            expect![["Foo"]],
        );
        check_trait(
            r#"
pub trait Foo {
    fn bar();
}
impl Foo for u8 {
    fn bar() {
        fn baz() {
            $0
        }
        baz();
    }
}
            "#,
            expect![["Foo"]],
        );
        check_trait(
            r#"
pub trait Foo {
    fn bar();
}
pub struct Bar;
impl Bar {
    $0
}
            "#,
            expect![[""]],
        );
    }

    #[test]
    fn missing_assoc_items() {
        check_missing_assoc(
            r#"
pub trait Foo {
    const FOO: u8;
    fn bar();
}
impl Foo for u8 {
    $0
}"#,
            expect![[r#"
                FOO
                bar"#]],
        );

        check_missing_assoc(
            r#"
pub trait Foo {
    const FOO: u8;
    fn bar();
}
impl Foo for u8 {
    const FOO: u8 = 10;
    $0
}"#,
            expect![[r#"
                bar"#]],
        );

        check_missing_assoc(
            r#"
pub trait Foo {
    const FOO: u8;
    fn bar();
}
impl Foo for u8 {
    const FOO: u8 = 10;
    fn bar() {$0}
}"#,
            expect![[r#""#]],
        );

        check_missing_assoc(
            r#"
pub struct Foo;
impl Foo {
    fn bar() {$0}
}"#,
            expect![[r#""#]],
        );

        check_missing_assoc(
            r#"
trait Tr {
    fn required();
}
macro_rules! m {
    () => { fn required() {} };
}
impl Tr for () {
    m!();
    $0
}

            "#,
            expect![[r#""#]],
        );
    }
}
<|endoftext|>