<commit_before>use std::collections::{BTreeMap, HashMap};
use std::collections::btree_map::Iter;
use std::path::{PathBuf, Path};
use std::fmt::{Display, Formatter, Result};
use std::ops::Add;
use std::cmp::{Ord, Ordering};

/// Used to track the state of logical conditions
#[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd)]
pub struct LogicState {
    /// Whether the condition has been observed as true
    pub been_true: bool,
    /// Whether the condition has been observed as false
    pub been_false: bool,
}

impl<'a> Add for &'a LogicState {
    type Output = LogicState;

    fn add(self, other: &'a LogicState) -> LogicState {
        LogicState {
            been_true: self.been_true | other.been_true,
            been_false: self.been_false | other.been_false,
        }
    }
}

/// Shows what type of coverage data is being collected by a given trace
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd)]
pub enum CoverageStat {
    /// Line coverage data (whether line has been hit)
    Line(u64),
    /// Branch coverage data (whether branch has been true and false
    Branch(LogicState),
    /// Condition coverage data (each boolean subcondition true and false)
    Condition(Vec<LogicState>),
}

impl Add for CoverageStat {
    type Output = CoverageStat;

    fn add(self, other: CoverageStat) -> CoverageStat {
        match (self, other) {
            (CoverageStat::Line(ref l), CoverageStat::Line(ref r)) => {
                CoverageStat::Line(l+r)
            },
            (CoverageStat::Branch(ref l), CoverageStat::Branch(ref r)) => {
                CoverageStat::Branch(l + r)
            },
            t @ _ => t.0,
        }
    }
}

impl Display for CoverageStat {
    fn fmt(&self, f: &mut Formatter) -> Result {
        match self {
            &CoverageStat::Line(x) => {
                write!(f, "hits: {}", x)
            },
            _ => write!(f, ""),
        }
    }
}

#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd)]
pub struct Trace {
    /// Line the trace is on in the file
    pub line: u64,
    /// Optional address showing location in the test artefact
    pub address: Option<u64>,
    /// Length of the instruction (useful to get entire condition/branch)
    pub length: usize,
    /// Coverage stats
    pub stats: CoverageStat,
}

/// Implemented to allow Traces to be sorted by line number
impl Ord for Trace {
    fn cmp(&self, other: &Trace) -> Ordering {
        self.line.cmp(&other.line)
    }
    fn max(self, other: Trace) -> Trace {
        if self.line > other.line {
            self
        } else {
            other
        }
    }
    fn min(self, other: Trace) -> Trace {
        if self.line < other.line {
            self
        } else {
            other
        }
    }
}

/// Stores all the program traces mapped to files and provides an interface to
/// add, query and change traces.
#[derive(Debug)]
pub struct TraceMap {
    /// Traces in the program mapped to the given file
    traces: BTreeMap<PathBuf, Vec<Trace>>,
}

impl TraceMap {
    /// Create a new TraceMap
    pub fn new() -> TraceMap {
        TraceMap {
            traces: BTreeMap::new(),
        }
    }

    /// Returns true if there are no traces
    pub fn is_empty(&self) -> bool {
        self.traces.is_empty()
    }

    /// Provides an interator to the underlying map of PathBufs to Vec<Trace>
    pub fn iter(&self) -> Iter<PathBuf, Vec<Trace>> {
        self.traces.iter()
    }

    /// Merges the results of one tracemap into the current one.
    /// This adds records which are missing and adds the statistics gathered to
    /// existing records
    pub fn merge(&mut self, other: &TraceMap) {
        for (k,  values) in other.iter() {
            if !self.traces.contains_key(k) {
                self.traces.insert(k.to_path_buf(), values.to_vec());
            } else {
                let mut existing = self.traces.get_mut(k).unwrap();
                for ref v in values.iter() {
                    let mut added = false;
                    if let Some(ref mut t) = existing.iter_mut()
                                                     .find(|ref x| x.line == v.line && x.address == v.address) {
                        t.stats = t.stats.clone() + v.stats.clone();
                        added = true;
                    }
                    if !added {
                        existing.push((*v).clone());
                        existing.sort_unstable();
                    }
                }
            }
        }
    }

    /// This will collapse duplicate Traces into a single trace. Warning this
    /// will lose the addresses of the duplicate traces but increment the results
    /// should be called only if you don't need those addresses from then on
    /// TODO possibly not the cleanest solution
    pub fn dedup(&mut self) {
        for (_, values) in self.traces.iter_mut() {
            // Map of lines and stats, merge duplicated stats here
            let mut lines: HashMap<u64, CoverageStat> = HashMap::new();
            // Duplicated traces need cleaning up. Maintain a list of them!
            let mut dirty: Vec<u64> = Vec::new();
            for v in values.iter() {
                if lines.contains_key(&v.line) {
                    dirty.push(v.line);
                    let s = lines.get(&v.line).unwrap().clone() + v.stats.clone();
                    lines.insert(v.line, s);
                } else {
                    lines.insert(v.line, v.stats.clone());
                }
            }
            for d in dirty.iter() {
                let mut first = true;
                values.retain(|x| {
                    let res = x.line != *d;
                    if !res {
                        if first {
                            first = false;
                            true
                        }else {
                            false
                        }
                    } else {
                        res
                    }
                });
                if let Some(new_stat) = lines.remove(&d) {
                    if let Some(ref mut t) = values.iter_mut().find(|x| x.line==*d) {
                        t.stats = new_stat;
                    }
                }
            }
        }
    }

    /// Add a trace to the tracemap for the given file
    pub fn add_trace(&mut self, file: &Path, trace: Trace) {
        if self.traces.contains_key(file) {
            if let Some(trace_vec) = self.traces.get_mut(file) {
                trace_vec.push(trace);
                trace_vec.sort_unstable();
            }
        } else {
            self.traces.insert(file.to_path_buf(), vec![trace]);
        }
    }

    /// Gets an immutable reference to a trace from an address. Returns None if
    /// there is no trace at that address
    pub fn get_trace(&self, address: u64) -> Option<&Trace> {
        self.all_traces()
            .iter()
            .find(|x| x.address == Some(address))
            .map(|x| *x)
    }

    /// Gets a mutable reference to a trace at a given address
    /// Returns None if there is no trace at that address
    pub fn get_trace_mut(&mut self, address: u64) -> Option<&mut Trace> {
        for val in self.all_traces_mut() {
            if val.address == Some(address) {
                return Some(val);
            }
        }
        None
    }

    /// Returns true if the location described by file and line number is present
    /// in the tracemap
    pub fn contains_location(&self, file: &Path, line: u64) -> bool {
        match self.traces.get(file) {
            Some(traces) => traces.iter().any(|x| x.line == line),
            None => false,
        }
    }

    /// Gets all traces below a certain path
    pub fn get_child_traces(&self, root: &Path) -> Vec<&Trace> {
        self.traces.iter()
                   .filter(|&(ref k, _)| k.starts_with(root))
                   .flat_map(|(_, ref v)| v.iter())
                   .collect()
    }

    /// Gets all traces
    pub fn all_traces(&self) -> Vec<&Trace> {
        self.traces.values().flat_map(|ref x| x.iter()).collect()
    }

    /// Gets a vector of all the traces to mutate
    fn all_traces_mut(&mut self) -> Vec<&mut Trace> {
        self.traces.values_mut().flat_map(|x| x.iter_mut()).collect()
    }

    pub fn files(&self) -> Vec<&PathBuf> {
        self.traces.keys().collect()
    }

    pub fn coverable_in_path(&self, path: &Path) -> usize {
        let mut result = 0usize;
        for t in self.get_child_traces(path) {
            result += match t.stats {
                CoverageStat::Branch(_) => {
                    2usize
                },
                CoverageStat::Condition(ref x) => {
                    x.len() * 2usize
                }
                _ => 1usize
            };
        }
        result
    }

    pub fn covered_in_path(&self, path: &Path) -> usize {
        let mut result = 0usize;
        for t in self.get_child_traces(path) {
            result += match t.stats {
                CoverageStat::Branch(ref x) => {
                    (x.been_true as usize) + (x.been_false as usize)
                },
                CoverageStat::Condition(ref x) => {
                    x.iter()
                     .fold(0, |acc, ref x| acc + (x.been_true as usize) + (x.been_false as usize))
                }
                CoverageStat::Line(ref x) => {
                    (x > &0) as usize
                }
            };
        }
        result
    }

    /// Give the total amount of coverable points in the code. This will vary
    /// based on the statistics available for line coverage it will be total
    /// line whereas for condition or decision it will count the number of
    /// conditions available
    pub fn total_coverable(&self) -> usize {
        let mut result = 0usize;
        for t in self.all_traces() {
            result += match t.stats {
                CoverageStat::Branch(_) => {
                    2usize
                },
                CoverageStat::Condition(ref x) => {
                    x.len() * 2usize
                }
                _ => 1usize
            };
        }
        result
    }

    /// From all the coverable data return the amount covered
    pub fn total_covered(&self) -> usize {
        let mut result = 0usize;
        for t in self.all_traces() {
            result += match t.stats {
                CoverageStat::Branch(ref x) => {
                    (x.been_true as usize) + (x.been_false as usize)
                },
                CoverageStat::Condition(ref x) => {
                    x.iter()
                     .fold(0, |acc, ref x| acc + (x.been_true as usize) + (x.been_false as usize))
                }
                CoverageStat::Line(ref x) => {
                    (x > &0) as usize
                }
            };
        }
        result
    }

    /// Returns coverage percentage ranging from 0.0-1.0
    pub fn coverage_percentage(&self) -> f64 {
        (self.total_covered() as f64) / (self.total_coverable() as f64)
    }

}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::Path;

    #[test]
    fn stat_addition() {
        let x = CoverageStat::Line(0);
        let y = CoverageStat::Line(5);
        let z = CoverageStat::Line(7);
        let xy = x.clone() + y.clone();
        let yx = y.clone() + x.clone();
        let yy = y.clone() + y.clone();
        let zy = z.clone() + y.clone();
        assert_eq!(&xy, &CoverageStat::Line(5));
        assert_eq!(&yx, &xy);
        assert_eq!(&yy, &CoverageStat::Line(10));
        assert_eq!(&zy, &CoverageStat::Line(12));

        let tf = LogicState{been_true:true, been_false:true};
        let t = LogicState{been_true:true, been_false:false};
        let f = LogicState{been_true:false, been_false:true};
        let n = LogicState{been_true:false, been_false:false};

        assert_eq!(&t+&f, tf);
        assert_eq!(&t+&t, t);
        assert_eq!(&tf+&f, tf);
        assert_eq!(&tf+&t, tf);
        assert_eq!(&t+&n, t);
        assert_eq!(&n+&f, f);
        assert_eq!(&n+&n,n);
    }

    #[test]
    fn merge_address_mismatch_and_dedup() {
        let mut t1 = TraceMap::new();
        let mut t2 = TraceMap::new();

        let a_trace = Trace {
            line: 1,
            address: Some(5),
            length: 0,
            stats: CoverageStat::Line(1)
        };
        t1.add_trace(Path::new("file.rs"), a_trace.clone());
        t2.add_trace(Path::new("file.rs"), Trace {
            line: 1,
            address: None,
            length: 0,
            stats: CoverageStat::Line(2)
        });

        t1.merge(&t2);
        assert_eq!(t1.all_traces().len(), 2);
        assert_eq!(t1.get_trace(5), Some(&a_trace));
        t1.dedup();
        let all = t1.all_traces();
        assert_eq!(all.len(), 1);
        assert_eq!(all[0].stats, CoverageStat::Line(3));
    }

    #[test]
    fn no_merge_dedup_needed() {
        let mut t1 = TraceMap::new();
        let mut t2 = TraceMap::new();

        let a_trace = Trace {
            line: 1,
            address: Some(5),
            length: 0,
            stats: CoverageStat::Line(1)
        };
        t1.add_trace(Path::new("file.rs"), a_trace.clone());
        t2.add_trace(Path::new("file.rs"), Trace {
            line: 2,
            address: None,
            length: 0,
            stats: CoverageStat::Line(2)
        });

        t1.merge(&t2);
        assert_eq!(t1.all_traces().len(), 2);
        assert_eq!(t1.get_trace(5), Some(&a_trace));
        t1.dedup();
        let all = t1.all_traces();
        assert_eq!(all.len(), 2);
    }
}
<commit_msg>Added a test for no merge or dedup<commit_after>use std::collections::{BTreeMap, HashMap};
use std::collections::btree_map::Iter;
use std::path::{PathBuf, Path};
use std::fmt::{Display, Formatter, Result};
use std::ops::Add;
use std::cmp::{Ord, Ordering};

