<commit_before>use std::{
    collections::HashSet,
    fmt,
    error::Error as StdError,
};
use crate::client::Context;
use crate::model::{
    channel::Message,
    permissions::Permissions,
    id::UserId,
};
use crate::utils::Colour;
use super::Args;
use futures::future::BoxFuture;

mod check;
pub mod buckets;

pub use self::check::*;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OnlyIn {
    Dm,
    Guild,
    None,
    #[doc(hidden)]
    __Nonexhaustive,
}

impl Default for OnlyIn {
    fn default() -> Self { Self::None }
}

#[derive(Debug, Default, PartialEq)]
pub struct CommandOptions {
    /// A set of checks to be called prior to executing the command. The checks
    /// will short-circuit on the first check that returns `false`.
    pub checks: &'static [&'static Check],
    /// Ratelimit bucket.
    pub bucket: Option<&'static str>,
    /// Names that the command can be referred to.
    pub names: &'static [&'static str],
    /// Command description, used by other commands.
    pub desc: Option<&'static str>,
    /// Delimiters used to split the arguments of the command by.
    /// If empty, the [global delimiters](struct.Configuration.html#method.delimiters) are used.
    pub delimiters: &'static [&'static str],
    /// Command usage schema, used by other commands.
    pub usage: Option<&'static str>,
    /// Example arguments, used by other commands.
    pub examples: &'static [&'static str],
    /// Minimum amount of arguments that should be passed.
    pub min_args: Option<u16>,
    /// Maximum amount of arguments that can be passed.
    pub max_args: Option<u16>,
    /// Roles allowed to use this command.
    pub allowed_roles: &'static [&'static str],
    /// Permissions required to use this command.
    pub required_permissions: Permissions,
    /// Whether the command should be displayed in help list or not, used by other commands.
    pub help_available: bool,
    /// Whether the command can only be used in dms or guilds; or both.
    pub only_in: OnlyIn,
    /// Whether the command can only be used by owners or not.
    pub owners_only: bool,
    /// Whether the command treats owners as normal users.
    pub owner_privilege: bool,
    /// Other commands belonging to this command.
    pub sub_commands: &'static [&'static Command],
}

pub type CommandError = Box<dyn StdError + Send + Sync>;
pub type CommandResult = std::result::Result<(), CommandError>;
pub type CommandFn = for<'fut> fn(&'fut Context, &'fut Message, Args) -> BoxFuture<'fut, CommandResult>;

pub struct Command {
    pub fun: CommandFn,
    pub options: &'static CommandOptions,
}

impl fmt::Debug for Command {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Command")
            .field("options", &self.options)
            .finish()
    }
}

impl PartialEq for Command {
    #[inline]
    fn eq(&self, other: &Command) -> bool {
        (self.fun as usize == other.fun as usize) && (self.options == other.options)
    }
}

pub type HelpCommandFn = for<'fut> fn(
    &'fut Context,
    &'fut Message,
    Args,
    &'fut HelpOptions,
    &'fut [&'static CommandGroup],
    HashSet<UserId>,
) -> BoxFuture<'fut, CommandResult>;

pub struct HelpCommand {
    pub fun: HelpCommandFn,
    pub options: &'static HelpOptions,
}

impl fmt::Debug for HelpCommand {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("HelpCommand")
            .field("fun", &"<function>")
            .field("options", &self.options)
            .finish()
    }
}

impl PartialEq for HelpCommand {
    #[inline]
    fn eq(&self, other: &HelpCommand) -> bool {
        (self.fun as usize == other.fun as usize) && (self.options == other.options)
    }
}

/// Describes the behaviour the help-command shall execute once it encounters
/// a command which the user or command fails to meet following criteria :
/// Lacking required permissions to execute the command.
/// Lacking required roles to execute the command.
/// The command can't be used in the current channel (as in `DM only` or `guild only`).
#[derive(Copy, Clone, Debug, PartialOrd, Ord, Eq, PartialEq)]
pub enum HelpBehaviour {
    /// The command will be displayed, hence nothing will be done.
    Nothing,
    /// Strikes a command by applying `~~{command_name}~~`.
    Strike,
    /// Does not list a command in the help-menu.
    Hide,
    #[doc(hidden)]
    __Nonexhaustive,
}

#[derive(Clone, Debug, PartialEq)]
pub struct HelpOptions {
    /// Which names should the help command use for dispatching.
    /// Defaults to `["help"]`
    pub names: &'static [&'static str],
    /// Suggests a command's name.
    pub suggestion_text: &'static str,
    /// If no help is available, this text will be displayed.
    pub no_help_available_text: &'static str,
    /// How to use a command, `{usage_label}: {command_name} {args}`
    pub usage_label: &'static str,
    /// Actual sample label, `{usage_sample_label}: {command_name} {args}`
    pub usage_sample_label: &'static str,
    /// Text labeling ungrouped commands, `{ungrouped_label}: ...`
    pub ungrouped_label: &'static str,
    /// Text labeling the start of the description.
    pub description_label: &'static str,
    /// Text labeling grouped commands, `{grouped_label} {group_name}: ...`
    pub grouped_label: &'static str,
    /// Text labeling a command's alternative names (aliases).
    pub aliases_label: &'static str,
    /// Text specifying that a command is only usable in a guild.
    pub guild_only_text: &'static str,
    /// Text labelling a command's names of checks.
    pub checks_label: &'static str,
    /// Text labelling a command's subcommands
    pub sub_commands_label: &'static str,
    /// Text specifying that a command is only usable in via DM.
    pub dm_only_text: &'static str,
    /// Text specifying that a command can be used via DM and in guilds.
    pub dm_and_guild_text: &'static str,
    /// Text expressing that a command is available.
    pub available_text: &'static str,
    /// Error-message once a command could not be found.
    /// Output-example (without whitespace between both substitutions: `{command_not_found_text}{command_name}`
    /// `{command_name}` describes user's input as in: `{prefix}help {command_name}`.
    pub command_not_found_text: &'static str,
    /// Explains the user on how to use access a single command's details.
    pub individual_command_tip: &'static str,
    /// Explains reasoning behind strikethrough-commands, see fields requiring `HelpBehaviour` for further information.
    /// If `HelpBehaviour::Strike` is unused, this field will evaluate to `None` during creation
    /// inside of the help macro.
    ///
    /// **Note**: Text is only used in direct messages.
    pub strikethrough_commands_tip_in_dm: Option<&'static str>,
    /// Explains reasoning behind strikethrough-commands, see fields requiring `HelpBehaviour` for further information.
    /// If `HelpBehaviour::Strike` is unused, this field will evaluate to `None` during creation
    /// inside of the help macro.
    ///
    /// **Note**: Text is only used in guilds.
    pub strikethrough_commands_tip_in_guild: Option<&'static str>,
    /// Announcing a group's prefix as in: {group_prefix} {prefix}.
    pub group_prefix: &'static str,
    /// If a user lacks required roles, this will treat how these commands will be displayed.
    pub lacking_role: HelpBehaviour,
    /// If a user lacks permissions, this will treat how these commands will be displayed.
    pub lacking_permissions: HelpBehaviour,
    /// If a user lacks ownership, this will treat how these commands will be displayed.
    pub lacking_ownership: HelpBehaviour,
    /// If conditions (of a check) may be lacking by the user, this will treat how these commands will be displayed.
    pub lacking_conditions: HelpBehaviour,
    /// If a user is using the help-command in a channel where a command is not available,
    /// this behaviour will be executed.
    pub wrong_channel: HelpBehaviour,
    /// Colour help-embed will use upon encountering an error.
    pub embed_error_colour: Colour,
    /// Colour help-embed will use if no error occurred.
    pub embed_success_colour: Colour,
    /// If not 0, help will check whether a command is similar to searched named.
    pub max_levenshtein_distance: usize,
    /// Help will use this as prefix to express how deeply nested a command or
    /// group is.
    pub indention_prefix: &'static str,
}

#[derive(Debug, Default, PartialEq)]
pub struct GroupOptions {
    pub prefixes: &'static [&'static str],
    pub only_in: OnlyIn,
    pub owners_only: bool,
    pub owner_privilege: bool,
    pub help_available: bool,
    pub allowed_roles: &'static [&'static str],
    pub required_permissions: Permissions,
    pub checks: &'static [&'static Check],
    pub default_command: Option<&'static Command>,
    pub description: Option<&'static str>,
    pub commands: &'static [&'static Command],
    pub sub_groups: &'static [&'static CommandGroup],
}

#[derive(Debug, PartialEq)]
pub struct CommandGroup {
    pub name: &'static str,
    pub options: &'static GroupOptions,
}

#[cfg(test)]
#[cfg(all(feature = "cache", feature = "http"))]
mod levenshtein_tests {
    use super::HelpBehaviour;

    #[test]
    fn help_behaviour_eq() {
        assert_eq!(HelpBehaviour::Hide, std::cmp::max(HelpBehaviour::Hide, HelpBehaviour::Hide));
        assert_eq!(HelpBehaviour::Strike, std::cmp::max(HelpBehaviour::Strike, HelpBehaviour::Strike));
        assert_eq!(HelpBehaviour::Nothing, std::cmp::max(HelpBehaviour::Nothing, HelpBehaviour::Nothing));
    }

    #[test]
    fn help_behaviour_hide() {
        assert_eq!(HelpBehaviour::Hide, std::cmp::max(HelpBehaviour::Hide, HelpBehaviour::Nothing));
        assert_eq!(HelpBehaviour::Hide, std::cmp::max(HelpBehaviour::Hide, HelpBehaviour::Strike));
    }

    #[test]
    fn help_behaviour_strike() {
        assert_eq!(HelpBehaviour::Strike, std::cmp::max(HelpBehaviour::Strike, HelpBehaviour::Nothing));
        assert_eq!(HelpBehaviour::Hide, std::cmp::max(HelpBehaviour::Strike, HelpBehaviour::Hide));
    }

    #[test]
    fn help_behaviour_nothing() {
        assert_eq!(HelpBehaviour::Strike, std::cmp::max(HelpBehaviour::Nothing, HelpBehaviour::Strike));
        assert_eq!(HelpBehaviour::Hide, std::cmp::max(HelpBehaviour::Nothing, HelpBehaviour::Hide));
    }
}
<commit_msg>Abstract CommandResult type (#934)<commit_after>use std::{
    collections::HashSet,
    fmt,
    error::Error as StdError,
};
use crate::client::Context;
use crate::model::{
    channel::Message,
    permissions::Permissions,
    id::UserId,
};
use crate::utils::Colour;
use super::Args;
use futures::future::BoxFuture;

mod check;
pub mod buckets;

pub use self::check::*;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OnlyIn {
    Dm,
    Guild,
    None,
    #[doc(hidden)]
    __Nonexhaustive,
}

impl Default for OnlyIn {
    fn default() -> Self { Self::None }
}