/// Used to track the state of logical conditions
#[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd)]
pub struct LogicState {
    /// Whether the condition has been observed as true
    pub been_true: bool,
    /// Whether the condition has been observed as false
    pub been_false: bool,
}

impl<'a> Add for &'a LogicState {
    type Output = LogicState;

    fn add(self, other: &'a LogicState) -> LogicState {
        LogicState {
            been_true: self.been_true | other.been_true,
            been_false: self.been_false | other.been_false,
        }
    }
}

/// Shows what type of coverage data is being collected by a given trace
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd)]
pub enum CoverageStat {
    /// Line coverage data (whether line has been hit)
    Line(u64),
    /// Branch coverage data (whether branch has been true and false
    Branch(LogicState),
    /// Condition coverage data (each boolean subcondition true and false)
    Condition(Vec<LogicState>),
}

impl Add for CoverageStat {
    type Output = CoverageStat;

    fn add(self, other: CoverageStat) -> CoverageStat {
        match (self, other) {
            (CoverageStat::Line(ref l), CoverageStat::Line(ref r)) => {
                CoverageStat::Line(l+r)
            },
            (CoverageStat::Branch(ref l), CoverageStat::Branch(ref r)) => {
                CoverageStat::Branch(l + r)
            },
            t @ _ => t.0,
        }
    }
}

impl Display for CoverageStat {
    fn fmt(&self, f: &mut Formatter) -> Result {
        match self {
            &CoverageStat::Line(x) => {
                write!(f, "hits: {}", x)
            },
            _ => write!(f, ""),
        }
    }
}

#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd)]
pub struct Trace {
    /// Line the trace is on in the file
    pub line: u64,
    /// Optional address showing location in the test artefact
    pub address: Option<u64>,
    /// Length of the instruction (useful to get entire condition/branch)
    pub length: usize,
    /// Coverage stats
    pub stats: CoverageStat,
}

/// Implemented to allow Traces to be sorted by line number
impl Ord for Trace {
    fn cmp(&self, other: &Trace) -> Ordering {
        self.line.cmp(&other.line)
    }
    fn max(self, other: Trace) -> Trace {
        if self.line > other.line {
            self
        } else {
            other
        }
    }
    fn min(self, other: Trace) -> Trace {
        if self.line < other.line {
            self
        } else {
            other
        }
    }
}

/// Stores all the program traces mapped to files and provides an interface to
/// add, query and change traces.
#[derive(Debug)]
pub struct TraceMap {
    /// Traces in the program mapped to the given file
    traces: BTreeMap<PathBuf, Vec<Trace>>,
}

impl TraceMap {
    /// Create a new TraceMap
    pub fn new() -> TraceMap {
        TraceMap {
            traces: BTreeMap::new(),
        }
    }

    /// Returns true if there are no traces
    pub fn is_empty(&self) -> bool {
        self.traces.is_empty()
    }

    /// Provides an interator to the underlying map of PathBufs to Vec<Trace>
    pub fn iter(&self) -> Iter<PathBuf, Vec<Trace>> {
        self.traces.iter()
    }

    /// Merges the results of one tracemap into the current one.
    /// This adds records which are missing and adds the statistics gathered to
    /// existing records
    pub fn merge(&mut self, other: &TraceMap) {
        for (k,  values) in other.iter() {
            if !self.traces.contains_key(k) {
                self.traces.insert(k.to_path_buf(), values.to_vec());
            } else {
                let mut existing = self.traces.get_mut(k).unwrap();
                for ref v in values.iter() {
                    let mut added = false;
                    if let Some(ref mut t) = existing.iter_mut()
                                                     .find(|ref x| x.line == v.line && x.address == v.address) {
                        t.stats = t.stats.clone() + v.stats.clone();
                        added = true;
                    }
                    if !added {
                        existing.push((*v).clone());
                        existing.sort_unstable();
                    }
                }
            }
        }
    }

    /// This will collapse duplicate Traces into a single trace. Warning this
    /// will lose the addresses of the duplicate traces but increment the results
    /// should be called only if you don't need those addresses from then on
    /// TODO possibly not the cleanest solution
    pub fn dedup(&mut self) {
        for (_, values) in self.traces.iter_mut() {
            // Map of lines and stats, merge duplicated stats here
            let mut lines: HashMap<u64, CoverageStat> = HashMap::new();
            // Duplicated traces need cleaning up. Maintain a list of them!
            let mut dirty: Vec<u64> = Vec::new();
            for v in values.iter() {
                if lines.contains_key(&v.line) {
                    dirty.push(v.line);
                    let s = lines.get(&v.line).unwrap().clone() + v.stats.clone();
                    lines.insert(v.line, s);
                } else {
                    lines.insert(v.line, v.stats.clone());
                }
            }
            for d in dirty.iter() {
                let mut first = true;
                values.retain(|x| {
                    let res = x.line != *d;
                    if !res {
                        if first {
                            first = false;
                            true
                        }else {
                            false
                        }
                    } else {
                        res
                    }
                });
                if let Some(new_stat) = lines.remove(&d) {
                    if let Some(ref mut t) = values.iter_mut().find(|x| x.line==*d) {
                        t.stats = new_stat;
                    }
                }
            }
        }
    }

    /// Add a trace to the tracemap for the given file
    pub fn add_trace(&mut self, file: &Path, trace: Trace) {
        if self.traces.contains_key(file) {
            if let Some(trace_vec) = self.traces.get_mut(file) {
                trace_vec.push(trace);
                trace_vec.sort_unstable();
            }
        } else {
            self.traces.insert(file.to_path_buf(), vec![trace]);
        }
    }

    /// Gets an immutable reference to a trace from an address. Returns None if
    /// there is no trace at that address
    pub fn get_trace(&self, address: u64) -> Option<&Trace> {
        self.all_traces()
            .iter()
            .find(|x| x.address == Some(address))
            .map(|x| *x)
    }

    /// Gets a mutable reference to a trace at a given address
    /// Returns None if there is no trace at that address
    pub fn get_trace_mut(&mut self, address: u64) -> Option<&mut Trace> {
        for val in self.all_traces_mut() {
            if val.address == Some(address) {
                return Some(val);
            }
        }
        None
    }

    /// Returns true if the location described by file and line number is present
    /// in the tracemap
    pub fn contains_location(&self, file: &Path, line: u64) -> bool {
        match self.traces.get(file) {
            Some(traces) => traces.iter().any(|x| x.line == line),
            None => false,
        }
    }

    /// Gets all traces below a certain path
    pub fn get_child_traces(&self, root: &Path) -> Vec<&Trace> {
        self.traces.iter()
                   .filter(|&(ref k, _)| k.starts_with(root))
                   .flat_map(|(_, ref v)| v.iter())
                   .collect()
    }

    /// Gets all traces
    pub fn all_traces(&self) -> Vec<&Trace> {
        self.traces.values().flat_map(|ref x| x.iter()).collect()
    }

    /// Gets a vector of all the traces to mutate
    fn all_traces_mut(&mut self) -> Vec<&mut Trace> {
        self.traces.values_mut().flat_map(|x| x.iter_mut()).collect()
    }

    pub fn files(&self) -> Vec<&PathBuf> {
        self.traces.keys().collect()
    }

    pub fn coverable_in_path(&self, path: &Path) -> usize {
        let mut result = 0usize;
        for t in self.get_child_traces(path) {
            result += match t.stats {
                CoverageStat::Branch(_) => {
                    2usize
                },
                CoverageStat::Condition(ref x) => {
                    x.len() * 2usize
                }
                _ => 1usize
            };
        }
        result
    }

    pub fn covered_in_path(&self, path: &Path) -> usize {
        let mut result = 0usize;
        for t in self.get_child_traces(path) {
            result += match t.stats {
                CoverageStat::Branch(ref x) => {
                    (x.been_true as usize) + (x.been_false as usize)
                },
                CoverageStat::Condition(ref x) => {
                    x.iter()
                     .fold(0, |acc, ref x| acc + (x.been_true as usize) + (x.been_false as usize))
                }
                CoverageStat::Line(ref x) => {
                    (x > &0) as usize
                }
            };
        }
        result
    }

    /// Give the total amount of coverable points in the code. This will vary
    /// based on the statistics available for line coverage it will be total
    /// line whereas for condition or decision it will count the number of
    /// conditions available
    pub fn total_coverable(&self) -> usize {
        let mut result = 0usize;
        for t in self.all_traces() {
            result += match t.stats {
                CoverageStat::Branch(_) => {
                    2usize
                },
                CoverageStat::Condition(ref x) => {
                    x.len() * 2usize
                }
                _ => 1usize
            };
        }
        result
    }

    /// From all the coverable data return the amount covered
    pub fn total_covered(&self) -> usize {
        let mut result = 0usize;
        for t in self.all_traces() {
            result += match t.stats {
                CoverageStat::Branch(ref x) => {
                    (x.been_true as usize) + (x.been_false as usize)
                },
                CoverageStat::Condition(ref x) => {
                    x.iter()
                     .fold(0, |acc, ref x| acc + (x.been_true as usize) + (x.been_false as usize))
                }
                CoverageStat::Line(ref x) => {
                    (x > &0) as usize
                }
            };
        }
        result
    }