#[derive(Debug, Default, PartialEq)]
pub struct CommandOptions {
    /// A set of checks to be called prior to executing the command. The checks
    /// will short-circuit on the first check that returns `false`.
    pub checks: &'static [&'static Check],
    /// Ratelimit bucket.
    pub bucket: Option<&'static str>,
    /// Names that the command can be referred to.
    pub names: &'static [&'static str],
    /// Command description, used by other commands.
    pub desc: Option<&'static str>,
    /// Delimiters used to split the arguments of the command by.
    /// If empty, the [global delimiters](struct.Configuration.html#method.delimiters) are used.
    pub delimiters: &'static [&'static str],
    /// Command usage schema, used by other commands.
    pub usage: Option<&'static str>,
    /// Example arguments, used by other commands.
    pub examples: &'static [&'static str],
    /// Minimum amount of arguments that should be passed.
    pub min_args: Option<u16>,
    /// Maximum amount of arguments that can be passed.
    pub max_args: Option<u16>,
    /// Roles allowed to use this command.
    pub allowed_roles: &'static [&'static str],
    /// Permissions required to use this command.
    pub required_permissions: Permissions,
    /// Whether the command should be displayed in help list or not, used by other commands.
    pub help_available: bool,
    /// Whether the command can only be used in dms or guilds; or both.
    pub only_in: OnlyIn,
    /// Whether the command can only be used by owners or not.
    pub owners_only: bool,
    /// Whether the command treats owners as normal users.
    pub owner_privilege: bool,
    /// Other commands belonging to this command.
    pub sub_commands: &'static [&'static Command],
}

pub type CommandError = Box<dyn StdError + Send + Sync>;
pub type CommandResult<T = ()> = std::result::Result<T, CommandError>;
pub type CommandFn = for<'fut> fn(&'fut Context, &'fut Message, Args) -> BoxFuture<'fut, CommandResult>;

pub struct Command {
    pub fun: CommandFn,
    pub options: &'static CommandOptions,
}

impl fmt::Debug for Command {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Command")
            .field("options", &self.options)
            .finish()
    }
}

impl PartialEq for Command {
    #[inline]
    fn eq(&self, other: &Command) -> bool {
        (self.fun as usize == other.fun as usize) && (self.options == other.options)
    }
}

pub type HelpCommandFn = for<'fut> fn(
    &'fut Context,
    &'fut Message,
    Args,
    &'fut HelpOptions,
    &'fut [&'static CommandGroup],
    HashSet<UserId>,
) -> BoxFuture<'fut, CommandResult>;

pub struct HelpCommand {
    pub fun: HelpCommandFn,
    pub options: &'static HelpOptions,
}

impl fmt::Debug for HelpCommand {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("HelpCommand")
            .field("fun", &"<function>")
            .field("options", &self.options)
            .finish()
    }
}

impl PartialEq for HelpCommand {
    #[inline]
    fn eq(&self, other: &HelpCommand) -> bool {
        (self.fun as usize == other.fun as usize) && (self.options == other.options)
    }
}

/// Describes the behaviour the help-command shall execute once it encounters
/// a command which the user or command fails to meet following criteria :
/// Lacking required permissions to execute the command.
/// Lacking required roles to execute the command.
/// The command can't be used in the current channel (as in `DM only` or `guild only`).
#[derive(Copy, Clone, Debug, PartialOrd, Ord, Eq, PartialEq)]
pub enum HelpBehaviour {
    /// The command will be displayed, hence nothing will be done.
    Nothing,
    /// Strikes a command by applying `~~{command_name}~~`.
    Strike,
    /// Does not list a command in the help-menu.
    Hide,
    #[doc(hidden)]
    __Nonexhaustive,
}

#[derive(Clone, Debug, PartialEq)]
pub struct HelpOptions {
    /// Which names should the help command use for dispatching.
    /// Defaults to `["help"]`
    pub names: &'static [&'static str],
    /// Suggests a command's name.
    pub suggestion_text: &'static str,
    /// If no help is available, this text will be displayed.
    pub no_help_available_text: &'static str,
    /// How to use a command, `{usage_label}: {command_name} {args}`
    pub usage_label: &'static str,
    /// Actual sample label, `{usage_sample_label}: {command_name} {args}`
    pub usage_sample_label: &'static str,
    /// Text labeling ungrouped commands, `{ungrouped_label}: ...`
    pub ungrouped_label: &'static str,
    /// Text labeling the start of the description.
    pub description_label: &'static str,
    /// Text labeling grouped commands, `{grouped_label} {group_name}: ...`
    pub grouped_label: &'static str,
    /// Text labeling a command's alternative names (aliases).
    pub aliases_label: &'static str,
    /// Text specifying that a command is only usable in a guild.
    pub guild_only_text: &'static str,
    /// Text labelling a command's names of checks.
    pub checks_label: &'static str,
    /// Text labelling a command's subcommands
    pub sub_commands_label: &'static str,
    /// Text specifying that a command is only usable in via DM.
    pub dm_only_text: &'static str,
    /// Text specifying that a command can be used via DM and in guilds.
    pub dm_and_guild_text: &'static str,
    /// Text expressing that a command is available.
    pub available_text: &'static str,
    /// Error-message once a command could not be found.
    /// Output-example (without whitespace between both substitutions: `{command_not_found_text}{command_name}`
    /// `{command_name}` describes user's input as in: `{prefix}help {command_name}`.
    pub command_not_found_text: &'static str,
    /// Explains the user on how to use access a single command's details.
    pub individual_command_tip: &'static str,
    /// Explains reasoning behind strikethrough-commands, see fields requiring `HelpBehaviour` for further information.
    /// If `HelpBehaviour::Strike` is unused, this field will evaluate to `None` during creation
    /// inside of the help macro.
    ///
    /// **Note**: Text is only used in direct messages.
    pub strikethrough_commands_tip_in_dm: Option<&'static str>,
    /// Explains reasoning behind strikethrough-commands, see fields requiring `HelpBehaviour` for further information.
    /// If `HelpBehaviour::Strike` is unused, this field will evaluate to `None` during creation
    /// inside of the help macro.
    ///
    /// **Note**: Text is only used in guilds.
    pub strikethrough_commands_tip_in_guild: Option<&'static str>,
    /// Announcing a group's prefix as in: {group_prefix} {prefix}.
    pub group_prefix: &'static str,
    /// If a user lacks required roles, this will treat how these commands will be displayed.
    pub lacking_role: HelpBehaviour,
    /// If a user lacks permissions, this will treat how these commands will be displayed.
    pub lacking_permissions: HelpBehaviour,
    /// If a user lacks ownership, this will treat how these commands will be displayed.
    pub lacking_ownership: HelpBehaviour,
    /// If conditions (of a check) may be lacking by the user, this will treat how these commands will be displayed.
    pub lacking_conditions: HelpBehaviour,
    /// If a user is using the help-command in a channel where a command is not available,
    /// this behaviour will be executed.
    pub wrong_channel: HelpBehaviour,
    /// Colour help-embed will use upon encountering an error.
    pub embed_error_colour: Colour,
    /// Colour help-embed will use if no error occurred.
    pub embed_success_colour: Colour,
    /// If not 0, help will check whether a command is similar to searched named.
    pub max_levenshtein_distance: usize,
    /// Help will use this as prefix to express how deeply nested a command or
    /// group is.
    pub indention_prefix: &'static str,
}

#[derive(Debug, Default, PartialEq)]
pub struct GroupOptions {
    pub prefixes: &'static [&'static str],
    pub only_in: OnlyIn,
    pub owners_only: bool,
    pub owner_privilege: bool,
    pub help_available: bool,
    pub allowed_roles: &'static [&'static str],
    pub required_permissions: Permissions,
    pub checks: &'static [&'static Check],
    pub default_command: Option<&'static Command>,
    pub description: Option<&'static str>,
    pub commands: &'static [&'static Command],
    pub sub_groups: &'static [&'static CommandGroup],
}

#[derive(Debug, PartialEq)]
pub struct CommandGroup {
    pub name: &'static str,
    pub options: &'static GroupOptions,
}

#[cfg(test)]
#[cfg(all(feature = "cache", feature = "http"))]
mod levenshtein_tests {
    use super::HelpBehaviour;

    #[test]
    fn help_behaviour_eq() {
        assert_eq!(HelpBehaviour::Hide, std::cmp::max(HelpBehaviour::Hide, HelpBehaviour::Hide));
        assert_eq!(HelpBehaviour::Strike, std::cmp::max(HelpBehaviour::Strike, HelpBehaviour::Strike));
        assert_eq!(HelpBehaviour::Nothing, std::cmp::max(HelpBehaviour::Nothing, HelpBehaviour::Nothing));
    }

    #[test]
    fn help_behaviour_hide() {
        assert_eq!(HelpBehaviour::Hide, std::cmp::max(HelpBehaviour::Hide, HelpBehaviour::Nothing));
        assert_eq!(HelpBehaviour::Hide, std::cmp::max(HelpBehaviour::Hide, HelpBehaviour::Strike));
    }

    #[test]
    fn help_behaviour_strike() {
        assert_eq!(HelpBehaviour::Strike, std::cmp::max(HelpBehaviour::Strike, HelpBehaviour::Nothing));
        assert_eq!(HelpBehaviour::Hide, std::cmp::max(HelpBehaviour::Strike, HelpBehaviour::Hide));
    }

    #[test]
    fn help_behaviour_nothing() {
        assert_eq!(HelpBehaviour::Strike, std::cmp::max(HelpBehaviour::Nothing, HelpBehaviour::Strike));
        assert_eq!(HelpBehaviour::Hide, std::cmp::max(HelpBehaviour::Nothing, HelpBehaviour::Hide));
    }
}
<|endoftext|>


<commit_before>use nom::{alphanumeric, digit, multispace};
use std::collections::{HashSet, VecDeque};
use std::str;

use column::Column;
use common::{binary_comparison_operator, binary_logical_operator, column_identifier,
             unary_negation_operator, Operator};

#[derive(Clone, Debug, Hash, PartialEq)]
pub enum ConditionBase {
    Field(Column),
    Literal(String),
    Placeholder,
}

#[derive(Clone, Debug, Hash, PartialEq)]
pub struct ConditionTree {
    pub operator: Operator,
    pub left: Option<Box<ConditionExpression>>,
    pub right: Option<Box<ConditionExpression>>,
}