    /// Returns coverage percentage ranging from 0.0-1.0
    pub fn coverage_percentage(&self) -> f64 {
        (self.total_covered() as f64) / (self.total_coverable() as f64)
    }

}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::Path;

    #[test]
    fn stat_addition() {
        let x = CoverageStat::Line(0);
        let y = CoverageStat::Line(5);
        let z = CoverageStat::Line(7);
        let xy = x.clone() + y.clone();
        let yx = y.clone() + x.clone();
        let yy = y.clone() + y.clone();
        let zy = z.clone() + y.clone();
        assert_eq!(&xy, &CoverageStat::Line(5));
        assert_eq!(&yx, &xy);
        assert_eq!(&yy, &CoverageStat::Line(10));
        assert_eq!(&zy, &CoverageStat::Line(12));

        let tf = LogicState{been_true:true, been_false:true};
        let t = LogicState{been_true:true, been_false:false};
        let f = LogicState{been_true:false, been_false:true};
        let n = LogicState{been_true:false, been_false:false};

        assert_eq!(&t+&f, tf);
        assert_eq!(&t+&t, t);
        assert_eq!(&tf+&f, tf);
        assert_eq!(&tf+&t, tf);
        assert_eq!(&t+&n, t);
        assert_eq!(&n+&f, f);
        assert_eq!(&n+&n,n);
    }

    #[test]
    fn merge_address_mismatch_and_dedup() {
        let mut t1 = TraceMap::new();
        let mut t2 = TraceMap::new();

        let a_trace = Trace {
            line: 1,
            address: Some(5),
            length: 0,
            stats: CoverageStat::Line(1)
        };
        t1.add_trace(Path::new("file.rs"), a_trace.clone());
        t2.add_trace(Path::new("file.rs"), Trace {
            line: 1,
            address: None,
            length: 0,
            stats: CoverageStat::Line(2)
        });

        t1.merge(&t2);
        assert_eq!(t1.all_traces().len(), 2);
        assert_eq!(t1.get_trace(5), Some(&a_trace));
        t1.dedup();
        let all = t1.all_traces();
        assert_eq!(all.len(), 1);
        assert_eq!(all[0].stats, CoverageStat::Line(3));
    }

    #[test]
    fn no_merge_dedup_needed() {
        let mut t1 = TraceMap::new();
        let mut t2 = TraceMap::new();

        let a_trace = Trace {
            line: 1,
            address: Some(5),
            length: 0,
            stats: CoverageStat::Line(1)
        };
        t1.add_trace(Path::new("file.rs"), a_trace.clone());
        t2.add_trace(Path::new("file.rs"), Trace {
            line: 2,
            address: None,
            length: 0,
            stats: CoverageStat::Line(2)
        });

        t1.merge(&t2);
        assert_eq!(t1.all_traces().len(), 2);
        assert_eq!(t1.get_trace(5), Some(&a_trace));
        t1.dedup();
        let all = t1.all_traces();
        assert_eq!(all.len(), 2);
    }

    #[test]
    fn merge_needed() {
        let mut t1 = TraceMap::new();
        let mut t2 = TraceMap::new();

        t1.add_trace(Path::new("file.rs"), Trace {
            line: 2,
            address: Some(1),
            length: 0,
            stats: CoverageStat::Line(5)
        });
        t2.add_trace(Path::new("file.rs"), Trace {
            line: 2,
            address: Some(1),
            length: 0,
            stats: CoverageStat::Line(2)
        });
        t1.merge(&t2);
        assert_eq!(t1.all_traces().len(), 1);
        assert_eq!(t1.get_trace(1), Some(&Trace {
            line: 2,
            address: Some(1),
            length: 0,
            stats: CoverageStat::Line(7)
        }));
        // Deduplicating should have no effect.
        t1.dedup();
        assert_eq!(t1.all_traces().len(), 1);
        assert_eq!(t1.get_trace(1), Some(&Trace {
            line: 2,
            address: Some(1),
            length: 0,
            stats: CoverageStat::Line(7)
        }));
    }
}
<|endoftext|>


<commit_before>/// Trait implemented by all types that can be stored directly in the GC heap:
/// the `Storage` types associated with any `IntoHeap` type.
///
pub unsafe trait InHeap<'a>: Sized {
    type Out: IntoHeap<'a>;

    unsafe fn mark(ptr: *mut Self);

    /// Extract the value from the heap. This is for macro-generated code to
    /// call; it is impossible for ordinary users to call this safely, because
    /// `self` must be a direct, unwrapped reference to a value stored in the
    /// GC heap, which ordinary users cannot obtain.
    ///
    /// This turns any raw pointers in the `Storage` value into safe
    /// references, so while it's a dangerous function, the result of a correct
    /// call can be safely handed out to user code.
    ///
    unsafe fn from_heap(&self) -> Self::Out;
}

/// Non-inlined function that serves as an entry point to marking. This is used
/// for marking root set entries.
pub unsafe fn mark_entry_point<'a, T: InHeap<'a>>(addr: *mut ()) {
    InHeap::mark(addr as *mut T);
}

/// `IntoHeap` types are safe to use and can be stored in a field of a GC struct.
///
/// "Safe to use" means they don't expose raw pointers to GC memory, and they
/// obey Rust's safety and aliasing rules. If a `IntoHeap` value contains a
/// pointer to a GC allocation, then that allocation (and everything reachable
/// from it) is protected from GC.
///
/// "Stored in a field of a GC struct" - This includes primitive types like `i32`,
/// but also GC structs themselves (because they can be nested).
///
/// This trait is unsafe to implement for several reasons, ranging from:
///
/// *   The common-sense: API users aren't supposed to know or care about this.
///     It is only public so that the public macros can see it. Use the macros.
///
/// *   ...To the obvious: `Storage` objects are full of pointers and if
///     `into_heap` puts garbage into them, GC will crash.
///
/// *   ...To the subtle: `from_heap` receives a non-mut reference to a heap
///     value. But there may exist gc-references to that value, in which case
///     `from_heap` (or other code it calls) could modify the value while this
///     direct, non-mut reference exists, which could lead to crashes (due to
///     changing enums if nothing else) - all without using any unsafe code.
///
pub unsafe trait IntoHeap<'a>: Sized {
    /// The type of the value when it is physically stored in the heap.
    type In: InHeap<'a, Out=Self>;

    /// Convert the value to the form it should have in the heap.
    /// This is for macro-generated code to call.
    fn into_heap(self) -> Self::In;
}

pub fn heap_type_id<'a, T: InHeap<'a>>() -> usize {
    mark_entry_point::<T> as *const () as usize
}

macro_rules! gc_trivial_impl {
    ($t:ty) => {
        unsafe impl<'a> InHeap<'a> for $t {
            type Out = $t;
            unsafe fn mark(_ptr: *mut $t) {}
            unsafe fn from_heap(&self) -> $t { self.clone() }
        }

        unsafe impl<'a> IntoHeap<'a> for $t {
            type In = $t;
            fn into_heap(self) -> $t { self }
        }
    }
}

gc_trivial_impl!(bool);
gc_trivial_impl!(char);
gc_trivial_impl!(i8);
gc_trivial_impl!(u8);
gc_trivial_impl!(i16);
gc_trivial_impl!(u16);
gc_trivial_impl!(i32);
gc_trivial_impl!(u32);
gc_trivial_impl!(i64);
gc_trivial_impl!(u64);
gc_trivial_impl!(isize);
gc_trivial_impl!(usize);
gc_trivial_impl!(f32);
gc_trivial_impl!(f64);

use std::rc::Rc;
gc_trivial_impl!(Rc<String>);

/*
// 'static types are heap-safe because ref types are never 'static.
// Unfortunately I can't make the compiler understand this: the rules
// to prevent conflicting trait impls make this conflict with almost
// everything.
unsafe impl<'a, T: Clone + 'static> InHeap<'a> for T { ...trivial... }
unsafe impl<'a, T: Clone + 'static> IntoHeap<'a> for T { ...trivial... }
*/
<commit_msg>Add an associated type bound to enforce the rule that `InHeap` and `IntoHeap` types must be paired 1-1. (`IntoHeap::In` already has the corresponding bound in the opposite direction. I think both bounds are necessary to enforce the rule, though honestly it is not the sort of rule that anyone is likely to break.)<commit_after>/// Trait implemented by all types that can be stored directly in the GC heap:
/// the `Storage` types associated with any `IntoHeap` type.
///
pub unsafe trait InHeap<'a>: Sized {
    type Out: IntoHeap<'a, In=Self>;

    unsafe fn mark(ptr: *mut Self);

    /// Extract the value from the heap. This is for macro-generated code to
    /// call; it is impossible for ordinary users to call this safely, because
    /// `self` must be a direct, unwrapped reference to a value stored in the
    /// GC heap, which ordinary users cannot obtain.
    ///
    /// This turns any raw pointers in the `Storage` value into safe
    /// references, so while it's a dangerous function, the result of a correct
    /// call can be safely handed out to user code.
    ///
    unsafe fn from_heap(&self) -> Self::Out;
}

/// Non-inlined function that serves as an entry point to marking. This is used
/// for marking root set entries.
pub unsafe fn mark_entry_point<'a, T: InHeap<'a>>(addr: *mut ()) {
    InHeap::mark(addr as *mut T);
}

/// `IntoHeap` types are safe to use and can be stored in a field of a GC struct.
///
/// "Safe to use" means they don't expose raw pointers to GC memory, and they
/// obey Rust's safety and aliasing rules. If a `IntoHeap` value contains a
/// pointer to a GC allocation, then that allocation (and everything reachable
/// from it) is protected from GC.
///
/// "Stored in a field of a GC struct" - This includes primitive types like `i32`,
/// but also GC structs themselves (because they can be nested).
///
/// This trait is unsafe to implement for several reasons, ranging from:
///
/// *   The common-sense: API users aren't supposed to know or care about this.
///     It is only public so that the public macros can see it. Use the macros.
///
/// *   ...To the obvious: `Storage` objects are full of pointers and if
///     `into_heap` puts garbage into them, GC will crash.
///
/// *   ...To the subtle: `from_heap` receives a non-mut reference to a heap
///     value. But there may exist gc-references to that value, in which case
///     `from_heap` (or other code it calls) could modify the value while this
///     direct, non-mut reference exists, which could lead to crashes (due to
///     changing enums if nothing else) - all without using any unsafe code.
///
pub unsafe trait IntoHeap<'a>: Sized {
    /// The type of the value when it is physically stored in the heap.
    type In: InHeap<'a, Out=Self>;

    /// Convert the value to the form it should have in the heap.
    /// This is for macro-generated code to call.
    fn into_heap(self) -> Self::In;
}

pub fn heap_type_id<'a, T: InHeap<'a>>() -> usize {
    mark_entry_point::<T> as *const () as usize
}

macro_rules! gc_trivial_impl {
    ($t:ty) => {
        unsafe impl<'a> InHeap<'a> for $t {
            type Out = $t;
            unsafe fn mark(_ptr: *mut $t) {}
            unsafe fn from_heap(&self) -> $t { self.clone() }
        }

        unsafe impl<'a> IntoHeap<'a> for $t {
            type In = $t;
            fn into_heap(self) -> $t { self }
        }
    }
}

gc_trivial_impl!(bool);
gc_trivial_impl!(char);
gc_trivial_impl!(i8);
gc_trivial_impl!(u8);
gc_trivial_impl!(i16);
gc_trivial_impl!(u16);
gc_trivial_impl!(i32);
gc_trivial_impl!(u32);
gc_trivial_impl!(i64);
gc_trivial_impl!(u64);
gc_trivial_impl!(isize);
gc_trivial_impl!(usize);
gc_trivial_impl!(f32);
gc_trivial_impl!(f64);

use std::rc::Rc;
gc_trivial_impl!(Rc<String>);

/*
// 'static types are heap-safe because ref types are never 'static.
// Unfortunately I can't make the compiler understand this: the rules
// to prevent conflicting trait impls make this conflict with almost
// everything.
unsafe impl<'a, T: Clone + 'static> InHeap<'a> for T { ...trivial... }
unsafe impl<'a, T: Clone + 'static> IntoHeap<'a> for T { ...trivial... }
*/
<|endoftext|>


<commit_before>//! Some unit tests for all the internal functionality of lrs

#[cfg(test)]
#[allow(dead_code)]
#[allow(unused_macros)]
#[allow(unused_imports)]
mod tests {
    use terms::*;
    use equation::*;

    // macro_rules! symbol {
    //     ($v:expr, $s:expr) => ( Symbol { val: $v, state: $s } );
    // }

    #[test]
     fn lrs_symbol_create() {
        let s = symbol!["A"];
        assert!(s.val == 'A');
        assert!(s.state == true);
    }

    #[test]
    fn lrs_term_create() {
        let term = term!["A", "!🐎", "🐟", "C"];

        assert!(term.contains(&symbol!["A"]));
        assert!(term.contains(&symbol!["!🐎"]));
        assert!(term.contains(&symbol!["🐟"]));
        assert!(term.contains(&symbol!["C"]));
    }

    #[test]
    #[allow(unused_variables)]
    fn lrs_term_remove() {
        let mut term = term!["A", "!🐎", "🐟", "C"];

        term.remove(symbol!["A"]);
        assert!(! term.contains(&symbol!["A"]));

        term.remove(symbol!["!🐎"]);
        assert!(! term.contains(&symbol!["!🐎"]));

        term.remove(symbol!["🐟"]);
        assert!(! term.contains(&symbol!["🐟"]));

        term.remove(symbol!["C"]);
        assert!(! term.contains(&symbol!["C"]));
    }
}
<commit_msg>Proving that multi-width utf-8 characters work as well<commit_after>//! Some unit tests for all the internal functionality of lrs

#[cfg(test)]
#[allow(dead_code)]
#[allow(unused_macros)]
#[allow(unused_imports)]
mod tests {
    use terms::*;
    use equation::*;

    // macro_rules! symbol {
    //     ($v:expr, $s:expr) => ( Symbol { val: $v, state: $s } );
    // }

    #[test]
     fn lrs_symbol_create() {
        let s = symbol!["A"];
        assert!(s.val == 'A');
        assert!(s.state == true);
    }

    #[test]
    fn lrs_term_create() {
        let term = term!["A", "!🐎", "🐟", "!🚀"];

        assert!(term.contains(&symbol!["A"]));
        assert!(term.contains(&symbol!["!🐎"]));
        assert!(term.contains(&symbol!["🐟"]));
        assert!(term.contains(&symbol!["!🚀"]));
    }

    #[test]
    #[allow(unused_variables)]
    fn lrs_term_remove() {
        let mut term = term!["A", "!🐎", "🐟", "!🚀"];

        term.remove(symbol!["A"]);
        assert!(! term.contains(&symbol!["A"]));

        term.remove(symbol!["!🐎"]);
        assert!(! term.contains(&symbol!["!🐎"]));

        term.remove(symbol!["🐟"]);
        assert!(! term.contains(&symbol!["🐟"]));

        term.remove(symbol!["!🚀"]);
        assert!(! term.contains(&symbol!["!🚀"]));
    }
}
<|endoftext|>


<commit_before>use std::ptr::null;
use libc::{c_int, c_char, c_double};
use std::ffi::CString;
use std::sync::{StaticMutex, MUTEX_INIT};
use utils::_string;

#[link(name="gdal")]
extern {
    fn OGRRegisterAll();
    fn OGROpen(pszName: *const c_char, bUpdate: c_int, pahDriverList: *const ()) -> *const ();
    fn OGR_DS_GetLayerCount(hDS: *const ()) -> c_int;
    fn OGR_DS_Destroy(hDataSource: *const ());
    fn OGR_DS_GetLayer(hDS: *const (), iLayer: c_int) -> *const ();
    fn OGR_L_GetLayerDefn(hLayer: *const ()) -> *const ();
    fn OGR_L_GetNextFeature(hLayer: *const ()) -> *const ();
    fn OGR_FD_GetFieldCount(hDefn: *const ()) -> c_int;
    fn OGR_FD_GetFieldDefn(hDefn: *const (), iField: c_int) -> *const ();
    fn OGR_F_GetFieldIndex(hFeat: *const (), pszName: *const c_char) -> c_int;
    fn OGR_F_GetFieldDefnRef(hFeat: *const (), i: c_int) -> *const ();
    fn OGR_F_GetFieldAsString(hFeat: *const (), iField: c_int) -> *const c_char;
    fn OGR_F_GetFieldAsDouble(hFeat: *const (), iField: c_int) -> c_double;
    fn OGR_F_GetGeometryRef(hFeat: *const ()) -> *const ();
    fn OGR_F_Destroy(hFeat: *const ());
    fn OGR_G_CreateFromWkt(ppszData: &mut *const c_char, hSRS: *const (), phGeometry: &mut *const ()) -> c_int;
    fn OGR_G_ExportToWkt(hGeom: *const (), ppszSrcText: &mut *const c_char) -> c_int;
    fn OGR_G_ExportToJson(hGeometry: *const ()) -> *const c_char;
    fn OGR_G_DestroyGeometry(hGeom: *mut ());
    fn OGR_Fld_GetNameRef(hDefn: *const ()) -> *const c_char;
    fn OGR_Fld_GetType(hDefn: *const ()) -> c_int;
    fn OGRFree(ptr: *mut ());
    fn VSIFree(ptr: *mut ());
}

const OGRERR_NONE:          c_int = 0;

const OFT_REAL:             c_int = 2;
const OFT_STRING:           c_int = 4;

static mut LOCK: StaticMutex = MUTEX_INIT;
static mut registered_drivers: bool = false;

fn register_drivers() {
    unsafe {
        let _g = LOCK.lock();
        if ! registered_drivers {
            OGRRegisterAll();
            registered_drivers = true;
        }
    }
}

pub struct VectorDataset {
    c_dataset: *const (),
}

impl VectorDataset {
    pub fn count(&self) -> isize {
        return unsafe { OGR_DS_GetLayerCount(self.c_dataset) } as isize;
    }

    pub fn layer<'a>(&'a self, idx: isize) -> Option<Layer<'a>> {
        let c_layer = unsafe { OGR_DS_GetLayer(self.c_dataset, idx as c_int) };
        return match c_layer.is_null() {
            true  => None,
            false => Some(Layer{_vector_dataset: self, c_layer: c_layer}),
        };
    }
}

impl Drop for VectorDataset {
    fn drop(&mut self) {
        unsafe { OGR_DS_Destroy(self.c_dataset); }
    }
}

pub struct Layer<'a> {
    _vector_dataset: &'a VectorDataset,
    c_layer: *const (),
}

impl<'a> Layer<'a> {
    pub fn fields(&'a self) -> FieldIterator<'a> {
        let c_feature_defn = unsafe { OGR_L_GetLayerDefn(self.c_layer) };
        let total = unsafe { OGR_FD_GetFieldCount(c_feature_defn) } as isize;
        return FieldIterator{
            layer: self,
            c_feature_defn: c_feature_defn,
            next_id: 0,
            total: total
        };
    }

    pub fn features(&'a self) -> FeatureIterator<'a> {
        return FeatureIterator{layer: self};
    }
}

pub struct FieldIterator<'a> {
    layer: &'a Layer<'a>,
    c_feature_defn: *const (),
    next_id: isize,
    total: isize,
}

impl<'a> Iterator for FieldIterator<'a> {
    type Item = Field<'a>;

    #[inline]
    fn next(&mut self) -> Option<Field<'a>> {
        if self.next_id == self.total {
            return None;
        }
        let field = Field{
            _layer: self.layer,
            c_field_defn: unsafe { OGR_FD_GetFieldDefn(
                self.c_feature_defn,
                self.next_id as c_int
            ) }
        };
        self.next_id += 1;
        return Some(field);
    }
}

pub struct Field<'a> {
    _layer: &'a Layer<'a>,
    c_field_defn: *const (),
}

impl<'a> Field<'a> {
    pub fn name(&'a self) -> String {
        let rv = unsafe { OGR_Fld_GetNameRef(self.c_field_defn) };
        return _string(rv);
    }
}

pub struct FeatureIterator<'a> {
    layer: &'a Layer<'a>,
}

impl<'a> Iterator for FeatureIterator<'a> {
    type Item = Feature<'a>;

    #[inline]
    fn next(&mut self) -> Option<Feature<'a>> {
        let c_feature = unsafe { OGR_L_GetNextFeature(self.layer.c_layer) };
        return match c_feature.is_null() {
            true  => None,
            false => Some(Feature{_layer: self.layer, c_feature: c_feature}),
        };
    }
}

pub struct Feature<'a> {
    _layer: &'a Layer<'a>,
    c_feature: *const (),
}

impl<'a> Feature<'a> {
    pub fn field(&self, name: &str) -> Option<FieldValue> {
        let c_name = CString::from_slice(name.as_bytes());
        let field_id = unsafe { OGR_F_GetFieldIndex(self.c_feature, c_name.as_ptr()) };
        if field_id == -1 {
            return None;
        }
        let field_defn = unsafe { OGR_F_GetFieldDefnRef(self.c_feature, field_id) };
        let field_type = unsafe { OGR_Fld_GetType(field_defn) };
        return match field_type {
            OFT_STRING => {
                let rv = unsafe { OGR_F_GetFieldAsString(self.c_feature, field_id) };
                return Some(FieldValue::StringValue(_string(rv)));
            },
            OFT_REAL => {
                let rv = unsafe { OGR_F_GetFieldAsDouble(self.c_feature, field_id) };
                return Some(FieldValue::RealValue(rv as f64));
            },
            _ => panic!("Unknown field type {}", field_type)
        }
    }

    pub fn wkt(&self) -> String {
        let c_geom = unsafe { OGR_F_GetGeometryRef(self.c_feature) };
        let mut c_wkt: *const c_char = null();
        let _err = unsafe { OGR_G_ExportToWkt(c_geom, &mut c_wkt) };
        assert_eq!(_err, OGRERR_NONE);
        let wkt = _string(c_wkt);
        unsafe { OGRFree(c_wkt as *mut ()) };
        return wkt;
    }

    pub fn json(&self) -> String {
        let c_geom = unsafe { OGR_F_GetGeometryRef(self.c_feature) };
        let c_json = unsafe { OGR_G_ExportToJson(c_geom) };
        let json = _string(c_json);
        unsafe { VSIFree(c_json as *mut ()) };
        return json;
    }
}

#[unsafe_destructor]
impl<'a> Drop for Feature<'a> {
    fn drop(&mut self) {
        unsafe { OGR_F_Destroy(self.c_feature); }
    }
}

pub struct Geometry {
    c_geometry: *const (),
}

impl Geometry {
    pub fn bbox(w: f64, s: f64, e: f64, n: f64) -> Geometry {
        let wkt = format!(
            "POLYGON (({} {}, {} {}, {} {}, {} {}, {} {}))",
            w, n,
            e, n,
            e, s,
            w, s,
            w, n,
        );
        let c_wkt = CString::from_slice(wkt.as_bytes());
        let mut c_wkt_ptr: *const c_char = c_wkt.as_ptr();
        let mut c_geom: *const () = null();
        let rv = unsafe { OGR_G_CreateFromWkt(&mut c_wkt_ptr, null(), &mut c_geom) };
        assert_eq!(rv, OGRERR_NONE);
        return Geometry{c_geometry: c_geom};
    }

    pub fn json(&self) -> String {
        let c_json = unsafe { OGR_G_ExportToJson(self.c_geometry) };
        let rv = _string(c_json);
        unsafe { VSIFree(c_json as *mut ()) };
        return rv;
    }
}

impl Drop for Geometry {
    fn drop(&mut self) {
        unsafe { OGR_G_DestroyGeometry(self.c_geometry as *mut ()) };
    }
}

pub fn open(path: &Path) -> Option<VectorDataset> {
    register_drivers();
    let filename = path.as_str().unwrap();
    let c_filename = CString::from_slice(filename.as_bytes());
    let c_dataset = unsafe { OGROpen(c_filename.as_ptr(), 0, null()) };
    return match c_dataset.is_null() {
        true  => None,
        false => Some(VectorDataset{c_dataset: c_dataset}),
    };
}