impl<'a> ConditionTree {
    pub fn contained_columns(&'a self) -> HashSet<&'a Column> {
        let mut s = HashSet::new();
        let mut q = VecDeque::<&'a ConditionTree>::new();
        q.push_back(self);
        while let Some(ref ct) = q.pop_front() {
            match **ct.left.as_ref().unwrap() {
                ConditionExpression::Base(ConditionBase::Field(ref c)) => {
                    s.insert(c);
                }
                ConditionExpression::LogicalOp(ref ct) |
                ConditionExpression::ComparisonOp(ref ct) => q.push_back(ct),
                _ => (),
            }
            match **ct.right.as_ref().unwrap() {
                ConditionExpression::Base(ConditionBase::Field(ref c)) => {
                    s.insert(c);
                }
                ConditionExpression::LogicalOp(ref ct) |
                ConditionExpression::ComparisonOp(ref ct) => q.push_back(ct),
                _ => (),
            }
        }
        s
    }
}

#[derive(Clone, Debug, Hash, PartialEq)]
pub enum ConditionExpression {
    ComparisonOp(ConditionTree),
    LogicalOp(ConditionTree),
    Base(ConditionBase),
}

fn fold_cond_exprs(initial: ConditionExpression,
                   remainder: Vec<(Operator, ConditionExpression)>)
                   -> ConditionExpression {
    remainder.into_iter().fold(initial, |acc, pair| {
        let (oper, expr) = pair;
        match oper {
            Operator::Equal | Operator::Less | Operator::Greater | Operator::LessOrEqual |
            Operator::Like | Operator::GreaterOrEqual | Operator::NotEqual => {
                ConditionExpression::ComparisonOp(ConditionTree {
                    operator: oper.clone(),
                    left: Some(Box::new(acc)),
                    right: Some(Box::new(expr)),
                })
            }
            Operator::And | Operator::Or => {
                ConditionExpression::LogicalOp(ConditionTree {
                    operator: oper.clone(),
                    left: Some(Box::new(acc)),
                    right: Some(Box::new(expr)),
                })
            }
            o => {
                println!("unsupported op {:?}", o);
                unimplemented!()
            }
        }
    })
}

/// Parse a conditional expression into a condition tree structure
named!(pub condition_expr<&[u8], ConditionExpression>,
    chain!(
        neg_op: opt!(unary_negation_operator) ~
        initial: boolean_primary ~
        remainder: many0!(
            complete!(
                chain!(
                    log_op: binary_logical_operator ~
                    right_expr: condition_expr,
                    || {
                        (log_op, right_expr)
                    }
                )
            )
        ),
        || {
            if let Some(Operator::Not) = neg_op {
                ConditionExpression::LogicalOp(
                    ConditionTree {
                        operator: Operator::Not,
                        left: Some(Box::new(fold_cond_exprs(initial, remainder))),
                        right: None,
                    }
                )
            } else {
                fold_cond_exprs(initial, remainder)
            }
        }
    )
);

named!(boolean_primary<&[u8], ConditionExpression>,
    chain!(
        initial: predicate ~
        remainder:
        many0!(
            complete!(
                chain!(
                    op: delimited!(opt!(multispace),
                                   binary_comparison_operator,
                                   opt!(multispace)) ~
                    right_expr: boolean_primary,
                    || {
                        (op, right_expr)
                    }
                )
            )
        ),
        || { fold_cond_exprs(initial, remainder) }
    )
);

named!(predicate<&[u8], ConditionExpression>,
    alt_complete!(
            chain!(
                delimited!(opt!(multispace), tag!("?"), opt!(multispace)),
                || {
                    ConditionExpression::Base(
                        ConditionBase::Placeholder
                    )
                }
            )
        |   chain!(
                field: delimited!(opt!(multispace), digit, opt!(multispace)),
                || {
                    ConditionExpression::Base(
                        ConditionBase::Literal(String::from(str::from_utf8(field).unwrap()))
                    )
                }
            )
        |   chain!(
                field: delimited!(opt!(multispace),
                                  alt_complete!(
                                        delimited!(tag!("\""), opt!(alphanumeric), tag!("\""))
                                      | delimited!(tag!("'"), opt!(alphanumeric), tag!("'"))
                                  ),
                                  opt!(multispace)),
                || {
                    let field = field.unwrap_or("".as_bytes());
                    ConditionExpression::Base(
                        ConditionBase::Literal(String::from(str::from_utf8(field).unwrap()))
                    )
                }
            )
        |   chain!(
                field: delimited!(opt!(multispace), column_identifier, opt!(multispace)),
                || {
                    ConditionExpression::Base(
                        ConditionBase::Field(field)
                    )
                }
            )
    )
);

#[cfg(test)]
mod tests {
    use super::*;
    use column::Column;
    use common::Operator;

    fn flat_condition_tree(op: Operator,
                           l: ConditionBase,
                           r: ConditionBase)
                           -> ConditionExpression {
        ConditionExpression::ComparisonOp(ConditionTree {
            operator: op,
            left: Some(Box::new(ConditionExpression::Base(l))),
            right: Some(Box::new(ConditionExpression::Base(r))),
        })
    }

    #[test]
    fn ct_contained_columns() {
        use std::collections::HashSet;

        let cond = "a.foo = ? and b.bar = 42";

        let res = condition_expr(cond.as_bytes());
        let c1 = Column::from("a.foo");
        let c2 = Column::from("b.bar");
        let mut expected_cols = HashSet::new();
        expected_cols.insert(&c1);
        expected_cols.insert(&c2);
        match res.unwrap().1 {
            ConditionExpression::LogicalOp(ct) => {
                assert_eq!(ct.contained_columns(), expected_cols);
            }
            _ => panic!(),
        }
    }

    #[test]
    fn equality_placeholder() {
        let cond = "foo = ?";

        let res = condition_expr(cond.as_bytes());
        assert_eq!(res.unwrap().1,
                   flat_condition_tree(Operator::Equal,
                                       ConditionBase::Field(Column::from("foo")),
                                       ConditionBase::Placeholder));
    }

    #[test]
    fn equality_literals() {
        let cond1 = "foo = 42";
        let cond2 = "foo = \"hello\"";

        let res1 = condition_expr(cond1.as_bytes());
        assert_eq!(res1.unwrap().1,
                   flat_condition_tree(Operator::Equal,
                                       ConditionBase::Field(Column::from("foo")),
                                       ConditionBase::Literal(String::from("42"))));

        let res2 = condition_expr(cond2.as_bytes());
        assert_eq!(res2.unwrap().1,
                   flat_condition_tree(Operator::Equal,
                                       ConditionBase::Field(Column::from("foo")),
                                       ConditionBase::Literal(String::from("hello"))));
    }

    #[test]
    fn inequality_literals() {
        let cond1 = "foo >= 42";
        let cond2 = "foo <= 5";

        let res1 = condition_expr(cond1.as_bytes());
        assert_eq!(res1.unwrap().1,
                   flat_condition_tree(Operator::GreaterOrEqual,
                                       ConditionBase::Field(Column::from("foo")),
                                       ConditionBase::Literal(String::from("42"))));

        let res2 = condition_expr(cond2.as_bytes());
        assert_eq!(res2.unwrap().1,
                   flat_condition_tree(Operator::LessOrEqual,
                                       ConditionBase::Field(Column::from("foo")),
                                       ConditionBase::Literal(String::from("5"))));
    }

    #[test]
    fn empty_string_literal() {
        let cond = "foo = ''";

        let res = condition_expr(cond.as_bytes());
        assert_eq!(res.unwrap().1,
                   flat_condition_tree(Operator::Equal,
                                       ConditionBase::Field(Column::from("foo")),
                                       ConditionBase::Literal(String::from(""))));
    }
}
<commit_msg>Support parentheses in condition trees<commit_after>use nom::{alphanumeric, digit, multispace};
use std::collections::{HashSet, VecDeque};
use std::str;

use column::Column;
use common::{binary_comparison_operator, binary_logical_operator, column_identifier,
             unary_negation_operator, Operator};

#[derive(Clone, Debug, Hash, PartialEq)]
pub enum ConditionBase {
    Field(Column),
    Literal(String),
    Placeholder,
}

#[derive(Clone, Debug, Hash, PartialEq)]
pub struct ConditionTree {
    pub operator: Operator,
    pub left: Option<Box<ConditionExpression>>,
    pub right: Option<Box<ConditionExpression>>,
}

impl<'a> ConditionTree {
    pub fn contained_columns(&'a self) -> HashSet<&'a Column> {
        let mut s = HashSet::new();
        let mut q = VecDeque::<&'a ConditionTree>::new();
        q.push_back(self);
        while let Some(ref ct) = q.pop_front() {
            match **ct.left.as_ref().unwrap() {
                ConditionExpression::Base(ConditionBase::Field(ref c)) => {
                    s.insert(c);
                }
                ConditionExpression::LogicalOp(ref ct) |
                ConditionExpression::ComparisonOp(ref ct) => q.push_back(ct),
                _ => (),
            }
            match **ct.right.as_ref().unwrap() {
                ConditionExpression::Base(ConditionBase::Field(ref c)) => {
                    s.insert(c);
                }
                ConditionExpression::LogicalOp(ref ct) |
                ConditionExpression::ComparisonOp(ref ct) => q.push_back(ct),
                _ => (),
            }
        }
        s
    }
}

#[derive(Clone, Debug, Hash, PartialEq)]
pub enum ConditionExpression {
    ComparisonOp(ConditionTree),
    LogicalOp(ConditionTree),
    Base(ConditionBase),
}

fn fold_cond_exprs(initial: ConditionExpression,
                   remainder: Vec<(Operator, ConditionExpression)>)
                   -> ConditionExpression {
    remainder.into_iter().fold(initial, |acc, pair| {
        let (oper, expr) = pair;
        match oper {
            Operator::Equal | Operator::Less | Operator::Greater | Operator::LessOrEqual |
            Operator::Like | Operator::GreaterOrEqual | Operator::NotEqual => {
                ConditionExpression::ComparisonOp(ConditionTree {
                    operator: oper.clone(),
                    left: Some(Box::new(acc)),
                    right: Some(Box::new(expr)),
                })
            }
            Operator::And | Operator::Or => {
                ConditionExpression::LogicalOp(ConditionTree {
                    operator: oper.clone(),
                    left: Some(Box::new(acc)),
                    right: Some(Box::new(expr)),
                })
            }
            o => {
                println!("unsupported op {:?}", o);
                unimplemented!()
            }
        }
    })
}

/// Parse a conditional expression into a condition tree structure
named!(pub condition_expr<&[u8], ConditionExpression>,
    chain!(
        neg_op: opt!(unary_negation_operator) ~
        initial: alt_complete!(boolean_primary | delimited!(tag!("("), condition_expr, tag!(")"))) ~
        remainder: many0!(
            complete!(
                chain!(
                    log_op: binary_logical_operator ~
                    right_expr: condition_expr,
                    || {
                        (log_op, right_expr)
                    }
                )
            )
        ),
        || {
            if let Some(Operator::Not) = neg_op {
                ConditionExpression::LogicalOp(
                    ConditionTree {
                        operator: Operator::Not,
                        left: Some(Box::new(fold_cond_exprs(initial, remainder))),
                        right: None,
                    }
                )
            } else {
                fold_cond_exprs(initial, remainder)
            }
        }
    )
);

named!(boolean_primary<&[u8], ConditionExpression>,
    chain!(
        initial: predicate ~
        remainder:
        many0!(
            complete!(
                chain!(
                    op: delimited!(opt!(multispace),
                                   binary_comparison_operator,
                                   opt!(multispace)) ~
                    right_expr: boolean_primary,
                    || {
                        (op, right_expr)
                    }
                )
            )
        ),
        || { fold_cond_exprs(initial, remainder) }
    )
);

named!(predicate<&[u8], ConditionExpression>,
    alt_complete!(
            chain!(
                delimited!(opt!(multispace), tag!("?"), opt!(multispace)),
                || {
                    ConditionExpression::Base(
                        ConditionBase::Placeholder
                    )
                }
            )
        |   chain!(
                field: delimited!(opt!(multispace), digit, opt!(multispace)),
                || {
                    ConditionExpression::Base(
                        ConditionBase::Literal(String::from(str::from_utf8(field).unwrap()))
                    )
                }
            )
        |   chain!(
                field: delimited!(opt!(multispace),
                                  alt_complete!(
                                        delimited!(tag!("\""), opt!(alphanumeric), tag!("\""))
                                      | delimited!(tag!("'"), opt!(alphanumeric), tag!("'"))
                                  ),
                                  opt!(multispace)),
                || {
                    let field = field.unwrap_or("".as_bytes());
                    ConditionExpression::Base(
                        ConditionBase::Literal(String::from(str::from_utf8(field).unwrap()))
                    )
                }
            )
        |   chain!(
                field: delimited!(opt!(multispace), column_identifier, opt!(multispace)),
                || {
                    ConditionExpression::Base(
                        ConditionBase::Field(field)
                    )
                }
            )
    )
);

#[cfg(test)]
mod tests {
    use super::*;
    use column::Column;
    use common::Operator;

    fn flat_condition_tree(op: Operator,
                           l: ConditionBase,
                           r: ConditionBase)
                           -> ConditionExpression {
        ConditionExpression::ComparisonOp(ConditionTree {
            operator: op,
            left: Some(Box::new(ConditionExpression::Base(l))),
            right: Some(Box::new(ConditionExpression::Base(r))),
        })
    }

    #[test]
    fn ct_contained_columns() {
        use std::collections::HashSet;

        let cond = "a.foo = ? and b.bar = 42";

        let res = condition_expr(cond.as_bytes());
        let c1 = Column::from("a.foo");
        let c2 = Column::from("b.bar");
        let mut expected_cols = HashSet::new();
        expected_cols.insert(&c1);
        expected_cols.insert(&c2);
        match res.unwrap().1 {
            ConditionExpression::LogicalOp(ct) => {
                assert_eq!(ct.contained_columns(), expected_cols);
            }
            _ => panic!(),
        }
    }

    #[test]
    fn equality_placeholder() {
        let cond = "foo = ?";

        let res = condition_expr(cond.as_bytes());
        assert_eq!(res.unwrap().1,
                   flat_condition_tree(Operator::Equal,
                                       ConditionBase::Field(Column::from("foo")),
                                       ConditionBase::Placeholder));
    }

    #[test]
    fn equality_literals() {
        let cond1 = "foo = 42";
        let cond2 = "foo = \"hello\"";

        let res1 = condition_expr(cond1.as_bytes());
        assert_eq!(res1.unwrap().1,
                   flat_condition_tree(Operator::Equal,
                                       ConditionBase::Field(Column::from("foo")),
                                       ConditionBase::Literal(String::from("42"))));

        let res2 = condition_expr(cond2.as_bytes());
        assert_eq!(res2.unwrap().1,
                   flat_condition_tree(Operator::Equal,
                                       ConditionBase::Field(Column::from("foo")),
                                       ConditionBase::Literal(String::from("hello"))));
    }

    #[test]
    fn inequality_literals() {
        let cond1 = "foo >= 42";
        let cond2 = "foo <= 5";

        let res1 = condition_expr(cond1.as_bytes());
        assert_eq!(res1.unwrap().1,
                   flat_condition_tree(Operator::GreaterOrEqual,
                                       ConditionBase::Field(Column::from("foo")),
                                       ConditionBase::Literal(String::from("42"))));

        let res2 = condition_expr(cond2.as_bytes());
        assert_eq!(res2.unwrap().1,
                   flat_condition_tree(Operator::LessOrEqual,
                                       ConditionBase::Field(Column::from("foo")),
                                       ConditionBase::Literal(String::from("5"))));
    }

    #[test]
    fn empty_string_literal() {
        let cond = "foo = ''";

        let res = condition_expr(cond.as_bytes());
        assert_eq!(res.unwrap().1,
                   flat_condition_tree(Operator::Equal,
                                       ConditionBase::Field(Column::from("foo")),
                                       ConditionBase::Literal(String::from(""))));
    }

    #[test]
    fn brackets() {
        let cond = "(foo = ? and bar = 12) or foobar = 'a'";

        use ConditionExpression::*;
        use ConditionBase::*;

        let a = ComparisonOp(ConditionTree {
            operator: Operator::Equal,
            left: Some(Box::new(Base(Field("foo".into())))),
            right: Some(Box::new(Base(Placeholder))),
        });

        let b = ComparisonOp(ConditionTree {
            operator: Operator::Equal,
            left: Some(Box::new(Base(Field("bar".into())))),
            right: Some(Box::new(Base(Literal("12".into())))),
        });

        let left = LogicalOp(ConditionTree {
            operator: Operator::And,
            left: Some(Box::new(a)),
            right: Some(Box::new(b)),
        });

        let right = ComparisonOp(ConditionTree {
            operator: Operator::Equal,
            left: Some(Box::new(Base(Field("foobar".into())))),
            right: Some(Box::new(Base(Literal("a".into())))),
        });

        let complete = LogicalOp(ConditionTree {
            operator: Operator::Or,
            left: Some(Box::new(left)),
            right: Some(Box::new(right)),
        });

        let res = condition_expr(cond.as_bytes());
        assert_eq!(res.unwrap().1, complete);
    }
}
<|endoftext|>


<commit_before>//! Calculate alignments with a generalized variant of the Smith Waterman algorithm.
//! Complexity: O(n * m) for strings of length m and n.
//!
//! # Example
//!
//! ```
//! use bio::alignment::pairwise::Aligner;
//! use bio::alignment::AlignmentOperation::{Match, Subst, Ins, Del};
//! let x = b"ACCGTGGAT";
//! let y = b"AAAAACCGTTGAT";
//! let score = |&: a: u8, b: u8| if a == b {1i32} else {-1i32};
//! let mut aligner = Aligner::with_capacity(x.len(), y.len(), -5, -1, score);
//! let alignment = aligner.semiglobal(x, y);
//! assert_eq!(alignment.i, 4);
//! assert_eq!(alignment.j, 0);
//! assert_eq!(alignment.operations, [Match, Match, Match, Match, Match, Subst, Match, Match, Match]);
//! ```

use std::i32;
use std::iter::repeat;
use std::cmp::max;

use alignment::{Alignment, AlignmentOperation};
use bitencoding::BitEnc;

#[derive(Copy)]
enum AlignmentType {
    Global,
    Semiglobal,
    Local
}

#[allow(non_snake_case)]
pub struct Aligner<F> where F: Fn(u8, u8) -> i32 {
    S: [Vec<i32>; 2],
    I: [Vec<i32>; 2],
    D: [Vec<i32>; 2],
    traceback: Traceback,
    gap_open: i32,
    gap_extend: i32,
    score: F
}

impl<F> Aligner<F> where F: Fn(u8, u8) -> i32 {
    /// Create new aligner instance. The size hints help to
    /// avoid unnecessary memory allocations.
    ///
    /// # Arguments
    ///
    /// * `m` - the expected size of x
    /// * `n` - the expected size of y
    /// * `gap_open` - the score for opening a gap (should be negative)
    /// * `gap_extend` - the score for extending a gap (should be negative)
    /// * `score` - function that returns the score for substitutions
    ///
    pub fn with_capacity(m: usize, n: usize, gap_open: i32, gap_extend: i32, score: F) -> Self {
        let get_vec = |&:| Vec::with_capacity(m + 1);
        Aligner {
            S: [get_vec(), get_vec()],
            I: [get_vec(), get_vec()],
            D: [get_vec(), get_vec()],
            traceback: Traceback::with_capacity(m, n),
            gap_open: gap_open,
            gap_extend: gap_extend,
            score: score
        }
    }

    fn init(&mut self, m: usize, alignment_type: AlignmentType) {
        // set minimum score to -inf, and allow to add gap_extend
        // without overflow
        let min_score = i32::MIN - self.gap_extend;
        for k in 0..2us {
            self.S[k].clear();
            self.I[k].clear();
            self.D[k].clear();
            self.I[k].extend(repeat(min_score).take(m + 1));
            self.D[k].extend(repeat(min_score).take(m + 1));
            match alignment_type {
                AlignmentType::Global => {
                    let ref mut s = self.S[k];
                    let mut score = self.gap_open;
                    for _ in 0..m+1 {
                        s.push(score);
                        score += self.gap_extend;
                    }
                },
                _ => self.S[k].extend(repeat(0).take(m + 1))
            }
        }
    }

    /// Calculate global alignment.
    pub fn global(&mut self, x: &[u8], y: &[u8]) -> Alignment {
        self.align(x, y, AlignmentType::Global)
    }

    /// Calculate semiglobal alignment.
    pub fn semiglobal(&mut self, x: &[u8], y: &[u8]) -> Alignment {
        self.align(x, y, AlignmentType::Semiglobal)
    }

    /// Calculate local alignment.
    pub fn local(&mut self, x: &[u8], y: &[u8]) -> Alignment {
        self.align(x, y, AlignmentType::Local)
    }