pub enum FieldValue {
    StringValue(String),
    RealValue(f64),
}

impl FieldValue {
    pub fn as_string(self) -> String {
        match self {
            FieldValue::StringValue(rv) => rv,
            _ => panic!("not a StringValue")
        }
    }

    pub fn as_real(self) -> f64 {
        match self {
            FieldValue::RealValue(rv) => rv,
            _ => panic!("not a RealValue")
        }
    }
}

#[cfg(test)]
mod test {
    use std::path::Path;
    use super::{Feature, FeatureIterator, open, Geometry};

    fn fixtures() -> Path {
        return Path::new(file!()).dir_path().dir_path().join("fixtures");
    }

    fn assert_almost_eq(a: f64, b: f64) {
        let f: f64 = a / b;
        assert!(f < 1.00001);
        assert!(f > 0.99999);
    }

    #[test]
    fn test_layer_count() {
        let ds = open(&fixtures().join("roads.geojson")).unwrap();
        assert_eq!(ds.count(), 1);
    }

    fn with_features<F>(fixture: &str, f: F) where F: Fn(FeatureIterator) {
        let ds = open(&fixtures().join(fixture)).unwrap();
        let layer = ds.layer(0).unwrap();
        f(layer.features());
    }

    fn with_first_feature<F>(fixture: &str, f: F) where F: Fn(Feature) {
        with_features(fixture, |mut features| f(features.next().unwrap()));
    }

    #[test]
    fn test_iterate_features() {
        with_features("roads.geojson", |features| {
            let feature_vec: Vec<Feature> = features.collect();
            assert_eq!(feature_vec.len(), 21);
        });
    }

    #[test]
    fn test_string_field() {
        with_features("roads.geojson", |mut features| {
            let feature = features.next().unwrap();
            assert_eq!(feature.field("highway")
                              .unwrap()
                              .as_string(),
                       "footway".to_string());
            assert_eq!(
                features.filter(|field| {
                    let highway = field.field("highway")
                                       .unwrap()
                                       .as_string();
                    highway == "residential".to_string() })
                    .count(),
                2);
        });
    }

    #[test]
    fn test_float_field() {
        with_first_feature("roads.geojson", |feature| {
            assert_almost_eq(
                feature.field("sort_key")
                       .unwrap()
                       .as_real(),
                -9.0
            );
        });
    }

    #[test]
    fn test_missing_field() {
        with_first_feature("roads.geojson", |feature| {
            assert!(feature.field("no such field").is_none());
        });
    }

    #[test]
    fn test_wkt() {
        with_first_feature("roads.geojson", |feature| {
            let wkt = feature.wkt();
            let wkt_ok = format!("{}{}",
                "LINESTRING (26.1019276 44.4302748,",
                "26.1019382 44.4303191,26.1020002 44.4304202)"
                ).to_string();
            assert_eq!(wkt, wkt_ok);
        });
    }

    #[test]
    fn test_json() {
        with_first_feature("roads.geojson", |feature| {
            let json = feature.json();
            let json_ok = format!("{}{}{}{}",
                "{ \"type\": \"LineString\", \"coordinates\": [ ",
                "[ 26.1019276, 44.4302748 ], ",
                "[ 26.1019382, 44.4303191 ], ",
                "[ 26.1020002, 44.4304202 ] ] }"
                ).to_string();
            assert_eq!(json, json_ok);
        });
    }

    #[test]
    fn test_schema() {
        let ds = open(&fixtures().join("roads.geojson")).unwrap();
        let layer = ds.layer(0).unwrap();
        let name_list: Vec<String> = layer
            .fields()
            .map(|f| f.name())
            .collect();
        let ok_names: Vec<String> = vec!(
            "kind", "sort_key", "is_link", "is_tunnel",
            "is_bridge", "railway", "highway")
            .iter().map(|s| s.to_string()).collect();
        assert_eq!(name_list, ok_names);
    }

    #[test]
    fn test_create_bbox() {
        let bbox = Geometry::bbox(-27., 33., 52., 85.);
        assert_eq!(bbox.json(), "{ \"type\": \"Polygon\", \"coordinates\": [ [ [ -27.0, 85.0 ], [ 52.0, 85.0 ], [ 52.0, 33.0 ], [ -27.0, 33.0 ], [ -27.0, 85.0 ] ] ] }");
    }
}
<commit_msg>set a spatial filter<commit_after>use std::ptr::null;
use libc::{c_int, c_char, c_double};
use std::ffi::CString;
use std::sync::{StaticMutex, MUTEX_INIT};
use utils::_string;

#[link(name="gdal")]
extern {
    fn OGRRegisterAll();
    fn OGROpen(pszName: *const c_char, bUpdate: c_int, pahDriverList: *const ()) -> *const ();
    fn OGR_DS_GetLayerCount(hDS: *const ()) -> c_int;
    fn OGR_DS_Destroy(hDataSource: *const ());
    fn OGR_DS_GetLayer(hDS: *const (), iLayer: c_int) -> *const ();
    fn OGR_L_GetLayerDefn(hLayer: *const ()) -> *const ();
    fn OGR_L_GetNextFeature(hLayer: *const ()) -> *const ();
    fn OGR_L_SetSpatialFilter(hLayer: *const (), hGeom: *const ());
    fn OGR_FD_GetFieldCount(hDefn: *const ()) -> c_int;
    fn OGR_FD_GetFieldDefn(hDefn: *const (), iField: c_int) -> *const ();
    fn OGR_F_GetFieldIndex(hFeat: *const (), pszName: *const c_char) -> c_int;
    fn OGR_F_GetFieldDefnRef(hFeat: *const (), i: c_int) -> *const ();
    fn OGR_F_GetFieldAsString(hFeat: *const (), iField: c_int) -> *const c_char;
    fn OGR_F_GetFieldAsDouble(hFeat: *const (), iField: c_int) -> c_double;
    fn OGR_F_GetGeometryRef(hFeat: *const ()) -> *const ();
    fn OGR_F_Destroy(hFeat: *const ());
    fn OGR_G_CreateFromWkt(ppszData: &mut *const c_char, hSRS: *const (), phGeometry: &mut *const ()) -> c_int;
    fn OGR_G_ExportToWkt(hGeom: *const (), ppszSrcText: &mut *const c_char) -> c_int;
    fn OGR_G_ExportToJson(hGeometry: *const ()) -> *const c_char;
    fn OGR_G_DestroyGeometry(hGeom: *mut ());
    fn OGR_Fld_GetNameRef(hDefn: *const ()) -> *const c_char;
    fn OGR_Fld_GetType(hDefn: *const ()) -> c_int;
    fn OGRFree(ptr: *mut ());
    fn VSIFree(ptr: *mut ());
}

const OGRERR_NONE:          c_int = 0;

const OFT_REAL:             c_int = 2;
const OFT_STRING:           c_int = 4;

static mut LOCK: StaticMutex = MUTEX_INIT;
static mut registered_drivers: bool = false;

fn register_drivers() {
    unsafe {
        let _g = LOCK.lock();
        if ! registered_drivers {
            OGRRegisterAll();
            registered_drivers = true;
        }
    }
}

pub struct VectorDataset {
    c_dataset: *const (),
}

impl VectorDataset {
    pub fn count(&self) -> isize {
        return unsafe { OGR_DS_GetLayerCount(self.c_dataset) } as isize;
    }

    pub fn layer<'a>(&'a self, idx: isize) -> Option<Layer<'a>> {
        let c_layer = unsafe { OGR_DS_GetLayer(self.c_dataset, idx as c_int) };
        return match c_layer.is_null() {
            true  => None,
            false => Some(Layer{_vector_dataset: self, c_layer: c_layer}),
        };
    }
}

impl Drop for VectorDataset {
    fn drop(&mut self) {
        unsafe { OGR_DS_Destroy(self.c_dataset); }
    }
}

pub struct Layer<'a> {
    _vector_dataset: &'a VectorDataset,
    c_layer: *const (),
}

impl<'a> Layer<'a> {
    pub fn fields(&'a self) -> FieldIterator<'a> {
        let c_feature_defn = unsafe { OGR_L_GetLayerDefn(self.c_layer) };
        let total = unsafe { OGR_FD_GetFieldCount(c_feature_defn) } as isize;
        return FieldIterator{
            layer: self,
            c_feature_defn: c_feature_defn,
            next_id: 0,
            total: total
        };
    }

    pub fn features(&'a self) -> FeatureIterator<'a> {
        return FeatureIterator{layer: self};
    }

    pub fn set_spatial_filter(&'a self, geometry: &Geometry) {
        unsafe { OGR_L_SetSpatialFilter(self.c_layer, geometry.c_geometry) };
    }
}

pub struct FieldIterator<'a> {
    layer: &'a Layer<'a>,
    c_feature_defn: *const (),
    next_id: isize,
    total: isize,
}

impl<'a> Iterator for FieldIterator<'a> {
    type Item = Field<'a>;

    #[inline]
    fn next(&mut self) -> Option<Field<'a>> {
        if self.next_id == self.total {
            return None;
        }
        let field = Field{
            _layer: self.layer,
            c_field_defn: unsafe { OGR_FD_GetFieldDefn(
                self.c_feature_defn,
                self.next_id as c_int
            ) }
        };
        self.next_id += 1;
        return Some(field);
    }
}

pub struct Field<'a> {
    _layer: &'a Layer<'a>,
    c_field_defn: *const (),
}

impl<'a> Field<'a> {
    pub fn name(&'a self) -> String {
        let rv = unsafe { OGR_Fld_GetNameRef(self.c_field_defn) };
        return _string(rv);
    }
}

pub struct FeatureIterator<'a> {
    layer: &'a Layer<'a>,
}

impl<'a> Iterator for FeatureIterator<'a> {
    type Item = Feature<'a>;

    #[inline]
    fn next(&mut self) -> Option<Feature<'a>> {
        let c_feature = unsafe { OGR_L_GetNextFeature(self.layer.c_layer) };
        return match c_feature.is_null() {
            true  => None,
            false => Some(Feature{_layer: self.layer, c_feature: c_feature}),
        };
    }
}

pub struct Feature<'a> {
    _layer: &'a Layer<'a>,
    c_feature: *const (),
}

impl<'a> Feature<'a> {
    pub fn field(&self, name: &str) -> Option<FieldValue> {
        let c_name = CString::from_slice(name.as_bytes());
        let field_id = unsafe { OGR_F_GetFieldIndex(self.c_feature, c_name.as_ptr()) };
        if field_id == -1 {
            return None;
        }
        let field_defn = unsafe { OGR_F_GetFieldDefnRef(self.c_feature, field_id) };
        let field_type = unsafe { OGR_Fld_GetType(field_defn) };
        return match field_type {
            OFT_STRING => {
                let rv = unsafe { OGR_F_GetFieldAsString(self.c_feature, field_id) };
                return Some(FieldValue::StringValue(_string(rv)));
            },
            OFT_REAL => {
                let rv = unsafe { OGR_F_GetFieldAsDouble(self.c_feature, field_id) };
                return Some(FieldValue::RealValue(rv as f64));
            },
            _ => panic!("Unknown field type {}", field_type)
        }
    }

    pub fn wkt(&self) -> String {
        let c_geom = unsafe { OGR_F_GetGeometryRef(self.c_feature) };
        let mut c_wkt: *const c_char = null();
        let _err = unsafe { OGR_G_ExportToWkt(c_geom, &mut c_wkt) };
        assert_eq!(_err, OGRERR_NONE);
        let wkt = _string(c_wkt);
        unsafe { OGRFree(c_wkt as *mut ()) };
        return wkt;
    }