    fn align(&mut self, x: &[u8], y: &[u8], alignment_type: AlignmentType) -> Alignment {
        let (m, n) = (x.len(), y.len());

        self.init(m, alignment_type);
        self.traceback.init(m, n, alignment_type);

        let (mut best, mut best_i, mut best_j) = (0, 0, 0);
        let mut col = 0;

        for i in 1..n+1 {
            col = i % 2;
            let prev = 1 - col;

            match alignment_type {
                AlignmentType::Global => {
                    self.S[col][0] = self.gap_open + (i as i32 - 1) * self.gap_extend;
                    self.traceback.del(i, 0);
                },
                _ => {
                    // with local and semiglobal, allow to begin anywhere in y
                    self.S[col][0] = 0;
                }
            }

            let b = y[i - 1];
            let mut score = 0i32;
            for j in 1..m+1 {
                let a = x[j - 1];

                // score for deletion
                let d_score = max(
                    self.S[prev][j] + self.gap_open,
                    self.D[prev][j] + self.gap_extend
                );
                // score for insertion
                let i_score = max(
                    self.S[col][j-1] + self.gap_open,
                    self.I[col][j-1] + self.gap_extend
                );
                // score for substitution
                score = self.S[prev][j-1] + (self.score)(a, b);

                if d_score > score {
                    score = d_score;
                    self.traceback.del(i, j);
                }
                else if i_score > score {
                    score = i_score;
                    self.traceback.ins(i, j);
                }
                else {
                    self.traceback.subst(i, j);
                }

                match alignment_type {
                    AlignmentType::Local => {
                        if score < 0 {
                            self.traceback.start(i, j);
                            score = 0;
                        }
                        else if score > best {
                            best = score;
                            best_i = i;
                            best_j = j;
                        }
                    },
                    _ => ()
                }

                self.S[col][j] = score;
                self.D[col][j] = d_score;
                self.I[col][j] = i_score;
            }

            match alignment_type {
                AlignmentType::Semiglobal => {
                    if score > best {
                        best = score;
                        best_i = i;
                        best_j = m;
                    }
                },
                _ => ()
            }
        }
        match alignment_type {
            AlignmentType::Global => {
                let score = self.S[col][m];
                self.traceback.get_alignment(n, m, x, y, score)
            },
            _ => self.traceback.get_alignment(best_i, best_j, x, y, best)
        }

    }
}

struct Traceback {
    matrix: Vec<BitEnc>
}

static TBSTART: u8 = 0b00;
static TBSUBST: u8 = 0b01;
static TBINS: u8   = 0b10;
static TBDEL: u8   = 0b11;

impl Traceback {

    fn with_capacity(m: usize, n: usize) -> Self {
        let mut matrix = Vec::with_capacity(n+1);
        for _ in 0..n+1 {
            matrix.push(BitEnc::with_capacity(2, m + 1));
        }
        Traceback {
            matrix: matrix
        }
    }

    fn init(&mut self, m: usize, n: usize, alignment_type: AlignmentType) {
        match alignment_type {
            AlignmentType::Global => {
                // set the first cell to start, the rest to deletions
                for i in 0..n+1 {
                    self.matrix[i].clear();
                    self.matrix[i].push_values(m + 1, TBDEL);
                }
                self.matrix[0].set(0, TBSTART);
            },
            _ => {
                for i in 0..n+1 {
                    self.matrix[i].clear();
                    self.matrix[i].push_values(m + 1, TBSTART);
                }
            }
        }
    }

    fn start(&mut self, i: usize, j: usize) {
        self.matrix[i].set(j, TBSTART);
    }

    fn subst(&mut self, i: usize, j: usize) {
        self.matrix[i].set(j, TBSUBST);
    }

    fn del(&mut self, i: usize, j: usize) {
        self.matrix[i].set(j, TBDEL);
    }

    fn ins(&mut self, i: usize, j: usize) {
        self.matrix[i].set(j, TBINS);
    }

    fn is_subst(&self, i: usize, j: usize) -> bool {
        self.matrix[i].get(j).unwrap() == TBSUBST
    }

    fn is_del(&self, i: usize, j: usize) -> bool {
        self.matrix[i].get(j).unwrap() == TBDEL
    }

    fn is_ins(&self, i: usize, j: usize) -> bool {
        self.matrix[i].get(j).unwrap() == TBINS
    }

    fn get_alignment(&self, mut i: usize, mut j: usize, x: &[u8], y: &[u8], score: i32) -> Alignment {
        let mut ops = Vec::with_capacity(x.len());

        loop {
            let (ii, jj, op) = if self.is_subst(i, j) {
                let op = if y[i-1] == x[j-1] {
                    AlignmentOperation::Match
                }
                else {
                    AlignmentOperation::Subst
                };
                (i - 1, j - 1, op)
            }
            else if self.is_del(i, j) {
                (i - 1, j, AlignmentOperation::Del)
            }
            else if self.is_ins(i, j) {
                (i, j - 1, AlignmentOperation::Ins)
            } else {
                // reached alignment start
                break;
            };
            ops.push(op);
            i = ii;
            j = jj;
        }

        ops.reverse();
        Alignment { i: i, j: j, operations: ops, score: score}
    }
}

#[cfg(test)]
mod tests {
    use super::Aligner;
    use alignment::AlignmentOperation::{Match, Subst, Del};

    #[test]
    fn test_semiglobal() {
        let x = b"ACCGTGGAT";
        let y = b"AAAAACCGTTGAT";
        let score = |&: a: u8, b: u8| if a == b {1i32} else {-1i32};
        let mut aligner = Aligner::with_capacity(x.len(), y.len(), -5, -1, score);
        let alignment = aligner.semiglobal(x, y);
        assert_eq!(alignment.i, 4);
        assert_eq!(alignment.j, 0);
        assert_eq!(alignment.operations, [Match, Match, Match, Match, Match, Subst, Match, Match, Match]);
    }

    #[test]
    fn test_local() {
        let x = b"ACCGTGGAT";
        let y = b"AAAAACCGTTGAT";
        let score = |&: a: u8, b: u8| if a == b {1i32} else {-1i32};
        let mut aligner = Aligner::with_capacity(x.len(), y.len(), -5, -1, score);
        let alignment = aligner.local(x, y);
        assert_eq!(alignment.i, 4);
        assert_eq!(alignment.j, 0);
        assert_eq!(alignment.operations, [Match, Match, Match, Match, Match, Subst, Match, Match, Match]);
    }

    #[test]
    fn test_global() {
        let x = b"ACCGTGGAT";
        let y = b"AAAAACCGTTGAT";
        let score = |&: a: u8, b: u8| if a == b {1i32} else {-1i32};
        let mut aligner = Aligner::with_capacity(x.len(), y.len(), -5, -1, score);
        let alignment = aligner.global(x, y);
        assert_eq!(alignment.i, 0);
        assert_eq!(alignment.j, 0);
        assert_eq!(alignment.operations, [Del, Del, Del, Del, Match, Match, Match, Match, Match, Subst, Match, Match, Match]);
    }
}
<commit_msg>Experiments.<commit_after>//! Calculate alignments with a generalized variant of the Smith Waterman algorithm.
//! Complexity: O(n * m) for strings of length m and n.
//!
//! # Example
//!
//! ```
//! use bio::alignment::pairwise::Aligner;
//! use bio::alignment::AlignmentOperation::{Match, Subst, Ins, Del};
//! let x = b"ACCGTGGAT";
//! let y = b"AAAAACCGTTGAT";
//! let score = |&: a: u8, b: u8| if a == b {1i32} else {-1i32};
//! let mut aligner = Aligner::with_capacity(x.len(), y.len(), -5, -1, score);
//! let alignment = aligner.semiglobal(x, y);
//! assert_eq!(alignment.i, 4);
//! assert_eq!(alignment.j, 0);
//! assert_eq!(alignment.operations, [Match, Match, Match, Match, Match, Subst, Match, Match, Match]);
//! ```

use std::i32;
use std::iter::repeat;
use std::cmp::max;

use alignment::{Alignment, AlignmentOperation};
use bitencoding::BitEnc;

struct AlignmentState {
    m: usize,
    n: usize,
    best: i32,
}

macro_rules! align {
    (
        $obj:ident, $state:ident,
        $init:block, $inner:block, $outer:block, $ret:block
    ) => (
        {
            let (mut best, mut best_i, mut best_j) = (0, 0, 0);
            let mut col = 0;

            for i in 1..$n+1 {
                col = i % 2;
                let prev = 1 - col;

                // init code
                $init

                let b = y[i - 1];
                let mut score = 0i32;
                for j in 1..$m+1 {
                    let a = x[j - 1];

                    // score for deletion
                    let d_score = max(
                        $obj.S[prev][j] + $obj.gap_open,
                        $obj.D[prev][j] + $obj.gap_extend
                    );
                    // score for insertion
                    let i_score = max(
                        $obj.S[col][j-1] + $obj.gap_open,
                        $obj.I[col][j-1] + $obj.gap_extend
                    );
                    // score for substitution
                    score = $obj.S[prev][j-1] + ($obj.score)(a, b);

                    if d_score > score {
                        score = d_score;
                        $obj.traceback.del(i, j);
                    }
                    else if i_score > score {
                        score = i_score;
                        $obj.traceback.ins(i, j);
                    }
                    else {
                        $obj.traceback.subst(i, j);
                    }

                    // inner code
                    $inner

                    $obj.S[col][j] = score;
                    $obj.D[col][j] = d_score;
                    $obj.I[col][j] = i_score;
                }

                // outer code
                $outer
            }

            // return code
            $ret
        }
    );
}

#[allow(non_snake_case)]
pub struct Aligner<F> where F: Fn(u8, u8) -> i32 {
    S: [Vec<i32>; 2],
    I: [Vec<i32>; 2],
    D: [Vec<i32>; 2],
    traceback: Traceback,
    gap_open: i32,
    gap_extend: i32,
    score: F
}

impl<F> Aligner<F> where F: Fn(u8, u8) -> i32 {
    /// Create new aligner instance. The size hints help to
    /// avoid unnecessary memory allocations.
    ///
    /// # Arguments
    ///
    /// * `m` - the expected size of x
    /// * `n` - the expected size of y
    /// * `gap_open` - the score for opening a gap (should be negative)
    /// * `gap_extend` - the score for extending a gap (should be negative)
    /// * `score` - function that returns the score for substitutions
    ///
    pub fn with_capacity(m: usize, n: usize, gap_open: i32, gap_extend: i32, score: F) -> Self {
        let get_vec = |&:| Vec::with_capacity(m + 1);
        Aligner {
            S: [get_vec(), get_vec()],
            I: [get_vec(), get_vec()],
            D: [get_vec(), get_vec()],
            traceback: Traceback::with_capacity(m, n),
            gap_open: gap_open,
            gap_extend: gap_extend,
            score: score
        }
    }

    fn init(&mut self, m: usize, global: bool) {
        // set minimum score to -inf, and allow to add gap_extend
        // without overflow
        let min_score = i32::MIN - self.gap_extend;
        for k in 0..2us {
            self.S[k].clear();
            self.I[k].clear();
            self.D[k].clear();
            self.I[k].extend(repeat(min_score).take(m + 1));
            self.D[k].extend(repeat(min_score).take(m + 1));
            if global {
                let ref mut s = self.S[k];
                let mut score = self.gap_open;
                for _ in 0..m+1 {
                    s.push(score);
                    score += self.gap_extend;
                }
            }
            else {
                self.S[k].extend(repeat(0).take(m + 1))
            }
        }
    }

    /// Calculate global alignment.
    pub fn global(&mut self, x: &[u8], y: &[u8]) -> Alignment {
        let (m, n) = (x.len(), y.len());
        self.init(m, true);
        self.traceback.init(m, n, true);

        align!(
            self, m, n,
            {
                self.S[col][0] = self.gap_open + (i as i32 - 1) * self.gap_extend;
                self.traceback.del(i, 0);
            }, {}, {},
            {
                let score = self.S[col][m];
                self.traceback.get_alignment(n, m, x, y, score)
            }
        );
    }

    /// Calculate semiglobal alignment.
    pub fn semiglobal(&mut self, x: &[u8], y: &[u8]) -> Alignment {
        let (m, n) = (x.len(), y.len());
        self.init(m, false);
        self.traceback.init(m, n, false);

        align!(
            self, m, n,
            { self.S[col][0] = 0; },
            {},
            {
                if score > best {
                    best = score;
                    best_i = i;
                    best_j = m;
                }
            },
            { self.traceback.get_alignment(best_i, best_j, x, y, best) }
        );
    }

    /// Calculate local alignment.
    pub fn local(&mut self, x: &[u8], y: &[u8]) -> Alignment {
        let (m, n) = (x.len(), y.len());
        self.init(m, false);
        self.traceback.init(m, n, false);

        align!(
            self, m, n,
            { self.S[col][0] = 0; },
            {
                if score < 0 {
                    self.traceback.start(i, j);
                    score = 0;
                }
                else if score > best {
                    best = score;
                    best_i = i;
                    best_j = j;
                }
            },
            {},
            { self.traceback.get_alignment(best_i, best_j, x, y, best) }
        );
    }
}

struct Traceback {
    matrix: Vec<BitEnc>
}

static TBSTART: u8 = 0b00;
static TBSUBST: u8 = 0b01;
static TBINS: u8   = 0b10;
static TBDEL: u8   = 0b11;

impl Traceback {

    fn with_capacity(m: usize, n: usize) -> Self {
        let mut matrix = Vec::with_capacity(n+1);
        for _ in 0..n+1 {
            matrix.push(BitEnc::with_capacity(2, m + 1));
        }
        Traceback {
            matrix: matrix
        }
    }

    fn init(&mut self, m: usize, n: usize, global: bool) {
        if global {
            // set the first cell to start, the rest to deletions
            for i in 0..n+1 {
                self.matrix[i].clear();
                self.matrix[i].push_values(m + 1, TBDEL);
            }
            self.matrix[0].set(0, TBSTART);
        }
        else {
            for i in 0..n+1 {
                self.matrix[i].clear();
                self.matrix[i].push_values(m + 1, TBSTART);
            }
        }
    }

    fn start(&mut self, i: usize, j: usize) {
        self.matrix[i].set(j, TBSTART);
    }

    fn subst(&mut self, i: usize, j: usize) {
        self.matrix[i].set(j, TBSUBST);
    }

    fn del(&mut self, i: usize, j: usize) {
        self.matrix[i].set(j, TBDEL);
    }

    fn ins(&mut self, i: usize, j: usize) {
        self.matrix[i].set(j, TBINS);
    }

    fn is_subst(&self, i: usize, j: usize) -> bool {
        self.matrix[i].get(j).unwrap() == TBSUBST
    }

    fn is_del(&self, i: usize, j: usize) -> bool {
        self.matrix[i].get(j).unwrap() == TBDEL
    }

    fn is_ins(&self, i: usize, j: usize) -> bool {
        self.matrix[i].get(j).unwrap() == TBINS
    }

    fn get_alignment(&self, mut i: usize, mut j: usize, x: &[u8], y: &[u8], score: i32) -> Alignment {
        let mut ops = Vec::with_capacity(x.len());

        loop {
            let (ii, jj, op) = if self.is_subst(i, j) {
                let op = if y[i-1] == x[j-1] {
                    AlignmentOperation::Match
                }
                else {
                    AlignmentOperation::Subst
                };
                (i - 1, j - 1, op)
            }
            else if self.is_del(i, j) {
                (i - 1, j, AlignmentOperation::Del)
            }
            else if self.is_ins(i, j) {
                (i, j - 1, AlignmentOperation::Ins)
            } else {
                // reached alignment start
                break;
            };
            ops.push(op);
            i = ii;
            j = jj;
        }

        ops.reverse();
        Alignment { i: i, j: j, operations: ops, score: score}
    }
}

#[cfg(test)]
mod tests {
    use super::Aligner;
    use alignment::AlignmentOperation::{Match, Subst, Del};

    #[test]
    fn test_semiglobal() {
        let x = b"ACCGTGGAT";
        let y = b"AAAAACCGTTGAT";
        let score = |&: a: u8, b: u8| if a == b {1i32} else {-1i32};
        let mut aligner = Aligner::with_capacity(x.len(), y.len(), -5, -1, score);
        let alignment = aligner.semiglobal(x, y);
        assert_eq!(alignment.i, 4);
        assert_eq!(alignment.j, 0);
        assert_eq!(alignment.operations, [Match, Match, Match, Match, Match, Subst, Match, Match, Match]);
    }

    #[test]
    fn test_local() {
        let x = b"ACCGTGGAT";
        let y = b"AAAAACCGTTGAT";
        let score = |&: a: u8, b: u8| if a == b {1i32} else {-1i32};
        let mut aligner = Aligner::with_capacity(x.len(), y.len(), -5, -1, score);
        let alignment = aligner.local(x, y);
        assert_eq!(alignment.i, 4);
        assert_eq!(alignment.j, 0);
        assert_eq!(alignment.operations, [Match, Match, Match, Match, Match, Subst, Match, Match, Match]);
    }

    #[test]
    fn test_global() {
        let x = b"ACCGTGGAT";
        let y = b"AAAAACCGTTGAT";
        let score = |&: a: u8, b: u8| if a == b {1i32} else {-1i32};
        let mut aligner = Aligner::with_capacity(x.len(), y.len(), -5, -1, score);
        let alignment = aligner.global(x, y);
        assert_eq!(alignment.i, 0);
        assert_eq!(alignment.j, 0);
        assert_eq!(alignment.operations, [Del, Del, Del, Del, Match, Match, Match, Match, Match, Subst, Match, Match, Match]);
    }
}
<|endoftext|>


<commit_before>//! This optimization moves cold code to the end of the function.
//!
//! Some code is executed much less often than other code. For example panicking or the
//! landingpads for unwinding. By moving this cold code to the end of the function the average
//! amount of jumps is reduced and the code locality is improved.
//!
//! # Undefined behaviour
//!
//! This optimization doesn't assume anything that isn't already assumed by Cranelift itself.

use crate::prelude::*;

pub(super) fn optimize_function(ctx: &mut Context, cold_ebbs: &EntitySet<Ebb>) {
    // FIXME Move the ebb in place instead of remove and append once
    // bytecodealliance/cranelift#1339 is implemented.

    let mut ebb_insts = HashMap::new();
    for ebb in cold_ebbs.keys().filter(|&ebb| cold_ebbs.contains(ebb)) {
        let insts = ctx.func.layout.ebb_insts(ebb).collect::<Vec<_>>();
        for &inst in &insts {
            ctx.func.layout.remove_inst(inst);
        }
        ebb_insts.insert(ebb, insts);
        ctx.func.layout.remove_ebb(ebb);
    }

    // And then append them at the back again.
    for ebb in cold_ebbs.keys().filter(|&ebb| cold_ebbs.contains(ebb)) {
        ctx.func.layout.append_ebb(ebb);
        for inst in ebb_insts.remove(&ebb).unwrap() {
            ctx.func.layout.append_inst(inst, ebb);
        }
    }
}
<commit_msg>Disable the code_layout optimization<commit_after>//! This optimization moves cold code to the end of the function.
//!
//! Some code is executed much less often than other code. For example panicking or the
//! landingpads for unwinding. By moving this cold code to the end of the function the average
//! amount of jumps is reduced and the code locality is improved.
//!
//! # Undefined behaviour
//!
//! This optimization doesn't assume anything that isn't already assumed by Cranelift itself.

use crate::prelude::*;

pub(super) fn optimize_function(ctx: &mut Context, cold_ebbs: &EntitySet<Ebb>) {
    return; // FIXME add ebb arguments back

    // FIXME Move the ebb in place instead of remove and append once
    // bytecodealliance/cranelift#1339 is implemented.

    let mut ebb_insts = HashMap::new();
    for ebb in cold_ebbs.keys().filter(|&ebb| cold_ebbs.contains(ebb)) {
        let insts = ctx.func.layout.ebb_insts(ebb).collect::<Vec<_>>();
        for &inst in &insts {
            ctx.func.layout.remove_inst(inst);
        }
        ebb_insts.insert(ebb, insts);
        ctx.func.layout.remove_ebb(ebb);
    }

    // And then append them at the back again.
    for ebb in cold_ebbs.keys().filter(|&ebb| cold_ebbs.contains(ebb)) {
        ctx.func.layout.append_ebb(ebb);
        for inst in ebb_insts.remove(&ebb).unwrap() {
            ctx.func.layout.append_inst(inst, ebb);
        }
    }
}
<|endoftext|>


<commit_before>//! # Futex
//! Futex or Fast Userspace Mutex is "a method for waiting until a certain condition becomes true."
//!
//! For more information about futexes, please read [this](https://eli.thegreenplace.net/2018/basics-of-futexes/) blog post, and the [futex(2)](http://man7.org/linux/man-pages/man2/futex.2.html) man page
use alloc::sync::Arc;
use alloc::collections::VecDeque;
use core::intrinsics;
use spin::{Once, RwLock, RwLockReadGuard, RwLockWriteGuard};

use crate::context::{self, Context};
use crate::time;
use crate::memory::PhysicalAddress;
use crate::paging::{ActivePageTable, VirtualAddress};
use crate::syscall::data::TimeSpec;
use crate::syscall::error::{Error, Result, ESRCH, EAGAIN, EFAULT, EINVAL};
use crate::syscall::flag::{FUTEX_WAIT, FUTEX_WAKE, FUTEX_REQUEUE};
use crate::syscall::validate::{validate_slice, validate_slice_mut};

type FutexList = VecDeque<FutexEntry>;

pub struct FutexEntry {
    target_physaddr: PhysicalAddress,
    context_lock: Arc<RwLock<Context>>,
}

/// Fast userspace mutex list
static FUTEXES: Once<RwLock<FutexList>> = Once::new();

/// Initialize futexes, called if needed
fn init_futexes() -> RwLock<FutexList> {
    RwLock::new(VecDeque::new())
}

/// Get the global futexes list, const
pub fn futexes() -> RwLockReadGuard<'static, FutexList> {
    FUTEXES.call_once(init_futexes).read()
}

/// Get the global futexes list, mutable
pub fn futexes_mut() -> RwLockWriteGuard<'static, FutexList> {
    FUTEXES.call_once(init_futexes).write()
}