    pub fn json(&self) -> String {
        let c_geom = unsafe { OGR_F_GetGeometryRef(self.c_feature) };
        let c_json = unsafe { OGR_G_ExportToJson(c_geom) };
        let json = _string(c_json);
        unsafe { VSIFree(c_json as *mut ()) };
        return json;
    }
}

#[unsafe_destructor]
impl<'a> Drop for Feature<'a> {
    fn drop(&mut self) {
        unsafe { OGR_F_Destroy(self.c_feature); }
    }
}

pub struct Geometry {
    c_geometry: *const (),
}

impl Geometry {
    pub fn bbox(w: f64, s: f64, e: f64, n: f64) -> Geometry {
        let wkt = format!(
            "POLYGON (({} {}, {} {}, {} {}, {} {}, {} {}))",
            w, n,
            e, n,
            e, s,
            w, s,
            w, n,
        );
        let c_wkt = CString::from_slice(wkt.as_bytes());
        let mut c_wkt_ptr: *const c_char = c_wkt.as_ptr();
        let mut c_geom: *const () = null();
        let rv = unsafe { OGR_G_CreateFromWkt(&mut c_wkt_ptr, null(), &mut c_geom) };
        assert_eq!(rv, OGRERR_NONE);
        return Geometry{c_geometry: c_geom};
    }

    pub fn json(&self) -> String {
        let c_json = unsafe { OGR_G_ExportToJson(self.c_geometry) };
        let rv = _string(c_json);
        unsafe { VSIFree(c_json as *mut ()) };
        return rv;
    }
}

impl Drop for Geometry {
    fn drop(&mut self) {
        unsafe { OGR_G_DestroyGeometry(self.c_geometry as *mut ()) };
    }
}

pub fn open(path: &Path) -> Option<VectorDataset> {
    register_drivers();
    let filename = path.as_str().unwrap();
    let c_filename = CString::from_slice(filename.as_bytes());
    let c_dataset = unsafe { OGROpen(c_filename.as_ptr(), 0, null()) };
    return match c_dataset.is_null() {
        true  => None,
        false => Some(VectorDataset{c_dataset: c_dataset}),
    };
}

pub enum FieldValue {
    StringValue(String),
    RealValue(f64),
}

impl FieldValue {
    pub fn as_string(self) -> String {
        match self {
            FieldValue::StringValue(rv) => rv,
            _ => panic!("not a StringValue")
        }
    }

    pub fn as_real(self) -> f64 {
        match self {
            FieldValue::RealValue(rv) => rv,
            _ => panic!("not a RealValue")
        }
    }
}

#[cfg(test)]
mod test {
    use std::path::Path;
    use super::{Feature, FeatureIterator, open, Geometry};

    fn fixtures() -> Path {
        return Path::new(file!()).dir_path().dir_path().join("fixtures");
    }

    fn assert_almost_eq(a: f64, b: f64) {
        let f: f64 = a / b;
        assert!(f < 1.00001);
        assert!(f > 0.99999);
    }

    #[test]
    fn test_layer_count() {
        let ds = open(&fixtures().join("roads.geojson")).unwrap();
        assert_eq!(ds.count(), 1);
    }

    fn with_features<F>(fixture: &str, f: F) where F: Fn(FeatureIterator) {
        let ds = open(&fixtures().join(fixture)).unwrap();
        let layer = ds.layer(0).unwrap();
        f(layer.features());
    }

    fn with_first_feature<F>(fixture: &str, f: F) where F: Fn(Feature) {
        with_features(fixture, |mut features| f(features.next().unwrap()));
    }

    #[test]
    fn test_iterate_features() {
        with_features("roads.geojson", |features| {
            let feature_vec: Vec<Feature> = features.collect();
            assert_eq!(feature_vec.len(), 21);
        });
    }

    #[test]
    fn test_string_field() {
        with_features("roads.geojson", |mut features| {
            let feature = features.next().unwrap();
            assert_eq!(feature.field("highway")
                              .unwrap()
                              .as_string(),
                       "footway".to_string());
            assert_eq!(
                features.filter(|field| {
                    let highway = field.field("highway")
                                       .unwrap()
                                       .as_string();
                    highway == "residential".to_string() })
                    .count(),
                2);
        });
    }

    #[test]
    fn test_float_field() {
        with_first_feature("roads.geojson", |feature| {
            assert_almost_eq(
                feature.field("sort_key")
                       .unwrap()
                       .as_real(),
                -9.0
            );
        });
    }

    #[test]
    fn test_missing_field() {
        with_first_feature("roads.geojson", |feature| {
            assert!(feature.field("no such field").is_none());
        });
    }

    #[test]
    fn test_wkt() {
        with_first_feature("roads.geojson", |feature| {
            let wkt = feature.wkt();
            let wkt_ok = format!("{}{}",
                "LINESTRING (26.1019276 44.4302748,",
                "26.1019382 44.4303191,26.1020002 44.4304202)"
                ).to_string();
            assert_eq!(wkt, wkt_ok);
        });
    }

    #[test]
    fn test_json() {
        with_first_feature("roads.geojson", |feature| {
            let json = feature.json();
            let json_ok = format!("{}{}{}{}",
                "{ \"type\": \"LineString\", \"coordinates\": [ ",
                "[ 26.1019276, 44.4302748 ], ",
                "[ 26.1019382, 44.4303191 ], ",
                "[ 26.1020002, 44.4304202 ] ] }"
                ).to_string();
            assert_eq!(json, json_ok);
        });
    }

    #[test]
    fn test_schema() {
        let ds = open(&fixtures().join("roads.geojson")).unwrap();
        let layer = ds.layer(0).unwrap();
        let name_list: Vec<String> = layer
            .fields()
            .map(|f| f.name())
            .collect();
        let ok_names: Vec<String> = vec!(
            "kind", "sort_key", "is_link", "is_tunnel",
            "is_bridge", "railway", "highway")
            .iter().map(|s| s.to_string()).collect();
        assert_eq!(name_list, ok_names);
    }

    #[test]
    fn test_create_bbox() {
        let bbox = Geometry::bbox(-27., 33., 52., 85.);
        assert_eq!(bbox.json(), "{ \"type\": \"Polygon\", \"coordinates\": [ [ [ -27.0, 85.0 ], [ 52.0, 85.0 ], [ 52.0, 33.0 ], [ -27.0, 33.0 ], [ -27.0, 85.0 ] ] ] }");
    }

    #[test]
    fn test_spatial_filter() {
        let ds = open(&fixtures().join("roads.geojson")).unwrap();
        let layer = ds.layer(0).unwrap();

        let all_features: Vec<Feature> = layer.features().collect();
        assert_eq!(all_features.len(), 21);

        let bbox = Geometry::bbox(26.1017, 44.4297, 26.1025, 44.4303);
        layer.set_spatial_filter(&bbox);

        let some_features: Vec<Feature> = layer.features().collect();
        assert_eq!(some_features.len(), 7);
    }
}
<|endoftext|>


<commit_before>use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
use rustc::hir::*;
use rustc::hir::intravisit::*;
use syntax::ast::{LitKind, NodeId, DUMMY_NODE_ID};
use syntax::codemap::{dummy_spanned, Span, DUMMY_SP};
use syntax::util::ThinVec;
use utils::{in_macro, snippet_opt, span_lint_and_then, SpanlessEq};

/// **What it does:** Checks for boolean expressions that can be written more
/// concisely.
///
/// **Why is this bad?** Readability of boolean expressions suffers from
/// unnecessary duplication.
///
/// **Known problems:** Ignores short circuiting behavior of `||` and
/// `&&`. Ignores `|`, `&` and `^`.
///
/// **Example:**
/// ```rust
/// if a && true  // should be: if a
/// if !(a == b)  // should be: if a != b
/// ```
declare_lint! {
    pub NONMINIMAL_BOOL,
    Allow,
    "boolean expressions that can be written more concisely"
}

/// **What it does:** Checks for boolean expressions that contain terminals that
/// can be eliminated.
///
/// **Why is this bad?** This is most likely a logic bug.
///
/// **Known problems:** Ignores short circuiting behavior.
///
/// **Example:**
/// ```rust
/// if a && b || a { ... }
/// ```
/// The `b` is unnecessary, the expression is equivalent to `if a`.
declare_lint! {
    pub LOGIC_BUG,
    Warn,
    "boolean expressions that contain terminals which can be eliminated"
}

const METHODS_WITH_NEGATION: [(&str, &str); 4] = [
    ("is_some", "is_none"),
    ("is_none", "is_some"),
    ("is_err", "is_ok"),
    ("is_ok", "is_err"),
];

#[derive(Copy, Clone)]
pub struct NonminimalBool;

impl LintPass for NonminimalBool {
    fn get_lints(&self) -> LintArray {
        lint_array!(NONMINIMAL_BOOL, LOGIC_BUG)
    }
}

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NonminimalBool {
    fn check_fn(
        &mut self,
        cx: &LateContext<'a, 'tcx>,
        _: intravisit::FnKind<'tcx>,
        _: &'tcx FnDecl,
        body: &'tcx Body,
        _: Span,
        _: NodeId,
    ) {
        NonminimalBoolVisitor { cx: cx }.visit_body(body)
    }
}

struct NonminimalBoolVisitor<'a, 'tcx: 'a> {
    cx: &'a LateContext<'a, 'tcx>,
}

use quine_mc_cluskey::Bool;
struct Hir2Qmm<'a, 'tcx: 'a, 'v> {
    terminals: Vec<&'v Expr>,
    cx: &'a LateContext<'a, 'tcx>,
}

impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
    fn extract(&mut self, op: BinOp_, a: &[&'v Expr], mut v: Vec<Bool>) -> Result<Vec<Bool>, String> {
        for a in a {
            if let ExprBinary(binop, ref lhs, ref rhs) = a.node {
                if binop.node == op {
                    v = self.extract(op, &[lhs, rhs], v)?;
                    continue;
                }
            }
            v.push(self.run(a)?);
        }
        Ok(v)
    }

    fn run(&mut self, e: &'v Expr) -> Result<Bool, String> {
        // prevent folding of `cfg!` macros and the like
        if !in_macro(e.span) {
            match e.node {
                ExprUnary(UnNot, ref inner) => return Ok(Bool::Not(box self.run(inner)?)),
                ExprBinary(binop, ref lhs, ref rhs) => match binop.node {
                    BiOr => return Ok(Bool::Or(self.extract(BiOr, &[lhs, rhs], Vec::new())?)),
                    BiAnd => return Ok(Bool::And(self.extract(BiAnd, &[lhs, rhs], Vec::new())?)),
                    _ => (),
                },
                ExprLit(ref lit) => match lit.node {
                    LitKind::Bool(true) => return Ok(Bool::True),
                    LitKind::Bool(false) => return Ok(Bool::False),
                    _ => (),
                },
                _ => (),
            }
        }
        for (n, expr) in self.terminals.iter().enumerate() {
            if SpanlessEq::new(self.cx).ignore_fn().eq_expr(e, expr) {
                #[allow(cast_possible_truncation)]
                return Ok(Bool::Term(n as u8));
            }
            let negated = match e.node {
                ExprBinary(binop, ref lhs, ref rhs) => {
                    let mk_expr = |op| {
                        Expr {
                            id: DUMMY_NODE_ID,
                            hir_id: DUMMY_HIR_ID,
                            span: DUMMY_SP,
                            attrs: ThinVec::new(),
                            node: ExprBinary(dummy_spanned(op), lhs.clone(), rhs.clone()),
                        }
                    };
                    match binop.node {
                        BiEq => mk_expr(BiNe),
                        BiNe => mk_expr(BiEq),
                        BiGt => mk_expr(BiLe),
                        BiGe => mk_expr(BiLt),
                        BiLt => mk_expr(BiGe),
                        BiLe => mk_expr(BiGt),
                        _ => continue,
                    }
                },
                _ => continue,
            };
            if SpanlessEq::new(self.cx).ignore_fn().eq_expr(&negated, expr) {
                #[allow(cast_possible_truncation)]
                return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
            }
        }
        let n = self.terminals.len();
        self.terminals.push(e);
        if n < 32 {
            #[allow(cast_possible_truncation)]
            Ok(Bool::Term(n as u8))
        } else {
            Err("too many literals".to_owned())
        }
    }
}

fn suggest(cx: &LateContext, suggestion: &Bool, terminals: &[&Expr]) -> String {
    fn recurse(brackets: bool, cx: &LateContext, suggestion: &Bool, terminals: &[&Expr], mut s: String) -> String {
        use quine_mc_cluskey::Bool::*;
        let snip = |e: &Expr| snippet_opt(cx, e.span).expect("don't try to improve booleans created by macros");
        match *suggestion {
            True => {
                s.push_str("true");
                s
            },
            False => {
                s.push_str("false");
                s
            },
            Not(ref inner) => match **inner {
                And(_) | Or(_) => {
                    s.push('!');
                    recurse(true, cx, inner, terminals, s)
                },
                Term(n) => if let ExprBinary(binop, ref lhs, ref rhs) = terminals[n as usize].node {
                    let op = match binop.node {
                        BiEq => " != ",
                        BiNe => " == ",
                        BiLt => " >= ",
                        BiGt => " <= ",
                        BiLe => " > ",
                        BiGe => " < ",
                        _ => {
                            s.push('!');
                            return recurse(true, cx, inner, terminals, s);
                        },
                    };
                    s.push_str(&snip(lhs));
                    s.push_str(op);
                    s.push_str(&snip(rhs));
                    s
                } else {
                    s.push('!');
                    recurse(false, cx, inner, terminals, s)
                },
                _ => {
                    s.push('!');
                    recurse(false, cx, inner, terminals, s)
                },
            },
            And(ref v) => {
                if brackets {
                    s.push('(');
                }
                if let Or(_) = v[0] {
                    s = recurse(true, cx, &v[0], terminals, s);
                } else {
                    s = recurse(false, cx, &v[0], terminals, s);
                }
                for inner in &v[1..] {
                    s.push_str(" && ");
                    if let Or(_) = *inner {
                        s = recurse(true, cx, inner, terminals, s);
                    } else {
                        s = recurse(false, cx, inner, terminals, s);
                    }
                }
                if brackets {
                    s.push(')');
                }
                s
            },
            Or(ref v) => {
                if brackets {
                    s.push('(');
                }
                s = recurse(false, cx, &v[0], terminals, s);
                for inner in &v[1..] {
                    s.push_str(" || ");
                    s = recurse(false, cx, inner, terminals, s);
                }
                if brackets {
                    s.push(')');
                }
                s
            },
            Term(n) => {
                if brackets {
                    if let ExprBinary(..) = terminals[n as usize].node {
                        s.push('(');
                    }
                }
                s.push_str(&snip(terminals[n as usize]));
                if brackets {
                    if let ExprBinary(..) = terminals[n as usize].node {
                        s.push(')');
                    }
                }
                s
            },
        }
    }
    recurse(false, cx, suggestion, terminals, String::new())
}

fn simple_negate(b: Bool) -> Bool {
    use quine_mc_cluskey::Bool::*;
    match b {
        True => False,
        False => True,
        t @ Term(_) => Not(Box::new(t)),
        And(mut v) => {
            for el in &mut v {
                *el = simple_negate(::std::mem::replace(el, True));
            }
            Or(v)
        },
        Or(mut v) => {
            for el in &mut v {
                *el = simple_negate(::std::mem::replace(el, True));
            }
            And(v)
        },
        Not(inner) => *inner,
    }
}

#[derive(Default)]
struct Stats {
    terminals: [usize; 32],
    negations: usize,
    ops: usize,
}

fn terminal_stats(b: &Bool) -> Stats {
    fn recurse(b: &Bool, stats: &mut Stats) {
        match *b {
            True | False => stats.ops += 1,
            Not(ref inner) => {
                match **inner {
                    And(_) | Or(_) => stats.ops += 1, // brackets are also operations
                    _ => stats.negations += 1,
                }
                recurse(inner, stats);
            },
            And(ref v) | Or(ref v) => {
                stats.ops += v.len() - 1;
                for inner in v {
                    recurse(inner, stats);
                }
            },
            Term(n) => stats.terminals[n as usize] += 1,
        }
    }
    use quine_mc_cluskey::Bool::*;
    let mut stats = Stats::default();
    recurse(b, &mut stats);
    stats
}

impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
    fn bool_expr(&self, e: &Expr) {
        let mut h2q = Hir2Qmm {
            terminals: Vec::new(),
            cx: self.cx,
        };
        if let Ok(expr) = h2q.run(e) {
            if h2q.terminals.len() > 8 {
                // QMC has exponentially slow behavior as the number of terminals increases
                // 8 is reasonable, it takes approximately 0.2 seconds.
                // See #825
                return;
            }

            let stats = terminal_stats(&expr);
            let mut simplified = expr.simplify();
            for simple in Bool::Not(Box::new(expr.clone())).simplify() {
                match simple {
                    Bool::Not(_) | Bool::True | Bool::False => {},
                    _ => simplified.push(Bool::Not(Box::new(simple.clone()))),
                }
                let simple_negated = simple_negate(simple);
                if simplified.iter().any(|s| *s == simple_negated) {
                    continue;
                }
                simplified.push(simple_negated);
            }
            let mut improvements = Vec::new();
            'simplified: for suggestion in &simplified {
                let simplified_stats = terminal_stats(suggestion);
                let mut improvement = false;
                for i in 0..32 {
                    // ignore any "simplifications" that end up requiring a terminal more often
                    // than in the original expression
                    if stats.terminals[i] < simplified_stats.terminals[i] {
                        continue 'simplified;
                    }
                    if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 {
                        span_lint_and_then(
                            self.cx,
                            LOGIC_BUG,
                            e.span,
                            "this boolean expression contains a logic bug",
                            |db| {
                                db.span_help(
                                    h2q.terminals[i].span,
                                    "this expression can be optimized out by applying boolean operations to the \
                                     outer expression",
                                );
                                db.span_suggestion(
                                    e.span,
                                    "it would look like the following",
                                    suggest(self.cx, suggestion, &h2q.terminals),
                                );
                            },
                        );
                        // don't also lint `NONMINIMAL_BOOL`
                        return;
                    }
                    // if the number of occurrences of a terminal decreases or any of the stats
                    // decreases while none increases
                    improvement |= (stats.terminals[i] > simplified_stats.terminals[i])
                        || (stats.negations > simplified_stats.negations && stats.ops == simplified_stats.ops)
                        || (stats.ops > simplified_stats.ops && stats.negations == simplified_stats.negations);
                }
                if improvement {
                    improvements.push(suggestion);
                }
            }
            if !improvements.is_empty() {
                span_lint_and_then(
                    self.cx,
                    NONMINIMAL_BOOL,
                    e.span,
                    "this boolean expression can be simplified",
                    |db| {
                        db.span_suggestions(
                            e.span,
                            "try",
                            improvements
                                .into_iter()
                                .map(|suggestion| suggest(self.cx, suggestion, &h2q.terminals))
                                .collect(),
                        );
                    },
                );
            }
        }
    }

    fn handle_method_call_in_not(&mut self, e: &'tcx Expr, inner: &'tcx Expr) {
        if let ExprMethodCall(ref path, _, ref args) = inner.node {
            if args.len() == 1 {
                METHODS_WITH_NEGATION.iter().for_each(|&(method, negation_method)| {
                    if method == path.name.as_str() {
                        span_lint_and_then(
                            self.cx,
                            NONMINIMAL_BOOL,
                            e.span,
                            "this boolean expression can be simplified",
                            |db| {
                                db.span_suggestion(
                                    e.span,
                                    "try",
                                    negation_method.to_owned()
                                );
                            }
                        )
                    }
                })
            }
        }
    }
}

impl<'a, 'tcx> Visitor<'tcx> for NonminimalBoolVisitor<'a, 'tcx> {
    fn visit_expr(&mut self, e: &'tcx Expr) {
        if in_macro(e.span) {
            return;
        }
        match e.node {
            ExprBinary(binop, _, _) if binop.node == BiOr || binop.node == BiAnd => self.bool_expr(e),
            ExprUnary(UnNot, ref inner) => if self.cx.tables.node_types()[inner.hir_id].is_bool() {
                self.handle_method_call_in_not(e, inner);
                self.bool_expr(e);
            } else {
                walk_expr(self, e);
            },
            _ => walk_expr(self, e),
        }
    }
    fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
        NestedVisitorMap::None
    }
}
<commit_msg>Use both pair orders.<commit_after>use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
use rustc::hir::*;
use rustc::hir::intravisit::*;
use syntax::ast::{LitKind, NodeId, DUMMY_NODE_ID};
use syntax::codemap::{dummy_spanned, Span, DUMMY_SP};
use syntax::util::ThinVec;
use utils::{in_macro, snippet_opt, span_lint_and_then, SpanlessEq};

/// **What it does:** Checks for boolean expressions that can be written more
/// concisely.
///
/// **Why is this bad?** Readability of boolean expressions suffers from
/// unnecessary duplication.
///
/// **Known problems:** Ignores short circuiting behavior of `||` and
/// `&&`. Ignores `|`, `&` and `^`.
///
/// **Example:**
/// ```rust
/// if a && true  // should be: if a
/// if !(a == b)  // should be: if a != b
/// ```
declare_lint! {
    pub NONMINIMAL_BOOL,
    Allow,
    "boolean expressions that can be written more concisely"
}

/// **What it does:** Checks for boolean expressions that contain terminals that
/// can be eliminated.
///
/// **Why is this bad?** This is most likely a logic bug.
///
/// **Known problems:** Ignores short circuiting behavior.
///
/// **Example:**
/// ```rust
/// if a && b || a { ... }
/// ```
/// The `b` is unnecessary, the expression is equivalent to `if a`.
declare_lint! {
    pub LOGIC_BUG,
    Warn,
    "boolean expressions that contain terminals which can be eliminated"
}

// For each pairs, both orders are considered.
const METHODS_WITH_NEGATION: [(&str, &str); 2] = [
    ("is_some", "is_none"),
    ("is_err", "is_ok"),
];

#[derive(Copy, Clone)]
pub struct NonminimalBool;

impl LintPass for NonminimalBool {
    fn get_lints(&self) -> LintArray {
        lint_array!(NONMINIMAL_BOOL, LOGIC_BUG)
    }
}

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NonminimalBool {
    fn check_fn(
        &mut self,
        cx: &LateContext<'a, 'tcx>,
        _: intravisit::FnKind<'tcx>,
        _: &'tcx FnDecl,
        body: &'tcx Body,
        _: Span,
        _: NodeId,
    ) {
        NonminimalBoolVisitor { cx: cx }.visit_body(body)
    }
}