// FIXME: Don't take a mutable reference to the addr, since rustc can make assumptions that the
// pointee cannot be changed by another thread, which could make atomic ops useless.
pub fn futex(addr: &mut i32, op: usize, val: i32, val2: usize, addr2: *mut i32) -> Result<usize> {
    let target_physaddr = unsafe {
        let mut active_table = ActivePageTable::new();
        let virtual_address = VirtualAddress::new(addr as *mut i32 as usize);

        // FIXME: Already validated in syscall/mod.rs
        active_table.translate(virtual_address).ok_or(Error::new(EFAULT))?
    };

    match op {
        FUTEX_WAIT => {
            let timeout_opt = if val2 != 0 {
                Some(validate_slice(val2 as *const TimeSpec, 1).map(|req| &req[0])?)
            } else {
                None
            };

            {
                let mut futexes = futexes_mut();

                let context_lock = {
                    let contexts = context::contexts();
                    let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
                    Arc::clone(&context_lock)
                };

                if unsafe { intrinsics::atomic_load(addr) != val } {
                    return Err(Error::new(EAGAIN));
                }

                {
                    let mut context = context_lock.write();

                    if let Some(timeout) = timeout_opt {
                        let start = time::monotonic();
                        let sum = start.1 + timeout.tv_nsec as u64;
                        let end = (start.0 + timeout.tv_sec as u64 + sum / 1_000_000_000, sum % 1_000_000_000);
                        context.wake = Some(end);
                    }

                    context.block("futex");
                }

                futexes.push_back(FutexEntry {
                    target_physaddr,
                    context_lock,
                });
            }

            unsafe { context::switch(); }

            if timeout_opt.is_some() {
                let context_lock = {
                    let contexts = context::contexts();
                    let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
                    Arc::clone(&context_lock)
                };

                {
                    let mut context = context_lock.write();
                    context.wake = None;
                }
            }

            Ok(0)
        },
        FUTEX_WAKE => {
            let mut woken = 0;

            {
                let mut futexes = futexes_mut();

                let mut i = 0;
                while i < futexes.len() && (woken as i32) < val {
                    if futexes[i].target_physaddr == target_physaddr {
                        if let Some(futex) = futexes.swap_remove_back(i) {
                            let mut context_guard = futex.context_lock.write();
                            context_guard.unblock();
                            woken += 1;
                        }
                    } else {
                        i += 1;
                    }
                }
            }

            Ok(woken)
        },
        FUTEX_REQUEUE => {
            let addr2_physaddr = unsafe {
                let mut active_table = ActivePageTable::new();

                let addr2_safe = validate_slice_mut(addr2, 1).map(|addr2_safe| &mut addr2_safe[0])?;
                let addr2_virt = VirtualAddress::new(addr2_safe as *mut i32 as usize);

                // FIXME: Already validated.
                active_table.translate(addr2_virt).ok_or(Error::new(EFAULT))?
            };

            let mut woken = 0;
            let mut requeued = 0;

            {
                let mut futexes = futexes_mut();

                let mut i = 0;
                while i < futexes.len() && (woken as i32) < val {
                    if futexes[i].target_physaddr == target_physaddr {
                        if let Some(futex) = futexes.swap_remove_back(i) {
                            futex.context_lock.write().unblock();
                            woken += 1;
                        }
                    } else {
                        i += 1;
                    }
                }
                while i < futexes.len() && requeued < val2 {
                    if futexes[i].target_physaddr == target_physaddr {
                        futexes[i].target_physaddr = addr2_physaddr;
                        requeued += 1;
                    }
                    i += 1;
                }
            }

            Ok(woken)
        },
        _ => Err(Error::new(EINVAL))
    }
}
<commit_msg>Fix warnings from futex changes<commit_after>//! # Futex
//! Futex or Fast Userspace Mutex is "a method for waiting until a certain condition becomes true."
//!
//! For more information about futexes, please read [this](https://eli.thegreenplace.net/2018/basics-of-futexes/) blog post, and the [futex(2)](http://man7.org/linux/man-pages/man2/futex.2.html) man page
use alloc::sync::Arc;
use alloc::collections::VecDeque;
use core::intrinsics;
use spin::{Once, RwLock, RwLockReadGuard, RwLockWriteGuard};

use crate::context::{self, Context};
use crate::time;
use crate::memory::PhysicalAddress;
use crate::paging::{ActivePageTable, VirtualAddress};
use crate::syscall::data::TimeSpec;
use crate::syscall::error::{Error, Result, ESRCH, EAGAIN, EFAULT, EINVAL};
use crate::syscall::flag::{FUTEX_WAIT, FUTEX_WAKE, FUTEX_REQUEUE};
use crate::syscall::validate::{validate_slice, validate_slice_mut};

type FutexList = VecDeque<FutexEntry>;

pub struct FutexEntry {
    target_physaddr: PhysicalAddress,
    context_lock: Arc<RwLock<Context>>,
}

/// Fast userspace mutex list
static FUTEXES: Once<RwLock<FutexList>> = Once::new();

/// Initialize futexes, called if needed
fn init_futexes() -> RwLock<FutexList> {
    RwLock::new(VecDeque::new())
}

/// Get the global futexes list, const
pub fn futexes() -> RwLockReadGuard<'static, FutexList> {
    FUTEXES.call_once(init_futexes).read()
}

/// Get the global futexes list, mutable
pub fn futexes_mut() -> RwLockWriteGuard<'static, FutexList> {
    FUTEXES.call_once(init_futexes).write()
}

// FIXME: Don't take a mutable reference to the addr, since rustc can make assumptions that the
// pointee cannot be changed by another thread, which could make atomic ops useless.
pub fn futex(addr: &mut i32, op: usize, val: i32, val2: usize, addr2: *mut i32) -> Result<usize> {
    let target_physaddr = unsafe {
        let active_table = ActivePageTable::new();
        let virtual_address = VirtualAddress::new(addr as *mut i32 as usize);

        // FIXME: Already validated in syscall/mod.rs
        active_table.translate(virtual_address).ok_or(Error::new(EFAULT))?
    };

    match op {
        FUTEX_WAIT => {
            let timeout_opt = if val2 != 0 {
                Some(validate_slice(val2 as *const TimeSpec, 1).map(|req| &req[0])?)
            } else {
                None
            };

            {
                let mut futexes = futexes_mut();

                let context_lock = {
                    let contexts = context::contexts();
                    let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
                    Arc::clone(&context_lock)
                };

                if unsafe { intrinsics::atomic_load(addr) != val } {
                    return Err(Error::new(EAGAIN));
                }

                {
                    let mut context = context_lock.write();

                    if let Some(timeout) = timeout_opt {
                        let start = time::monotonic();
                        let sum = start.1 + timeout.tv_nsec as u64;
                        let end = (start.0 + timeout.tv_sec as u64 + sum / 1_000_000_000, sum % 1_000_000_000);
                        context.wake = Some(end);
                    }

                    context.block("futex");
                }

                futexes.push_back(FutexEntry {
                    target_physaddr,
                    context_lock,
                });
            }

            unsafe { context::switch(); }

            if timeout_opt.is_some() {
                let context_lock = {
                    let contexts = context::contexts();
                    let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
                    Arc::clone(&context_lock)
                };

                {
                    let mut context = context_lock.write();
                    context.wake = None;
                }
            }

            Ok(0)
        },
        FUTEX_WAKE => {
            let mut woken = 0;

            {
                let mut futexes = futexes_mut();

                let mut i = 0;
                while i < futexes.len() && (woken as i32) < val {
                    if futexes[i].target_physaddr == target_physaddr {
                        if let Some(futex) = futexes.swap_remove_back(i) {
                            let mut context_guard = futex.context_lock.write();
                            context_guard.unblock();
                            woken += 1;
                        }
                    } else {
                        i += 1;
                    }
                }
            }

            Ok(woken)
        },
        FUTEX_REQUEUE => {
            let addr2_physaddr = unsafe {
                let active_table = ActivePageTable::new();

                let addr2_safe = validate_slice_mut(addr2, 1).map(|addr2_safe| &mut addr2_safe[0])?;
                let addr2_virt = VirtualAddress::new(addr2_safe as *mut i32 as usize);

                // FIXME: Already validated.
                active_table.translate(addr2_virt).ok_or(Error::new(EFAULT))?
            };

            let mut woken = 0;
            let mut requeued = 0;

            {
                let mut futexes = futexes_mut();

                let mut i = 0;
                while i < futexes.len() && (woken as i32) < val {
                    if futexes[i].target_physaddr == target_physaddr {
                        if let Some(futex) = futexes.swap_remove_back(i) {
                            futex.context_lock.write().unblock();
                            woken += 1;
                        }
                    } else {
                        i += 1;
                    }
                }
                while i < futexes.len() && requeued < val2 {
                    if futexes[i].target_physaddr == target_physaddr {
                        futexes[i].target_physaddr = addr2_physaddr;
                        requeued += 1;
                    }
                    i += 1;
                }
            }

            Ok(woken)
        },
        _ => Err(Error::new(EINVAL))
    }
}
<|endoftext|>


<commit_before>use crate::hir::map::Map;
use crate::ty::TyCtxt;
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::sync::{par_iter, Lock, ParallelIterator};
use rustc_hir as hir;
use rustc_hir::def_id::{DefId, DefIndex, CRATE_DEF_INDEX};
use rustc_hir::intravisit;
use rustc_hir::itemlikevisit::ItemLikeVisitor;
use rustc_hir::{HirId, ItemLocalId};

pub fn check_crate(tcx: TyCtxt<'_>) {
    tcx.dep_graph.assert_ignored();

    let errors = Lock::new(Vec::new());
    let hir_map = tcx.hir();

    par_iter(&hir_map.krate().modules).for_each(|(module_id, _)| {
        let local_def_id = hir_map.local_def_id(*module_id);
        hir_map.visit_item_likes_in_module(
            local_def_id,
            &mut OuterVisitor { hir_map, errors: &errors },
        );
    });

    let errors = errors.into_inner();

    if !errors.is_empty() {
        let message = errors.iter().fold(String::new(), |s1, s2| s1 + "\n" + s2);
        tcx.sess.delay_span_bug(rustc_span::DUMMY_SP, &message);
    }
}

struct HirIdValidator<'a, 'hir> {
    hir_map: Map<'hir>,
    owner_def_index: Option<DefIndex>,
    hir_ids_seen: FxHashSet<ItemLocalId>,
    errors: &'a Lock<Vec<String>>,
}

struct OuterVisitor<'a, 'hir> {
    hir_map: Map<'hir>,
    errors: &'a Lock<Vec<String>>,
}

impl<'a, 'hir> OuterVisitor<'a, 'hir> {
    fn new_inner_visitor(&self, hir_map: Map<'hir>) -> HirIdValidator<'a, 'hir> {
        HirIdValidator {
            hir_map,
            owner_def_index: None,
            hir_ids_seen: Default::default(),
            errors: self.errors,
        }
    }
}

impl<'a, 'hir> ItemLikeVisitor<'hir> for OuterVisitor<'a, 'hir> {
    fn visit_item(&mut self, i: &'hir hir::Item<'hir>) {
        let mut inner_visitor = self.new_inner_visitor(self.hir_map);
        inner_visitor.check(i.hir_id, |this| intravisit::walk_item(this, i));
    }

    fn visit_trait_item(&mut self, i: &'hir hir::TraitItem<'hir>) {
        let mut inner_visitor = self.new_inner_visitor(self.hir_map);
        inner_visitor.check(i.hir_id, |this| intravisit::walk_trait_item(this, i));
    }

    fn visit_impl_item(&mut self, i: &'hir hir::ImplItem<'hir>) {
        let mut inner_visitor = self.new_inner_visitor(self.hir_map);
        inner_visitor.check(i.hir_id, |this| intravisit::walk_impl_item(this, i));
    }
}

impl<'a, 'hir> HirIdValidator<'a, 'hir> {
    #[cold]
    #[inline(never)]
    fn error(&self, f: impl FnOnce() -> String) {
        self.errors.lock().push(f());
    }

    fn check<F: FnOnce(&mut HirIdValidator<'a, 'hir>)>(&mut self, hir_id: HirId, walk: F) {
        assert!(self.owner_def_index.is_none());
        let owner_def_index = self.hir_map.local_def_id(hir_id).index;
        self.owner_def_index = Some(owner_def_index);
        walk(self);

        if owner_def_index == CRATE_DEF_INDEX {
            return;
        }

        // There's always at least one entry for the owning item itself
        let max = self
            .hir_ids_seen
            .iter()
            .map(|local_id| local_id.as_usize())
            .max()
            .expect("owning item has no entry");

        if max != self.hir_ids_seen.len() - 1 {
            // Collect the missing ItemLocalIds
            let missing: Vec<_> = (0..=max as u32)
                .filter(|&i| !self.hir_ids_seen.contains(&ItemLocalId::from_u32(i)))
                .collect();

            // Try to map those to something more useful
            let mut missing_items = Vec::with_capacity(missing.len());

            for local_id in missing {
                let hir_id =
                    HirId { owner: owner_def_index, local_id: ItemLocalId::from_u32(local_id) };

                trace!("missing hir id {:#?}", hir_id);

                missing_items.push(format!(
                    "[local_id: {}, node:{}]",
                    local_id,
                    self.hir_map.node_to_string(hir_id)
                ));
            }
            self.error(|| {
                format!(
                    "ItemLocalIds not assigned densely in {}. \
                Max ItemLocalId = {}, missing IDs = {:?}; seens IDs = {:?}",
                    self.hir_map.def_path(DefId::local(owner_def_index)).to_string_no_crate(),
                    max,
                    missing_items,
                    self.hir_ids_seen
                        .iter()
                        .map(|&local_id| HirId { owner: owner_def_index, local_id })
                        .map(|h| format!("({:?} {})", h, self.hir_map.node_to_string(h)))
                        .collect::<Vec<_>>()
                )
            });
        }
    }
}

impl<'a, 'hir> intravisit::Visitor<'hir> for HirIdValidator<'a, 'hir> {
    type Map = Map<'hir>;

    fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
        intravisit::NestedVisitorMap::OnlyBodies(self.hir_map)
    }

    fn visit_id(&mut self, hir_id: HirId) {
        let owner = self.owner_def_index.expect("no owner_def_index");

        if hir_id == hir::DUMMY_HIR_ID {
            self.error(|| {
                format!(
                    "HirIdValidator: HirId {:?} is invalid",
                    self.hir_map.node_to_string(hir_id)
                )
            });
            return;
        }

        if owner != hir_id.owner {
            self.error(|| {
                format!(
                    "HirIdValidator: The recorded owner of {} is {} instead of {}",
                    self.hir_map.node_to_string(hir_id),
                    self.hir_map.def_path(DefId::local(hir_id.owner)).to_string_no_crate(),
                    self.hir_map.def_path(DefId::local(owner)).to_string_no_crate()
                )
            });
        }

        self.hir_ids_seen.insert(hir_id.local_id);
    }

    fn visit_impl_item_ref(&mut self, _: &'hir hir::ImplItemRef<'hir>) {
        // Explicitly do nothing here. ImplItemRefs contain hir::Visibility
        // values that actually belong to an ImplItem instead of the ItemKind::Impl
        // we are currently in. So for those it's correct that they have a
        // different owner.
    }
}
<commit_msg>Don't try to print missing HIR ids<commit_after>use crate::hir::map::Map;
use crate::ty::TyCtxt;
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::sync::{par_iter, Lock, ParallelIterator};
use rustc_hir as hir;
use rustc_hir::def_id::{DefId, DefIndex, CRATE_DEF_INDEX};
use rustc_hir::intravisit;
use rustc_hir::itemlikevisit::ItemLikeVisitor;
use rustc_hir::{HirId, ItemLocalId};

pub fn check_crate(tcx: TyCtxt<'_>) {
    tcx.dep_graph.assert_ignored();

    let errors = Lock::new(Vec::new());
    let hir_map = tcx.hir();

    par_iter(&hir_map.krate().modules).for_each(|(module_id, _)| {
        let local_def_id = hir_map.local_def_id(*module_id);
        hir_map.visit_item_likes_in_module(
            local_def_id,
            &mut OuterVisitor { hir_map, errors: &errors },
        );
    });

    let errors = errors.into_inner();

    if !errors.is_empty() {
        let message = errors.iter().fold(String::new(), |s1, s2| s1 + "\n" + s2);
        tcx.sess.delay_span_bug(rustc_span::DUMMY_SP, &message);
    }
}

struct HirIdValidator<'a, 'hir> {
    hir_map: Map<'hir>,
    owner_def_index: Option<DefIndex>,
    hir_ids_seen: FxHashSet<ItemLocalId>,
    errors: &'a Lock<Vec<String>>,
}

struct OuterVisitor<'a, 'hir> {
    hir_map: Map<'hir>,
    errors: &'a Lock<Vec<String>>,
}

impl<'a, 'hir> OuterVisitor<'a, 'hir> {
    fn new_inner_visitor(&self, hir_map: Map<'hir>) -> HirIdValidator<'a, 'hir> {
        HirIdValidator {
            hir_map,
            owner_def_index: None,
            hir_ids_seen: Default::default(),
            errors: self.errors,
        }
    }
}

impl<'a, 'hir> ItemLikeVisitor<'hir> for OuterVisitor<'a, 'hir> {
    fn visit_item(&mut self, i: &'hir hir::Item<'hir>) {
        let mut inner_visitor = self.new_inner_visitor(self.hir_map);
        inner_visitor.check(i.hir_id, |this| intravisit::walk_item(this, i));
    }

    fn visit_trait_item(&mut self, i: &'hir hir::TraitItem<'hir>) {
        let mut inner_visitor = self.new_inner_visitor(self.hir_map);
        inner_visitor.check(i.hir_id, |this| intravisit::walk_trait_item(this, i));
    }

    fn visit_impl_item(&mut self, i: &'hir hir::ImplItem<'hir>) {
        let mut inner_visitor = self.new_inner_visitor(self.hir_map);
        inner_visitor.check(i.hir_id, |this| intravisit::walk_impl_item(this, i));
    }
}

impl<'a, 'hir> HirIdValidator<'a, 'hir> {
    #[cold]
    #[inline(never)]
    fn error(&self, f: impl FnOnce() -> String) {
        self.errors.lock().push(f());
    }

    fn check<F: FnOnce(&mut HirIdValidator<'a, 'hir>)>(&mut self, hir_id: HirId, walk: F) {
        assert!(self.owner_def_index.is_none());
        let owner_def_index = self.hir_map.local_def_id(hir_id).index;
        self.owner_def_index = Some(owner_def_index);
        walk(self);

        if owner_def_index == CRATE_DEF_INDEX {
            return;
        }

        // There's always at least one entry for the owning item itself
        let max = self
            .hir_ids_seen
            .iter()
            .map(|local_id| local_id.as_usize())
            .max()
            .expect("owning item has no entry");

        if max != self.hir_ids_seen.len() - 1 {
            // Collect the missing ItemLocalIds
            let missing: Vec<_> = (0..=max as u32)
                .filter(|&i| !self.hir_ids_seen.contains(&ItemLocalId::from_u32(i)))
                .collect();

            // Try to map those to something more useful
            let mut missing_items = Vec::with_capacity(missing.len());

            for local_id in missing {
                let hir_id =
                    HirId { owner: owner_def_index, local_id: ItemLocalId::from_u32(local_id) };

                trace!("missing hir id {:#?}", hir_id);

                missing_items.push(format!(
                    "[local_id: {}, owner: {}]",
                    local_id,
                    self.hir_map.def_path(DefId::local(owner_def_index)).to_string_no_crate()
                ));
            }
            self.error(|| {
                format!(
                    "ItemLocalIds not assigned densely in {}. \
                Max ItemLocalId = {}, missing IDs = {:?}; seens IDs = {:?}",
                    self.hir_map.def_path(DefId::local(owner_def_index)).to_string_no_crate(),
                    max,
                    missing_items,
                    self.hir_ids_seen
                        .iter()
                        .map(|&local_id| HirId { owner: owner_def_index, local_id })
                        .map(|h| format!("({:?} {})", h, self.hir_map.node_to_string(h)))
                        .collect::<Vec<_>>()
                )
            });
        }
    }
}

impl<'a, 'hir> intravisit::Visitor<'hir> for HirIdValidator<'a, 'hir> {
    type Map = Map<'hir>;

    fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
        intravisit::NestedVisitorMap::OnlyBodies(self.hir_map)
    }

    fn visit_id(&mut self, hir_id: HirId) {
        let owner = self.owner_def_index.expect("no owner_def_index");

        if hir_id == hir::DUMMY_HIR_ID {
            self.error(|| {
                format!(
                    "HirIdValidator: HirId {:?} is invalid",
                    self.hir_map.node_to_string(hir_id)
                )
            });
            return;
        }

        if owner != hir_id.owner {
            self.error(|| {
                format!(
                    "HirIdValidator: The recorded owner of {} is {} instead of {}",
                    self.hir_map.node_to_string(hir_id),
                    self.hir_map.def_path(DefId::local(hir_id.owner)).to_string_no_crate(),
                    self.hir_map.def_path(DefId::local(owner)).to_string_no_crate()
                )
            });
        }

        self.hir_ids_seen.insert(hir_id.local_id);
    }

    fn visit_impl_item_ref(&mut self, _: &'hir hir::ImplItemRef<'hir>) {
        // Explicitly do nothing here. ImplItemRefs contain hir::Visibility
        // values that actually belong to an ImplItem instead of the ItemKind::Impl
        // we are currently in. So for those it's correct that they have a
        // different owner.
    }
}
<|endoftext|>