struct NonminimalBoolVisitor<'a, 'tcx: 'a> {
    cx: &'a LateContext<'a, 'tcx>,
}

use quine_mc_cluskey::Bool;
struct Hir2Qmm<'a, 'tcx: 'a, 'v> {
    terminals: Vec<&'v Expr>,
    cx: &'a LateContext<'a, 'tcx>,
}

impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
    fn extract(&mut self, op: BinOp_, a: &[&'v Expr], mut v: Vec<Bool>) -> Result<Vec<Bool>, String> {
        for a in a {
            if let ExprBinary(binop, ref lhs, ref rhs) = a.node {
                if binop.node == op {
                    v = self.extract(op, &[lhs, rhs], v)?;
                    continue;
                }
            }
            v.push(self.run(a)?);
        }
        Ok(v)
    }

    fn run(&mut self, e: &'v Expr) -> Result<Bool, String> {
        // prevent folding of `cfg!` macros and the like
        if !in_macro(e.span) {
            match e.node {
                ExprUnary(UnNot, ref inner) => return Ok(Bool::Not(box self.run(inner)?)),
                ExprBinary(binop, ref lhs, ref rhs) => match binop.node {
                    BiOr => return Ok(Bool::Or(self.extract(BiOr, &[lhs, rhs], Vec::new())?)),
                    BiAnd => return Ok(Bool::And(self.extract(BiAnd, &[lhs, rhs], Vec::new())?)),
                    _ => (),
                },
                ExprLit(ref lit) => match lit.node {
                    LitKind::Bool(true) => return Ok(Bool::True),
                    LitKind::Bool(false) => return Ok(Bool::False),
                    _ => (),
                },
                _ => (),
            }
        }
        for (n, expr) in self.terminals.iter().enumerate() {
            if SpanlessEq::new(self.cx).ignore_fn().eq_expr(e, expr) {
                #[allow(cast_possible_truncation)]
                return Ok(Bool::Term(n as u8));
            }
            let negated = match e.node {
                ExprBinary(binop, ref lhs, ref rhs) => {
                    let mk_expr = |op| {
                        Expr {
                            id: DUMMY_NODE_ID,
                            hir_id: DUMMY_HIR_ID,
                            span: DUMMY_SP,
                            attrs: ThinVec::new(),
                            node: ExprBinary(dummy_spanned(op), lhs.clone(), rhs.clone()),
                        }
                    };
                    match binop.node {
                        BiEq => mk_expr(BiNe),
                        BiNe => mk_expr(BiEq),
                        BiGt => mk_expr(BiLe),
                        BiGe => mk_expr(BiLt),
                        BiLt => mk_expr(BiGe),
                        BiLe => mk_expr(BiGt),
                        _ => continue,
                    }
                },
                _ => continue,
            };
            if SpanlessEq::new(self.cx).ignore_fn().eq_expr(&negated, expr) {
                #[allow(cast_possible_truncation)]
                return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
            }
        }
        let n = self.terminals.len();
        self.terminals.push(e);
        if n < 32 {
            #[allow(cast_possible_truncation)]
            Ok(Bool::Term(n as u8))
        } else {
            Err("too many literals".to_owned())
        }
    }
}

fn suggest(cx: &LateContext, suggestion: &Bool, terminals: &[&Expr]) -> String {
    fn recurse(brackets: bool, cx: &LateContext, suggestion: &Bool, terminals: &[&Expr], mut s: String) -> String {
        use quine_mc_cluskey::Bool::*;
        let snip = |e: &Expr| snippet_opt(cx, e.span).expect("don't try to improve booleans created by macros");
        match *suggestion {
            True => {
                s.push_str("true");
                s
            },
            False => {
                s.push_str("false");
                s
            },
            Not(ref inner) => match **inner {
                And(_) | Or(_) => {
                    s.push('!');
                    recurse(true, cx, inner, terminals, s)
                },
                Term(n) => if let ExprBinary(binop, ref lhs, ref rhs) = terminals[n as usize].node {
                    let op = match binop.node {
                        BiEq => " != ",
                        BiNe => " == ",
                        BiLt => " >= ",
                        BiGt => " <= ",
                        BiLe => " > ",
                        BiGe => " < ",
                        _ => {
                            s.push('!');
                            return recurse(true, cx, inner, terminals, s);
                        },
                    };
                    s.push_str(&snip(lhs));
                    s.push_str(op);
                    s.push_str(&snip(rhs));
                    s
                } else {
                    s.push('!');
                    recurse(false, cx, inner, terminals, s)
                },
                _ => {
                    s.push('!');
                    recurse(false, cx, inner, terminals, s)
                },
            },
            And(ref v) => {
                if brackets {
                    s.push('(');
                }
                if let Or(_) = v[0] {
                    s = recurse(true, cx, &v[0], terminals, s);
                } else {
                    s = recurse(false, cx, &v[0], terminals, s);
                }
                for inner in &v[1..] {
                    s.push_str(" && ");
                    if let Or(_) = *inner {
                        s = recurse(true, cx, inner, terminals, s);
                    } else {
                        s = recurse(false, cx, inner, terminals, s);
                    }
                }
                if brackets {
                    s.push(')');
                }
                s
            },
            Or(ref v) => {
                if brackets {
                    s.push('(');
                }
                s = recurse(false, cx, &v[0], terminals, s);
                for inner in &v[1..] {
                    s.push_str(" || ");
                    s = recurse(false, cx, inner, terminals, s);
                }
                if brackets {
                    s.push(')');
                }
                s
            },
            Term(n) => {
                if brackets {
                    if let ExprBinary(..) = terminals[n as usize].node {
                        s.push('(');
                    }
                }
                s.push_str(&snip(terminals[n as usize]));
                if brackets {
                    if let ExprBinary(..) = terminals[n as usize].node {
                        s.push(')');
                    }
                }
                s
            },
        }
    }
    recurse(false, cx, suggestion, terminals, String::new())
}

fn simple_negate(b: Bool) -> Bool {
    use quine_mc_cluskey::Bool::*;
    match b {
        True => False,
        False => True,
        t @ Term(_) => Not(Box::new(t)),
        And(mut v) => {
            for el in &mut v {
                *el = simple_negate(::std::mem::replace(el, True));
            }
            Or(v)
        },
        Or(mut v) => {
            for el in &mut v {
                *el = simple_negate(::std::mem::replace(el, True));
            }
            And(v)
        },
        Not(inner) => *inner,
    }
}

#[derive(Default)]
struct Stats {
    terminals: [usize; 32],
    negations: usize,
    ops: usize,
}

fn terminal_stats(b: &Bool) -> Stats {
    fn recurse(b: &Bool, stats: &mut Stats) {
        match *b {
            True | False => stats.ops += 1,
            Not(ref inner) => {
                match **inner {
                    And(_) | Or(_) => stats.ops += 1, // brackets are also operations
                    _ => stats.negations += 1,
                }
                recurse(inner, stats);
            },
            And(ref v) | Or(ref v) => {
                stats.ops += v.len() - 1;
                for inner in v {
                    recurse(inner, stats);
                }
            },
            Term(n) => stats.terminals[n as usize] += 1,
        }
    }
    use quine_mc_cluskey::Bool::*;
    let mut stats = Stats::default();
    recurse(b, &mut stats);
    stats
}

impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
    fn bool_expr(&self, e: &Expr) {
        let mut h2q = Hir2Qmm {
            terminals: Vec::new(),
            cx: self.cx,
        };
        if let Ok(expr) = h2q.run(e) {
            if h2q.terminals.len() > 8 {
                // QMC has exponentially slow behavior as the number of terminals increases
                // 8 is reasonable, it takes approximately 0.2 seconds.
                // See #825
                return;
            }

            let stats = terminal_stats(&expr);
            let mut simplified = expr.simplify();
            for simple in Bool::Not(Box::new(expr.clone())).simplify() {
                match simple {
                    Bool::Not(_) | Bool::True | Bool::False => {},
                    _ => simplified.push(Bool::Not(Box::new(simple.clone()))),
                }
                let simple_negated = simple_negate(simple);
                if simplified.iter().any(|s| *s == simple_negated) {
                    continue;
                }
                simplified.push(simple_negated);
            }
            let mut improvements = Vec::new();
            'simplified: for suggestion in &simplified {
                let simplified_stats = terminal_stats(suggestion);
                let mut improvement = false;
                for i in 0..32 {
                    // ignore any "simplifications" that end up requiring a terminal more often
                    // than in the original expression
                    if stats.terminals[i] < simplified_stats.terminals[i] {
                        continue 'simplified;
                    }
                    if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 {
                        span_lint_and_then(
                            self.cx,
                            LOGIC_BUG,
                            e.span,
                            "this boolean expression contains a logic bug",
                            |db| {
                                db.span_help(
                                    h2q.terminals[i].span,
                                    "this expression can be optimized out by applying boolean operations to the \
                                     outer expression",
                                );
                                db.span_suggestion(
                                    e.span,
                                    "it would look like the following",
                                    suggest(self.cx, suggestion, &h2q.terminals),
                                );
                            },
                        );
                        // don't also lint `NONMINIMAL_BOOL`
                        return;
                    }
                    // if the number of occurrences of a terminal decreases or any of the stats
                    // decreases while none increases
                    improvement |= (stats.terminals[i] > simplified_stats.terminals[i])
                        || (stats.negations > simplified_stats.negations && stats.ops == simplified_stats.ops)
                        || (stats.ops > simplified_stats.ops && stats.negations == simplified_stats.negations);
                }
                if improvement {
                    improvements.push(suggestion);
                }
            }
            if !improvements.is_empty() {
                span_lint_and_then(
                    self.cx,
                    NONMINIMAL_BOOL,
                    e.span,
                    "this boolean expression can be simplified",
                    |db| {
                        db.span_suggestions(
                            e.span,
                            "try",
                            improvements
                                .into_iter()
                                .map(|suggestion| suggest(self.cx, suggestion, &h2q.terminals))
                                .collect(),
                        );
                    },
                );
            }
        }
    }

    fn handle_method_call_in_not(&mut self, e: &'tcx Expr, inner: &'tcx Expr) {
        if let ExprMethodCall(ref path, _, ref args) = inner.node {
            if args.len() == 1 {
                METHODS_WITH_NEGATION.iter().for_each(|&(method1, method2)| {
                    for &(method, negation_method) in &[(method1, method2), (method2, method1)] {
                        if method == path.name.as_str() {
                            span_lint_and_then(
                                self.cx,
                                NONMINIMAL_BOOL,
                                e.span,
                                "this boolean expression can be simplified",
                                |db| {
                                    db.span_suggestion(
                                        e.span,
                                        "try",
                                        negation_method.to_owned()
                                    );
                                }
                            )
                        }
                    }
                })
            }
        }
    }
}

impl<'a, 'tcx> Visitor<'tcx> for NonminimalBoolVisitor<'a, 'tcx> {
    fn visit_expr(&mut self, e: &'tcx Expr) {
        if in_macro(e.span) {
            return;
        }
        match e.node {
            ExprBinary(binop, _, _) if binop.node == BiOr || binop.node == BiAnd => self.bool_expr(e),
            ExprUnary(UnNot, ref inner) => if self.cx.tables.node_types()[inner.hir_id].is_bool() {
                self.handle_method_call_in_not(e, inner);
                self.bool_expr(e);
            } else {
                walk_expr(self, e);
            },
            _ => walk_expr(self, e),
        }
    }
    fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
        NestedVisitorMap::None
    }
}
<|endoftext|>