<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Generic types for CSS values in SVG

use cssparser::Parser;
use parser::{Parse, ParserContext};
use std::fmt;
use style_traits::{ParseError, StyleParseError, ToCss};
use values::computed::NumberOrPercentage;
use values::computed::length::LengthOrPercentage;
use values::distance::{ComputeSquaredDistance, SquaredDistance};

/// An SVG paint value
///
/// https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Animate, Clone, ComputeSquaredDistance, Debug, PartialEq)]
#[derive(ToAnimatedValue, ToComputedValue, ToCss)]
pub struct SVGPaint<ColorType, UrlPaintServer> {
    /// The paint source
    pub kind: SVGPaintKind<ColorType, UrlPaintServer>,
    /// The fallback color
    pub fallback: Option<ColorType>,
}

/// An SVG paint value without the fallback
///
/// Whereas the spec only allows PaintServer
/// to have a fallback, Gecko lets the context
/// properties have a fallback as well.
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Animate, Clone, ComputeSquaredDistance, Debug, PartialEq)]
#[derive(ToAnimatedValue, ToAnimatedZero, ToComputedValue, ToCss)]
pub enum SVGPaintKind<ColorType, UrlPaintServer> {
    /// `none`
    None,
    /// `<color>`
    Color(ColorType),
    /// `url(...)`
    #[animation(error)]
    PaintServer(UrlPaintServer),
    /// `context-fill`
    ContextFill,
    /// `context-stroke`
    ContextStroke,
}

impl<ColorType, UrlPaintServer> SVGPaintKind<ColorType, UrlPaintServer> {
    /// Parse a keyword value only
    fn parse_ident<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
        try_match_ident_ignore_ascii_case! { input.expect_ident()?,
            "none" => Ok(SVGPaintKind::None),
            "context-fill" => Ok(SVGPaintKind::ContextFill),
            "context-stroke" => Ok(SVGPaintKind::ContextStroke),
        }
    }
}

/// Parse SVGPaint's fallback.
/// fallback is keyword(none) or Color.
/// https://svgwg.org/svg2-draft/painting.html#SpecifyingPaint
fn parse_fallback<'i, 't, ColorType: Parse>(context: &ParserContext,
                                            input: &mut Parser<'i, 't>)
                                            -> Option<ColorType> {
    if input.try(|i| i.expect_ident_matching("none")).is_ok() {
        None
    } else {
        input.try(|i| ColorType::parse(context, i)).ok()
    }
}

impl<ColorType: Parse, UrlPaintServer: Parse> Parse for SVGPaint<ColorType, UrlPaintServer> {
    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
        if let Ok(url) = input.try(|i| UrlPaintServer::parse(context, i)) {
            Ok(SVGPaint {
                kind: SVGPaintKind::PaintServer(url),
                fallback: parse_fallback(context, input),
            })
        } else if let Ok(kind) = input.try(SVGPaintKind::parse_ident) {
            if let SVGPaintKind::None = kind {
                Ok(SVGPaint {
                    kind: kind,
                    fallback: None,
                })
            } else {
                Ok(SVGPaint {
                    kind: kind,
                    fallback: parse_fallback(context, input),
                })
            }
        } else if let Ok(color) = input.try(|i| ColorType::parse(context, i)) {
            Ok(SVGPaint {
                kind: SVGPaintKind::Color(color),
                fallback: None,
            })
        } else {
            Err(StyleParseError::UnspecifiedError.into())
        }
    }
}

/// A value of <length> | <percentage> | <number> for svg which allow unitless length.
/// https://www.w3.org/TR/SVG11/painting.html#StrokeProperties
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Clone, Copy, Debug, PartialEq, ToAnimatedValue)]
#[derive(ToAnimatedZero, ToComputedValue, ToCss)]
pub enum SvgLengthOrPercentageOrNumber<LengthOrPercentage, Number> {
    /// <length> | <percentage>
    LengthOrPercentage(LengthOrPercentage),
    /// <number>
    Number(Number),
}

impl<L, N> ComputeSquaredDistance for SvgLengthOrPercentageOrNumber<L, N>
    where
        L: ComputeSquaredDistance + Copy + Into<NumberOrPercentage>,
        N: ComputeSquaredDistance + Copy + Into<NumberOrPercentage>
{
    #[inline]
    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
        match (self, other) {
            (
                &SvgLengthOrPercentageOrNumber::LengthOrPercentage(ref from),
                &SvgLengthOrPercentageOrNumber::LengthOrPercentage(ref to)
            ) => {
                from.compute_squared_distance(to)
            },
            (
                &SvgLengthOrPercentageOrNumber::Number(ref from),
                &SvgLengthOrPercentageOrNumber::Number(ref to)
            ) => {
                from.compute_squared_distance(to)
            },
            (
                &SvgLengthOrPercentageOrNumber::LengthOrPercentage(from),
                &SvgLengthOrPercentageOrNumber::Number(to)
            ) => {
                from.into().compute_squared_distance(&to.into())
            },
            (
                &SvgLengthOrPercentageOrNumber::Number(from),
                &SvgLengthOrPercentageOrNumber::LengthOrPercentage(to)
            ) => {
                from.into().compute_squared_distance(&to.into())
            },
        }
    }
}

impl<LengthOrPercentageType, NumberType> SvgLengthOrPercentageOrNumber<LengthOrPercentageType, NumberType>
    where LengthOrPercentage: From<LengthOrPercentageType>,
          LengthOrPercentageType: Copy
{
    /// return true if this struct has calc value.
    pub fn has_calc(&self) -> bool {
        match self {
            &SvgLengthOrPercentageOrNumber::LengthOrPercentage(lop) => {
                match LengthOrPercentage::from(lop) {
                    LengthOrPercentage::Calc(_) => true,
                    _ => false,
                }
            },
            _ => false,
        }
    }
}

/// Parsing the SvgLengthOrPercentageOrNumber. At first, we need to parse number
/// since prevent converting to the length.
impl <LengthOrPercentageType: Parse, NumberType: Parse> Parse for
    SvgLengthOrPercentageOrNumber<LengthOrPercentageType, NumberType> {
    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                     -> Result<Self, ParseError<'i>> {
        if let Ok(num) = input.try(|i| NumberType::parse(context, i)) {
            return Ok(SvgLengthOrPercentageOrNumber::Number(num));
        }

        if let Ok(lop) = input.try(|i| LengthOrPercentageType::parse(context, i)) {
            return Ok(SvgLengthOrPercentageOrNumber::LengthOrPercentage(lop));
        }
        Err(StyleParseError::UnspecifiedError.into())
    }
}

/// An SVG length value supports `context-value` in addition to length.
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Clone, ComputeSquaredDistance, Copy, Debug, PartialEq)]
#[derive(ToAnimatedValue, ToAnimatedZero)]
#[derive(ToComputedValue, ToCss)]
pub enum SVGLength<LengthType> {
    /// `<length> | <percentage> | <number>`
    Length(LengthType),
    /// `context-value`
    ContextValue,
}

/// Generic value for stroke-dasharray.
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Clone, ComputeSquaredDistance, Debug, PartialEq, ToAnimatedValue, ToComputedValue)]
pub enum SVGStrokeDashArray<LengthType> {
    /// `[ <length> | <percentage> | <number> ]#`
    Values(Vec<LengthType>),
    /// `context-value`
    ContextValue,
}

impl<LengthType> ToCss for SVGStrokeDashArray<LengthType> where LengthType: ToCss {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
        match self {
            &SVGStrokeDashArray::Values(ref values) => {
                let mut iter = values.iter();
                if let Some(first) = iter.next() {
                    first.to_css(dest)?;
                    for item in iter {
                        dest.write_str(", ")?;
                        item.to_css(dest)?;
                    }
                    Ok(())
                } else {
                    dest.write_str("none")
                }
            }
            &SVGStrokeDashArray::ContextValue => {
                dest.write_str("context-value")
            }
        }
    }
}

/// An SVG opacity value accepts `context-{fill,stroke}-opacity` in
/// addition to opacity value.
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Clone, ComputeSquaredDistance, Copy, Debug)]
#[derive(PartialEq, ToAnimatedZero, ToComputedValue, ToCss)]
pub enum SVGOpacity<OpacityType> {
    /// `<opacity-value>`
    Opacity(OpacityType),
    /// `context-fill-opacity`
    ContextFillOpacity,
    /// `context-stroke-opacity`
    ContextStrokeOpacity,
}
<commit_msg>Don't allow interpolating SVGPaintKind::None.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Generic types for CSS values in SVG

use cssparser::Parser;
use parser::{Parse, ParserContext};
use std::fmt;
use style_traits::{ParseError, StyleParseError, ToCss};
use values::computed::NumberOrPercentage;
use values::computed::length::LengthOrPercentage;
use values::distance::{ComputeSquaredDistance, SquaredDistance};

/// An SVG paint value
///
/// https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Animate, Clone, ComputeSquaredDistance, Debug, PartialEq)]
#[derive(ToAnimatedValue, ToComputedValue, ToCss)]
pub struct SVGPaint<ColorType, UrlPaintServer> {
    /// The paint source
    pub kind: SVGPaintKind<ColorType, UrlPaintServer>,
    /// The fallback color
    pub fallback: Option<ColorType>,
}

/// An SVG paint value without the fallback
///
/// Whereas the spec only allows PaintServer
/// to have a fallback, Gecko lets the context
/// properties have a fallback as well.
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Animate, Clone, ComputeSquaredDistance, Debug, PartialEq)]
#[derive(ToAnimatedValue, ToAnimatedZero, ToComputedValue, ToCss)]
pub enum SVGPaintKind<ColorType, UrlPaintServer> {
    /// `none`
    #[animation(error)]
    None,
    /// `<color>`
    Color(ColorType),
    /// `url(...)`
    #[animation(error)]
    PaintServer(UrlPaintServer),
    /// `context-fill`
    ContextFill,
    /// `context-stroke`
    ContextStroke,
}

impl<ColorType, UrlPaintServer> SVGPaintKind<ColorType, UrlPaintServer> {
    /// Parse a keyword value only
    fn parse_ident<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
        try_match_ident_ignore_ascii_case! { input.expect_ident()?,
            "none" => Ok(SVGPaintKind::None),
            "context-fill" => Ok(SVGPaintKind::ContextFill),
            "context-stroke" => Ok(SVGPaintKind::ContextStroke),
        }
    }
}

/// Parse SVGPaint's fallback.
/// fallback is keyword(none) or Color.
/// https://svgwg.org/svg2-draft/painting.html#SpecifyingPaint
fn parse_fallback<'i, 't, ColorType: Parse>(context: &ParserContext,
                                            input: &mut Parser<'i, 't>)
                                            -> Option<ColorType> {
    if input.try(|i| i.expect_ident_matching("none")).is_ok() {
        None
    } else {
        input.try(|i| ColorType::parse(context, i)).ok()
    }
}

impl<ColorType: Parse, UrlPaintServer: Parse> Parse for SVGPaint<ColorType, UrlPaintServer> {
    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
        if let Ok(url) = input.try(|i| UrlPaintServer::parse(context, i)) {
            Ok(SVGPaint {
                kind: SVGPaintKind::PaintServer(url),
                fallback: parse_fallback(context, input),
            })
        } else if let Ok(kind) = input.try(SVGPaintKind::parse_ident) {
            if let SVGPaintKind::None = kind {
                Ok(SVGPaint {
                    kind: kind,
                    fallback: None,
                })
            } else {
                Ok(SVGPaint {
                    kind: kind,
                    fallback: parse_fallback(context, input),
                })
            }
        } else if let Ok(color) = input.try(|i| ColorType::parse(context, i)) {
            Ok(SVGPaint {
                kind: SVGPaintKind::Color(color),
                fallback: None,
            })
        } else {
            Err(StyleParseError::UnspecifiedError.into())
        }
    }
}

/// A value of <length> | <percentage> | <number> for svg which allow unitless length.
/// https://www.w3.org/TR/SVG11/painting.html#StrokeProperties
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Clone, Copy, Debug, PartialEq, ToAnimatedValue)]
#[derive(ToAnimatedZero, ToComputedValue, ToCss)]
pub enum SvgLengthOrPercentageOrNumber<LengthOrPercentage, Number> {
    /// <length> | <percentage>
    LengthOrPercentage(LengthOrPercentage),
    /// <number>
    Number(Number),
}

impl<L, N> ComputeSquaredDistance for SvgLengthOrPercentageOrNumber<L, N>
    where
        L: ComputeSquaredDistance + Copy + Into<NumberOrPercentage>,
        N: ComputeSquaredDistance + Copy + Into<NumberOrPercentage>
{
    #[inline]
    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
        match (self, other) {
            (
                &SvgLengthOrPercentageOrNumber::LengthOrPercentage(ref from),
                &SvgLengthOrPercentageOrNumber::LengthOrPercentage(ref to)
            ) => {
                from.compute_squared_distance(to)
            },
            (
                &SvgLengthOrPercentageOrNumber::Number(ref from),
                &SvgLengthOrPercentageOrNumber::Number(ref to)
            ) => {
                from.compute_squared_distance(to)
            },
            (
                &SvgLengthOrPercentageOrNumber::LengthOrPercentage(from),
                &SvgLengthOrPercentageOrNumber::Number(to)
            ) => {
                from.into().compute_squared_distance(&to.into())
            },
            (
                &SvgLengthOrPercentageOrNumber::Number(from),
                &SvgLengthOrPercentageOrNumber::LengthOrPercentage(to)
            ) => {
                from.into().compute_squared_distance(&to.into())
            },
        }
    }
}

impl<LengthOrPercentageType, NumberType> SvgLengthOrPercentageOrNumber<LengthOrPercentageType, NumberType>
    where LengthOrPercentage: From<LengthOrPercentageType>,
          LengthOrPercentageType: Copy
{
    /// return true if this struct has calc value.
    pub fn has_calc(&self) -> bool {
        match self {
            &SvgLengthOrPercentageOrNumber::LengthOrPercentage(lop) => {
                match LengthOrPercentage::from(lop) {
                    LengthOrPercentage::Calc(_) => true,
                    _ => false,
                }
            },
            _ => false,
        }
    }
}

/// Parsing the SvgLengthOrPercentageOrNumber. At first, we need to parse number
/// since prevent converting to the length.
impl <LengthOrPercentageType: Parse, NumberType: Parse> Parse for
    SvgLengthOrPercentageOrNumber<LengthOrPercentageType, NumberType> {
    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                     -> Result<Self, ParseError<'i>> {
        if let Ok(num) = input.try(|i| NumberType::parse(context, i)) {
            return Ok(SvgLengthOrPercentageOrNumber::Number(num));
        }

        if let Ok(lop) = input.try(|i| LengthOrPercentageType::parse(context, i)) {
            return Ok(SvgLengthOrPercentageOrNumber::LengthOrPercentage(lop));
        }
        Err(StyleParseError::UnspecifiedError.into())
    }
}

/// An SVG length value supports `context-value` in addition to length.
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Clone, ComputeSquaredDistance, Copy, Debug, PartialEq)]
#[derive(ToAnimatedValue, ToAnimatedZero)]
#[derive(ToComputedValue, ToCss)]
pub enum SVGLength<LengthType> {
    /// `<length> | <percentage> | <number>`
    Length(LengthType),
    /// `context-value`
    ContextValue,
}

/// Generic value for stroke-dasharray.
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Clone, ComputeSquaredDistance, Debug, PartialEq, ToAnimatedValue, ToComputedValue)]
pub enum SVGStrokeDashArray<LengthType> {
    /// `[ <length> | <percentage> | <number> ]#`
    Values(Vec<LengthType>),
    /// `context-value`
    ContextValue,
}

impl<LengthType> ToCss for SVGStrokeDashArray<LengthType> where LengthType: ToCss {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
        match self {
            &SVGStrokeDashArray::Values(ref values) => {
                let mut iter = values.iter();
                if let Some(first) = iter.next() {
                    first.to_css(dest)?;
                    for item in iter {
                        dest.write_str(", ")?;
                        item.to_css(dest)?;
                    }
                    Ok(())
                } else {
                    dest.write_str("none")
                }
            }
            &SVGStrokeDashArray::ContextValue => {
                dest.write_str("context-value")
            }
        }
    }
}

/// An SVG opacity value accepts `context-{fill,stroke}-opacity` in
/// addition to opacity value.
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Clone, ComputeSquaredDistance, Copy, Debug)]
#[derive(PartialEq, ToAnimatedZero, ToComputedValue, ToCss)]
pub enum SVGOpacity<OpacityType> {
    /// `<opacity-value>`
    Opacity(OpacityType),
    /// `context-fill-opacity`
    ContextFillOpacity,
    /// `context-stroke-opacity`
    ContextStrokeOpacity,
}
<|endoftext|>


<commit_before>use rustc_errors::DiagnosticBuilder;
use rustc_middle::lint::LintDiagnosticBuilder;
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::*;
use rustc_middle::ty::TyCtxt;
use rustc_session::lint::builtin::CONST_ITEM_MUTATION;
use rustc_span::def_id::DefId;

use crate::transform::{MirPass, MirSource};

pub struct CheckConstItemMutation;

impl<'tcx> MirPass<'tcx> for CheckConstItemMutation {
    fn run_pass(&self, tcx: TyCtxt<'tcx>, _src: MirSource<'tcx>, body: &mut Body<'tcx>) {
        let mut checker = ConstMutationChecker { body, tcx, target_local: None };
        checker.visit_body(&body);
    }
}

struct ConstMutationChecker<'a, 'tcx> {
    body: &'a Body<'tcx>,
    tcx: TyCtxt<'tcx>,
    target_local: Option<Local>,
}

impl<'a, 'tcx> ConstMutationChecker<'a, 'tcx> {
    fn is_const_item(&self, local: Local) -> Option<DefId> {
        if let Some(box LocalInfo::ConstRef { def_id }) = self.body.local_decls[local].local_info {
            Some(def_id)
        } else {
            None
        }
    }

    fn is_const_item_without_destructor(&self, local: Local) -> Option<DefId> {
        let def_id = self.is_const_item(local)?;

        // We avoid linting mutation of a const item if the const's type has a
        // Drop impl. The Drop logic observes the mutation which was performed.
        //
        //     struct Log { msg: &'static str }
        //     const LOG: Log = Log { msg: "" };
        //     impl Drop for Log {
        //         fn drop(&mut self) { println!("{}", self.msg); }
        //     }
        //
        //     LOG.msg = "wow";  // prints "wow"
        //
        match self.tcx.adt_destructor(def_id) {
            Some(_) => None,
            None => Some(def_id),
        }
    }

    fn lint_const_item_usage(
        &self,
        const_item: DefId,
        location: Location,
        decorate: impl for<'b> FnOnce(LintDiagnosticBuilder<'b>) -> DiagnosticBuilder<'b>,
    ) {
        let source_info = self.body.source_info(location);
        let lint_root = self.body.source_scopes[source_info.scope]
            .local_data
            .as_ref()
            .assert_crate_local()
            .lint_root;

        self.tcx.struct_span_lint_hir(CONST_ITEM_MUTATION, lint_root, source_info.span, |lint| {
            decorate(lint)
                .span_note(self.tcx.def_span(const_item), "`const` item defined here")
                .emit()
        });
    }
}

impl<'a, 'tcx> Visitor<'tcx> for ConstMutationChecker<'a, 'tcx> {
    fn visit_statement(&mut self, stmt: &Statement<'tcx>, loc: Location) {
        if let StatementKind::Assign(box (lhs, _)) = &stmt.kind {
            // Check for assignment to fields of a constant
            // Assigning directly to a constant (e.g. `FOO = true;`) is a hard error,
            // so emitting a lint would be redundant.
            if !lhs.projection.is_empty() {
                if let Some(def_id) = self.is_const_item_without_destructor(lhs.local) {
                    // Don't lint on writes through a pointer
                    // (e.g. `unsafe { *FOO = 0; *BAR.field = 1; }`)
                    if !matches!(lhs.projection.last(), Some(PlaceElem::Deref)) {
                        self.lint_const_item_usage(def_id, loc, |lint| {
                            let mut lint = lint.build("attempting to modify a `const` item");
                            lint.note("each usage of a `const` item creates a new temporary - the original `const` item will not be modified");
                            lint
                        })
                    }
                }
            }
            // We are looking for MIR of the form:
            //
            // ```
            // _1 = const FOO;
            // _2 = &mut _1;
            // method_call(_2, ..)
            // ```
            //
            // Record our current LHS, so that we can detect this
            // pattern in `visit_rvalue`
            self.target_local = lhs.as_local();
        }
        self.super_statement(stmt, loc);
        self.target_local = None;
    }
    fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, loc: Location) {
        if let Rvalue::Ref(_, BorrowKind::Mut { .. }, place) = rvalue {
            let local = place.local;
            if let Some(def_id) = self.is_const_item_without_destructor(local) {
                // If this Rvalue is being used as the right-hand side of a
                // `StatementKind::Assign`, see if it ends up getting used as
                // the `self` parameter of a method call (as the terminator of our current
                // BasicBlock). If so, we emit a more specific lint.
                let method_did = self.target_local.and_then(|target_local| {
                    crate::util::find_self_call(self.tcx, &self.body, target_local, loc.block)
                });
                let lint_loc =
                    if method_did.is_some() { self.body.terminator_loc(loc.block) } else { loc };
                self.lint_const_item_usage(def_id, lint_loc, |lint| {
                    let mut lint = lint.build("taking a mutable reference to a `const` item");
                    lint
                        .note("each usage of a `const` item creates a new temporary")
                        .note("the mutable reference will refer to this temporary, not the original `const` item");

                    if let Some((method_did, _substs)) = method_did {
                        lint.span_note(self.tcx.def_span(method_did), "mutable reference created due to call to this method");
                    }

                    lint
                });
            }
        }
        self.super_rvalue(rvalue, loc);
    }
}
<commit_msg>Skip dropck::check_drop_impl in is_const_item_without_destructor<commit_after>use rustc_errors::DiagnosticBuilder;
use rustc_middle::lint::LintDiagnosticBuilder;
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::*;
use rustc_middle::ty::TyCtxt;
use rustc_session::lint::builtin::CONST_ITEM_MUTATION;
use rustc_span::def_id::DefId;

use crate::transform::{MirPass, MirSource};

pub struct CheckConstItemMutation;

impl<'tcx> MirPass<'tcx> for CheckConstItemMutation {
    fn run_pass(&self, tcx: TyCtxt<'tcx>, _src: MirSource<'tcx>, body: &mut Body<'tcx>) {
        let mut checker = ConstMutationChecker { body, tcx, target_local: None };
        checker.visit_body(&body);
    }
}

struct ConstMutationChecker<'a, 'tcx> {
    body: &'a Body<'tcx>,
    tcx: TyCtxt<'tcx>,
    target_local: Option<Local>,
}

impl<'a, 'tcx> ConstMutationChecker<'a, 'tcx> {
    fn is_const_item(&self, local: Local) -> Option<DefId> {
        if let Some(box LocalInfo::ConstRef { def_id }) = self.body.local_decls[local].local_info {
            Some(def_id)
        } else {
            None
        }
    }

    fn is_const_item_without_destructor(&self, local: Local) -> Option<DefId> {
        let def_id = self.is_const_item(local)?;
        let mut any_dtor = |_tcx, _def_id| Ok(());

        // We avoid linting mutation of a const item if the const's type has a
        // Drop impl. The Drop logic observes the mutation which was performed.
        //
        //     struct Log { msg: &'static str }
        //     const LOG: Log = Log { msg: "" };
        //     impl Drop for Log {
        //         fn drop(&mut self) { println!("{}", self.msg); }
        //     }
        //
        //     LOG.msg = "wow";  // prints "wow"
        //
        match self.tcx.calculate_dtor(def_id, &mut any_dtor) {
            Some(_) => None,
            None => Some(def_id),
        }
    }

    fn lint_const_item_usage(
        &self,
        const_item: DefId,
        location: Location,
        decorate: impl for<'b> FnOnce(LintDiagnosticBuilder<'b>) -> DiagnosticBuilder<'b>,
    ) {
        let source_info = self.body.source_info(location);
        let lint_root = self.body.source_scopes[source_info.scope]
            .local_data
            .as_ref()
            .assert_crate_local()
            .lint_root;

        self.tcx.struct_span_lint_hir(CONST_ITEM_MUTATION, lint_root, source_info.span, |lint| {
            decorate(lint)
                .span_note(self.tcx.def_span(const_item), "`const` item defined here")
                .emit()
        });
    }
}

impl<'a, 'tcx> Visitor<'tcx> for ConstMutationChecker<'a, 'tcx> {
    fn visit_statement(&mut self, stmt: &Statement<'tcx>, loc: Location) {
        if let StatementKind::Assign(box (lhs, _)) = &stmt.kind {
            // Check for assignment to fields of a constant
            // Assigning directly to a constant (e.g. `FOO = true;`) is a hard error,
            // so emitting a lint would be redundant.
            if !lhs.projection.is_empty() {
                if let Some(def_id) = self.is_const_item_without_destructor(lhs.local) {
                    // Don't lint on writes through a pointer
                    // (e.g. `unsafe { *FOO = 0; *BAR.field = 1; }`)
                    if !matches!(lhs.projection.last(), Some(PlaceElem::Deref)) {
                        self.lint_const_item_usage(def_id, loc, |lint| {
                            let mut lint = lint.build("attempting to modify a `const` item");
                            lint.note("each usage of a `const` item creates a new temporary - the original `const` item will not be modified");
                            lint
                        })
                    }
                }
            }
            // We are looking for MIR of the form:
            //
            // ```
            // _1 = const FOO;
            // _2 = &mut _1;
            // method_call(_2, ..)
            // ```
            //
            // Record our current LHS, so that we can detect this
            // pattern in `visit_rvalue`
            self.target_local = lhs.as_local();
        }
        self.super_statement(stmt, loc);
        self.target_local = None;
    }
    fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, loc: Location) {
        if let Rvalue::Ref(_, BorrowKind::Mut { .. }, place) = rvalue {
            let local = place.local;
            if let Some(def_id) = self.is_const_item_without_destructor(local) {
                // If this Rvalue is being used as the right-hand side of a
                // `StatementKind::Assign`, see if it ends up getting used as
                // the `self` parameter of a method call (as the terminator of our current
                // BasicBlock). If so, we emit a more specific lint.
                let method_did = self.target_local.and_then(|target_local| {
                    crate::util::find_self_call(self.tcx, &self.body, target_local, loc.block)
                });
                let lint_loc =
                    if method_did.is_some() { self.body.terminator_loc(loc.block) } else { loc };
                self.lint_const_item_usage(def_id, lint_loc, |lint| {
                    let mut lint = lint.build("taking a mutable reference to a `const` item");
                    lint
                        .note("each usage of a `const` item creates a new temporary")
                        .note("the mutable reference will refer to this temporary, not the original `const` item");

                    if let Some((method_did, _substs)) = method_did {
                        lint.span_note(self.tcx.def_span(method_did), "mutable reference created due to call to this method");
                    }

                    lint
                });
            }
        }
        self.super_rvalue(rvalue, loc);
    }
}
<|endoftext|>


<commit_before>// Std
use std::io::Write;

// Internal
use clap::*;
use clap_complete::*;

/// Generate fig completion file
pub struct Fig;

impl Generator for Fig {
    fn file_name(&self, name: &str) -> String {
        format!("{}.ts", name)
    }

    fn generate(&self, cmd: &Command, buf: &mut dyn Write) {
        let command = cmd.get_bin_name().unwrap();
        let mut buffer = String::new();

        buffer.push_str(&format!(
            "const completion: Fig.Spec = {{\n  name: \"{}\",\n",
            command
        ));

        buffer.push_str(&format!(
            "  description: \"{}\",\n",
            cmd.get_about().unwrap_or_default()
        ));

        gen_fig_inner(command, &[], 2, cmd, &mut buffer);

        buffer.push_str("};\n\nexport default completion;\n");

        buf.write_all(buffer.as_bytes())
            .expect("Failed to write to generated file");
    }
}

// Escape string inside double quotes
fn escape_string(string: &str) -> String {
    string.replace('\\', "\\\\").replace('\"', "\\\"")
}

fn gen_fig_inner(
    root_command: &str,
    parent_commands: &[&str],
    indent: usize,
    cmd: &Command,
    buffer: &mut String,
) {
    if cmd.has_subcommands() {
        buffer.push_str(&format!("{:indent$}subcommands: [\n", "", indent = indent));
        // generate subcommands
        for subcommand in cmd.get_subcommands() {
            let mut aliases: Vec<&str> = subcommand.get_all_aliases().collect();
            if !aliases.is_empty() {
                aliases.insert(0, subcommand.get_name());

                buffer.push_str(&format!(
                    "{:indent$}{{\n{:indent$}  name: [",
                    "",
                    "",
                    indent = indent + 2
                ));

                buffer.push_str(
                    &aliases
                        .iter()
                        .map(|name| format!("\"{}\"", name))
                        .collect::<Vec<_>>()
                        .join(", "),
                );

                buffer.push_str("],\n");
            } else {
                buffer.push_str(&format!(
                    "{:indent$}{{\n{:indent$}  name: \"{}\",\n",
                    "",
                    "",
                    subcommand.get_name(),
                    indent = indent + 2
                ));
            }

            if let Some(data) = subcommand.get_about() {
                buffer.push_str(&format!(
                    "{:indent$}description: \"{}\",\n",
                    "",
                    escape_string(data),
                    indent = indent + 4
                ));
            }

            if subcommand.is_hide_set() {
                buffer.push_str(&format!(
                    "{:indent$}hidden: true,\n",
                    "",
                    indent = indent + 4
                ))
            }

            let mut parent_commands: Vec<_> = parent_commands.into();
            parent_commands.push(subcommand.get_name());
            gen_fig_inner(
                root_command,
                &parent_commands,
                indent + 4,
                subcommand,
                buffer,
            );

            buffer.push_str(&format!("{:indent$}}},\n", "", indent = indent + 2));
        }
        buffer.push_str(&format!("{:indent$}],\n", "", indent = indent));
    }

    buffer.push_str(&gen_options(cmd, indent));

    let args = cmd.get_positionals().collect::<Vec<_>>();

    match args.len() {
        0 => {}
        1 => {
            buffer.push_str(&format!("{:indent$}args: ", "", indent = indent));

            buffer.push_str(&gen_args(args[0], indent));
        }
        _ => {
            buffer.push_str(&format!("{:indent$}args: [\n", "", indent = indent));
            for arg in args {
                buffer.push_str(&format!("{:indent$}", "", indent = indent + 2));
                buffer.push_str(&gen_args(arg, indent + 2));
            }
            buffer.push_str(&format!("{:indent$}]\n", "", indent = indent));
        }
    };
}

fn gen_options(cmd: &Command, indent: usize) -> String {
    let mut buffer = String::new();

    let flags = generator::utils::flags(cmd);

    if cmd.get_opts().next().is_some() || !flags.is_empty() {
        buffer.push_str(&format!("{:indent$}options: [\n", "", indent = indent));

        for option in cmd.get_opts() {
            buffer.push_str(&format!("{:indent$}{{\n", "", indent = indent + 2));

            let mut names = vec![];

            if let Some(shorts) = option.get_short_and_visible_aliases() {
                names.extend(shorts.iter().map(|short| format!("-{}", short)));
            }

            if let Some(longs) = option.get_long_and_visible_aliases() {
                names.extend(longs.iter().map(|long| format!("--{}", long)));
            }

            if names.len() > 1 {
                buffer.push_str(&format!("{:indent$}name: [", "", indent = indent + 4));

                buffer.push_str(
                    &names
                        .iter()
                        .map(|name| format!("\"{}\"", name))
                        .collect::<Vec<_>>()
                        .join(", "),
                );

                buffer.push_str("],\n");
            } else {
                buffer.push_str(&format!(
                    "{:indent$}name: \"{}\",\n",
                    "",
                    names[0],
                    indent = indent + 4
                ));
            }

            if let Some(data) = option.get_help() {
                buffer.push_str(&format!(
                    "{:indent$}description: \"{}\",\n",
                    "",
                    escape_string(data),
                    indent = indent + 4
                ));
            }

            if option.is_hide_set() {
                buffer.push_str(&format!(
                    "{:indent$}hidden: true,\n",
                    "",
                    indent = indent + 4
                ))
            }

            let conflicts = arg_conflicts(cmd, option);

            if !conflicts.is_empty() {
                buffer.push_str(&format!(
                    "{:indent$}exclusiveOn: [\n",
                    "",
                    indent = indent + 4
                ));

                for conflict in conflicts {
                    buffer.push_str(&format!(
                        "{:indent$}\"{}\",\n",
                        "",
                        conflict,
                        indent = indent + 6
                    ));
                }

                buffer.push_str(&format!("{:indent$}],\n", "", indent = indent + 4));
            }

            #[allow(deprecated)]
            if option.is_multiple_occurrences_set() {
                buffer.push_str(&format!(
                    "{:indent$}isRepeatable: true,\n",
                    "",
                    indent = indent + 4
                ));
            }

            if option.is_require_equals_set() {
                buffer.push_str(&format!(
                    "{:indent$}requiresEquals: true,\n",
                    "",
                    indent = indent + 4
                ));
            }

            buffer.push_str(&format!("{:indent$}args: ", "", indent = indent + 4));

            buffer.push_str(&gen_args(option, indent + 4));

            buffer.push_str(&format!("{:indent$}}},\n", "", indent = indent + 2));
        }

        for flag in generator::utils::flags(cmd) {
            buffer.push_str(&format!("{:indent$}{{\n", "", indent = indent + 2));

            let mut flags = vec![];

            if let Some(shorts) = flag.get_short_and_visible_aliases() {
                flags.extend(shorts.iter().map(|s| format!("-{}", s)));
            }

            if let Some(longs) = flag.get_long_and_visible_aliases() {
                flags.extend(longs.iter().map(|s| format!("--{}", s)));
            }

            if flags.len() > 1 {
                buffer.push_str(&format!("{:indent$}name: [", "", indent = indent + 4));

                buffer.push_str(
                    &flags
                        .iter()
                        .map(|name| format!("\"{}\"", name))
                        .collect::<Vec<_>>()
                        .join(", "),
                );

                buffer.push_str("],\n");
            } else {
                buffer.push_str(&format!(
                    "{:indent$}name: \"{}\",\n",
                    "",
                    flags[0],
                    indent = indent + 4
                ));
            }

            if let Some(data) = flag.get_help() {
                buffer.push_str(&format!(
                    "{:indent$}description: \"{}\",\n",
                    "",
                    escape_string(data).as_str(),
                    indent = indent + 4
                ));
            }

            let conflicts = arg_conflicts(cmd, &flag);

            if !conflicts.is_empty() {
                buffer.push_str(&format!(
                    "{:indent$}exclusiveOn: [\n",
                    "",
                    indent = indent + 4
                ));

                for conflict in conflicts {
                    buffer.push_str(&format!(
                        "{:indent$}\"{}\",\n",
                        "",
                        conflict,
                        indent = indent + 6
                    ));
                }

                buffer.push_str(&format!("{:indent$}],\n", "", indent = indent + 4));
            }

            #[allow(deprecated)]
            if flag.is_multiple_occurrences_set() {
                buffer.push_str(&format!(
                    "{:indent$}isRepeatable: true,\n",
                    "",
                    indent = indent + 4
                ));
            }

            buffer.push_str(&format!("{:indent$}}},\n", "", indent = indent + 2));
        }

        buffer.push_str(&format!("{:indent$}],\n", "", indent = indent));
    }

    buffer
}

fn gen_args(arg: &Arg, indent: usize) -> String {
    if !arg.is_takes_value_set() {
        return "".to_string();
    }

    let mut buffer = String::new();

    buffer.push_str(&format!(
        "{{\n{:indent$}  name: \"{}\",\n",
        "",
        arg.get_id(),
        indent = indent
    ));

    if arg.is_multiple_values_set() {
        buffer.push_str(&format!(
            "{:indent$}isVariadic: true,\n",
            "",
            indent = indent + 2
        ));
    }

    if !arg.is_required_set() {
        buffer.push_str(&format!(
            "{:indent$}isOptional: true,\n",
            "",
            indent = indent + 2
        ));
    }

    if let Some(data) = generator::utils::possible_values(arg) {
        buffer.push_str(&format!(
            "{:indent$}suggestions: [\n",
            "",
            indent = indent + 2
        ));

        for value in data {
            if let Some(help) = value.get_help() {
                buffer.push_str(&format!(
                    "{:indent$}{{\n{:indent$}  name: \"{}\",\n",
                    "",
                    "",
                    value.get_name(),
                    indent = indent + 4,
                ));

                buffer.push_str(&format!(
                    "{:indent$}description: \"{}\",\n",
                    "",
                    escape_string(help),
                    indent = indent + 6
                ));

                buffer.push_str(&format!("{:indent$}}},\n", "", indent = indent + 4));
            } else {
                buffer.push_str(&format!(
                    "{:indent$}\"{}\",\n",
                    "",
                    value.get_name(),
                    indent = indent + 4,
                ));
            }
        }

        buffer.push_str(&format!("{:indent$}],\n", "", indent = indent + 2));
    } else {
        match arg.get_value_hint() {
            ValueHint::AnyPath | ValueHint::FilePath | ValueHint::ExecutablePath => {
                buffer.push_str(&format!(
                    "{:indent$}template: \"filepaths\",\n",
                    "",
                    indent = indent + 2
                ));
            }
            ValueHint::DirPath => {
                buffer.push_str(&format!(
                    "{:indent$}template: \"folders\",\n",
                    "",
                    indent = indent + 2
                ));
            }
            ValueHint::CommandString | ValueHint::CommandName | ValueHint::CommandWithArguments => {
                buffer.push_str(&format!(
                    "{:indent$}isCommand: true,\n",
                    "",
                    indent = indent + 2
                ));
            }
            // Disable completion for others
            _ => (),
        };
    };

    buffer.push_str(&format!("{:indent$}}},\n", "", indent = indent));

    buffer
}

fn arg_conflicts(cmd: &Command, arg: &Arg) -> Vec<String> {
    let mut res = vec![];

    for conflict in cmd.get_arg_conflicts_with(arg) {
        if let Some(s) = conflict.get_short() {
            res.push(format!("-{}", s));
        }

        if let Some(l) = conflict.get_long() {
            res.push(format!("--{}", l));
        }
    }

    res
}
<commit_msg>feat(clap_complete_fig): Switch to using `ArgAction`<commit_after>// Std
use std::io::Write;

// Internal
use clap::*;
use clap_complete::*;

/// Generate fig completion file
pub struct Fig;

impl Generator for Fig {
    fn file_name(&self, name: &str) -> String {
        format!("{}.ts", name)
    }

    fn generate(&self, cmd: &Command, buf: &mut dyn Write) {
        let command = cmd.get_bin_name().unwrap();
        let mut buffer = String::new();

        buffer.push_str(&format!(
            "const completion: Fig.Spec = {{\n  name: \"{}\",\n",
            command
        ));

        buffer.push_str(&format!(
            "  description: \"{}\",\n",
            cmd.get_about().unwrap_or_default()
        ));

        gen_fig_inner(command, &[], 2, cmd, &mut buffer);

        buffer.push_str("};\n\nexport default completion;\n");

        buf.write_all(buffer.as_bytes())
            .expect("Failed to write to generated file");
    }
}

// Escape string inside double quotes
fn escape_string(string: &str) -> String {
    string.replace('\\', "\\\\").replace('\"', "\\\"")
}

fn gen_fig_inner(
    root_command: &str,
    parent_commands: &[&str],
    indent: usize,
    cmd: &Command,
    buffer: &mut String,
) {
    if cmd.has_subcommands() {
        buffer.push_str(&format!("{:indent$}subcommands: [\n", "", indent = indent));
        // generate subcommands
        for subcommand in cmd.get_subcommands() {
            let mut aliases: Vec<&str> = subcommand.get_all_aliases().collect();
            if !aliases.is_empty() {
                aliases.insert(0, subcommand.get_name());

                buffer.push_str(&format!(
                    "{:indent$}{{\n{:indent$}  name: [",
                    "",
                    "",
                    indent = indent + 2
                ));

                buffer.push_str(
                    &aliases
                        .iter()
                        .map(|name| format!("\"{}\"", name))
                        .collect::<Vec<_>>()
                        .join(", "),
                );

                buffer.push_str("],\n");
            } else {
                buffer.push_str(&format!(
                    "{:indent$}{{\n{:indent$}  name: \"{}\",\n",
                    "",
                    "",
                    subcommand.get_name(),
                    indent = indent + 2
                ));
            }

            if let Some(data) = subcommand.get_about() {
                buffer.push_str(&format!(
                    "{:indent$}description: \"{}\",\n",
                    "",
                    escape_string(data),
                    indent = indent + 4
                ));
            }

            if subcommand.is_hide_set() {
                buffer.push_str(&format!(
                    "{:indent$}hidden: true,\n",
                    "",
                    indent = indent + 4
                ))
            }

            let mut parent_commands: Vec<_> = parent_commands.into();
            parent_commands.push(subcommand.get_name());
            gen_fig_inner(
                root_command,
                &parent_commands,
                indent + 4,
                subcommand,
                buffer,
            );

            buffer.push_str(&format!("{:indent$}}},\n", "", indent = indent + 2));
        }
        buffer.push_str(&format!("{:indent$}],\n", "", indent = indent));
    }

    buffer.push_str(&gen_options(cmd, indent));

    let args = cmd.get_positionals().collect::<Vec<_>>();

    match args.len() {
        0 => {}
        1 => {
            buffer.push_str(&format!("{:indent$}args: ", "", indent = indent));

            buffer.push_str(&gen_args(args[0], indent));
        }
        _ => {
            buffer.push_str(&format!("{:indent$}args: [\n", "", indent = indent));
            for arg in args {
                buffer.push_str(&format!("{:indent$}", "", indent = indent + 2));
                buffer.push_str(&gen_args(arg, indent + 2));
            }
            buffer.push_str(&format!("{:indent$}]\n", "", indent = indent));
        }
    };
}

fn gen_options(cmd: &Command, indent: usize) -> String {
    let mut buffer = String::new();

    let flags = generator::utils::flags(cmd);

    if cmd.get_opts().next().is_some() || !flags.is_empty() {
        buffer.push_str(&format!("{:indent$}options: [\n", "", indent = indent));

        for option in cmd.get_opts() {
            buffer.push_str(&format!("{:indent$}{{\n", "", indent = indent + 2));

            let mut names = vec![];

            if let Some(shorts) = option.get_short_and_visible_aliases() {
                names.extend(shorts.iter().map(|short| format!("-{}", short)));
            }

            if let Some(longs) = option.get_long_and_visible_aliases() {
                names.extend(longs.iter().map(|long| format!("--{}", long)));
            }

            if names.len() > 1 {
                buffer.push_str(&format!("{:indent$}name: [", "", indent = indent + 4));

                buffer.push_str(
                    &names
                        .iter()
                        .map(|name| format!("\"{}\"", name))
                        .collect::<Vec<_>>()
                        .join(", "),
                );

                buffer.push_str("],\n");
            } else {
                buffer.push_str(&format!(
                    "{:indent$}name: \"{}\",\n",
                    "",
                    names[0],
                    indent = indent + 4
                ));
            }

            if let Some(data) = option.get_help() {
                buffer.push_str(&format!(
                    "{:indent$}description: \"{}\",\n",
                    "",
                    escape_string(data),
                    indent = indent + 4
                ));
            }

            if option.is_hide_set() {
                buffer.push_str(&format!(
                    "{:indent$}hidden: true,\n",
                    "",
                    indent = indent + 4
                ))
            }

            let conflicts = arg_conflicts(cmd, option);

            if !conflicts.is_empty() {
                buffer.push_str(&format!(
                    "{:indent$}exclusiveOn: [\n",
                    "",
                    indent = indent + 4
                ));

                for conflict in conflicts {
                    buffer.push_str(&format!(
                        "{:indent$}\"{}\",\n",
                        "",
                        conflict,
                        indent = indent + 6
                    ));
                }

                buffer.push_str(&format!("{:indent$}],\n", "", indent = indent + 4));
            }

            if let ArgAction::Set | ArgAction::Append | ArgAction::Count =
                option.get_action()
            {
                buffer.push_str(&format!(
                    "{:indent$}isRepeatable: true,\n",
                    "",
                    indent = indent + 4
                ));
            }

            if option.is_require_equals_set() {
                buffer.push_str(&format!(
                    "{:indent$}requiresEquals: true,\n",
                    "",
                    indent = indent + 4
                ));
            }

            buffer.push_str(&format!("{:indent$}args: ", "", indent = indent + 4));

            buffer.push_str(&gen_args(option, indent + 4));

            buffer.push_str(&format!("{:indent$}}},\n", "", indent = indent + 2));
        }

        for flag in generator::utils::flags(cmd) {
            buffer.push_str(&format!("{:indent$}{{\n", "", indent = indent + 2));

            let mut flags = vec![];

            if let Some(shorts) = flag.get_short_and_visible_aliases() {
                flags.extend(shorts.iter().map(|s| format!("-{}", s)));
            }

            if let Some(longs) = flag.get_long_and_visible_aliases() {
                flags.extend(longs.iter().map(|s| format!("--{}", s)));
            }

            if flags.len() > 1 {
                buffer.push_str(&format!("{:indent$}name: [", "", indent = indent + 4));

                buffer.push_str(
                    &flags
                        .iter()
                        .map(|name| format!("\"{}\"", name))
                        .collect::<Vec<_>>()
                        .join(", "),
                );

                buffer.push_str("],\n");
            } else {
                buffer.push_str(&format!(
                    "{:indent$}name: \"{}\",\n",
                    "",
                    flags[0],
                    indent = indent + 4
                ));
            }

            if let Some(data) = flag.get_help() {
                buffer.push_str(&format!(
                    "{:indent$}description: \"{}\",\n",
                    "",
                    escape_string(data).as_str(),
                    indent = indent + 4
                ));
            }

            let conflicts = arg_conflicts(cmd, &flag);

            if !conflicts.is_empty() {
                buffer.push_str(&format!(
                    "{:indent$}exclusiveOn: [\n",
                    "",
                    indent = indent + 4
                ));

                for conflict in conflicts {
                    buffer.push_str(&format!(
                        "{:indent$}\"{}\",\n",
                        "",
                        conflict,
                        indent = indent + 6
                    ));
                }

                buffer.push_str(&format!("{:indent$}],\n", "", indent = indent + 4));
            }

            if let ArgAction::Set | ArgAction::Append | ArgAction::Count =
                flag.get_action()
            {
                buffer.push_str(&format!(
                    "{:indent$}isRepeatable: true,\n",
                    "",
                    indent = indent + 4
                ));
            }

            buffer.push_str(&format!("{:indent$}}},\n", "", indent = indent + 2));
        }

        buffer.push_str(&format!("{:indent$}],\n", "", indent = indent));
    }

    buffer
}

fn gen_args(arg: &Arg, indent: usize) -> String {
    if !arg.is_takes_value_set() {
        return "".to_string();
    }

    let mut buffer = String::new();

    buffer.push_str(&format!(
        "{{\n{:indent$}  name: \"{}\",\n",
        "",
        arg.get_id(),
        indent = indent
    ));

    if arg.is_multiple_values_set() {
        buffer.push_str(&format!(
            "{:indent$}isVariadic: true,\n",
            "",
            indent = indent + 2
        ));
    }

    if !arg.is_required_set() {
        buffer.push_str(&format!(
            "{:indent$}isOptional: true,\n",
            "",
            indent = indent + 2
        ));
    }

    if let Some(data) = generator::utils::possible_values(arg) {
        buffer.push_str(&format!(
            "{:indent$}suggestions: [\n",
            "",
            indent = indent + 2
        ));

        for value in data {
            if let Some(help) = value.get_help() {
                buffer.push_str(&format!(
                    "{:indent$}{{\n{:indent$}  name: \"{}\",\n",
                    "",
                    "",
                    value.get_name(),
                    indent = indent + 4,
                ));

                buffer.push_str(&format!(
                    "{:indent$}description: \"{}\",\n",
                    "",
                    escape_string(help),
                    indent = indent + 6
                ));

                buffer.push_str(&format!("{:indent$}}},\n", "", indent = indent + 4));
            } else {
                buffer.push_str(&format!(
                    "{:indent$}\"{}\",\n",
                    "",
                    value.get_name(),
                    indent = indent + 4,
                ));
            }
        }

        buffer.push_str(&format!("{:indent$}],\n", "", indent = indent + 2));
    } else {
        match arg.get_value_hint() {
            ValueHint::AnyPath | ValueHint::FilePath | ValueHint::ExecutablePath => {
                buffer.push_str(&format!(
                    "{:indent$}template: \"filepaths\",\n",
                    "",
                    indent = indent + 2
                ));
            }
            ValueHint::DirPath => {
                buffer.push_str(&format!(
                    "{:indent$}template: \"folders\",\n",
                    "",
                    indent = indent + 2
                ));
            }
            ValueHint::CommandString | ValueHint::CommandName | ValueHint::CommandWithArguments => {
                buffer.push_str(&format!(
                    "{:indent$}isCommand: true,\n",
                    "",
                    indent = indent + 2
                ));
            }
            // Disable completion for others
            _ => (),
        };
    };

    buffer.push_str(&format!("{:indent$}}},\n", "", indent = indent));

    buffer
}

fn arg_conflicts(cmd: &Command, arg: &Arg) -> Vec<String> {
    let mut res = vec![];

    for conflict in cmd.get_arg_conflicts_with(arg) {
        if let Some(s) = conflict.get_short() {
            res.push(format!("-{}", s));
        }

        if let Some(l) = conflict.get_long() {
            res.push(format!("--{}", l));
        }
    }

    res
}
<|endoftext|>


<commit_before>use self::config::Config;
use self::results::Results;
use crate::beacon_chain_harness::BeaconChainHarness;
use beacon_chain::CheckPoint;
use log::{info, warn};
use types::*;
use types::{
    attester_slashing::AttesterSlashingBuilder, proposer_slashing::ProposerSlashingBuilder,
};
use yaml_rust::Yaml;

mod config;
mod results;
mod state_check;
mod yaml_helpers;

#[derive(Debug)]
pub struct Manifest {
    pub results: Results,
    pub config: Config,
}

pub struct ExecutionResult {
    pub chain: Vec<CheckPoint>,
    pub spec: ChainSpec,
}

impl Manifest {
    pub fn from_yaml(test_case: &Yaml) -> Self {
        Self {
            results: Results::from_yaml(&test_case["results"]),
            config: Config::from_yaml(&test_case["config"]),
        }
    }

    fn spec(&self) -> ChainSpec {
        let mut spec = ChainSpec::foundation();

        if let Some(n) = self.config.epoch_length {
            spec.epoch_length = n;
        }

        spec
    }

    pub fn execute(&self) -> ExecutionResult {
        let spec = self.spec();
        let validator_count = self.config.deposits_for_chain_start;
        let slots = self.config.num_slots;

        info!(
            "Building BeaconChainHarness with {} validators...",
            validator_count
        );

        let mut harness = BeaconChainHarness::new(spec, validator_count);

        info!("Starting simulation across {} slots...", slots);

        // -1 slots because genesis counts as a slot.
        for slot_height in 0..slots - 1 {
            // Feed deposits to the BeaconChain.
            if let Some(ref deposits) = self.config.deposits {
                for (slot, deposit, keypair) in deposits {
                    if *slot == slot_height {
                        info!("Including deposit at slot height {}.", slot_height);
                        harness.add_deposit(deposit.clone(), Some(keypair.clone()));
                    }
                }
            }

            // Feed proposer slashings to the BeaconChain.
            if let Some(ref slashings) = self.config.proposer_slashings {
                for (slot, validator_index) in slashings {
                    if *slot == slot_height {
                        info!(
                            "Including proposer slashing at slot height {} for validator #{}.",
                            slot_height, validator_index
                        );
                        let slashing = build_proposer_slashing(&harness, *validator_index);
                        harness.add_proposer_slashing(slashing);
                    }
                }
            }

            // Feed attester slashings to the BeaconChain.
            if let Some(ref slashings) = self.config.attester_slashings {
                for (slot, validator_indices) in slashings {
                    if *slot == slot_height {
                        info!(
                            "Including attester slashing at slot height {} for validators {:?}.",
                            slot_height, validator_indices
                        );
                        let slashing =
                            build_double_vote_attester_slashing(&harness, &validator_indices[..]);
                        harness.add_attester_slashing(slashing);
                    }
                }
            }

            // Build a block or skip a slot.
            match self.config.skip_slots {
                Some(ref skip_slots) if skip_slots.contains(&slot_height) => {
                    warn!("Skipping slot at height {}.", slot_height);
                    harness.increment_beacon_chain_slot();
                }
                _ => {
                    info!("Producing block at slot height {}.", slot_height);
                    harness.advance_chain_with_block();
                }
            }
        }

        harness.run_fork_choice();

        info!("Test execution complete!");

        ExecutionResult {
            chain: harness.chain_dump().expect("Chain dump failed."),
            spec: (*harness.spec).clone(),
        }
    }

    pub fn assert_result_valid(&self, execution_result: ExecutionResult) {
        info!("Verifying test results...");
        let spec = &execution_result.spec;

        if let Some(num_skipped_slots) = self.results.num_skipped_slots {
            assert_eq!(
                execution_result.chain.len(),
                self.config.num_slots as usize - num_skipped_slots,
                "actual skipped slots != expected."
            );
            info!(
                "OK: Chain length is {} ({} skipped slots).",
                execution_result.chain.len(),
                num_skipped_slots
            );
        }

        if let Some(ref state_checks) = self.results.state_checks {
            for checkpoint in &execution_result.chain {
                let state = &checkpoint.beacon_state;

                for state_check in state_checks {
                    let adjusted_state_slot =
                        state.slot - spec.genesis_epoch.start_slot(spec.epoch_length);

                    if state_check.slot == adjusted_state_slot {
                        state_check.assert_valid(state, spec);
                    }
                }
            }
        }
    }
}

fn build_double_vote_attester_slashing(
    harness: &BeaconChainHarness,
    validator_indices: &[u64],
) -> AttesterSlashing {
    let signer = |validator_index: u64, message: &[u8], epoch: Epoch, domain: u64| {
        harness
            .validator_sign(validator_index as usize, message, epoch, domain)
            .expect("Unable to sign AttesterSlashing")
    };

    AttesterSlashingBuilder::double_vote(validator_indices, signer, &harness.spec)
}

fn build_proposer_slashing(harness: &BeaconChainHarness, validator_index: u64) -> ProposerSlashing {
    let signer = |validator_index: u64, message: &[u8], epoch: Epoch, domain: u64| {
        harness
            .validator_sign(validator_index as usize, message, epoch, domain)
            .expect("Unable to sign AttesterSlashing")
    };

    ProposerSlashingBuilder::double_vote(validator_index, signer, &harness.spec)
}
<commit_msg>Add info log when building test_harness chain dump<commit_after>use self::config::Config;
use self::results::Results;
use crate::beacon_chain_harness::BeaconChainHarness;
use beacon_chain::CheckPoint;
use log::{info, warn};
use types::*;
use types::{
    attester_slashing::AttesterSlashingBuilder, proposer_slashing::ProposerSlashingBuilder,
};
use yaml_rust::Yaml;

mod config;
mod results;
mod state_check;
mod yaml_helpers;

#[derive(Debug)]
pub struct Manifest {
    pub results: Results,
    pub config: Config,
}

pub struct ExecutionResult {
    pub chain: Vec<CheckPoint>,
    pub spec: ChainSpec,
}

impl Manifest {
    pub fn from_yaml(test_case: &Yaml) -> Self {
        Self {
            results: Results::from_yaml(&test_case["results"]),
            config: Config::from_yaml(&test_case["config"]),
        }
    }

    fn spec(&self) -> ChainSpec {
        let mut spec = ChainSpec::foundation();

        if let Some(n) = self.config.epoch_length {
            spec.epoch_length = n;
        }

        spec
    }

    pub fn execute(&self) -> ExecutionResult {
        let spec = self.spec();
        let validator_count = self.config.deposits_for_chain_start;
        let slots = self.config.num_slots;

        info!(
            "Building BeaconChainHarness with {} validators...",
            validator_count
        );

        let mut harness = BeaconChainHarness::new(spec, validator_count);

        info!("Starting simulation across {} slots...", slots);

        // -1 slots because genesis counts as a slot.
        for slot_height in 0..slots - 1 {
            // Feed deposits to the BeaconChain.
            if let Some(ref deposits) = self.config.deposits {
                for (slot, deposit, keypair) in deposits {
                    if *slot == slot_height {
                        info!("Including deposit at slot height {}.", slot_height);
                        harness.add_deposit(deposit.clone(), Some(keypair.clone()));
                    }
                }
            }

            // Feed proposer slashings to the BeaconChain.
            if let Some(ref slashings) = self.config.proposer_slashings {
                for (slot, validator_index) in slashings {
                    if *slot == slot_height {
                        info!(
                            "Including proposer slashing at slot height {} for validator #{}.",
                            slot_height, validator_index
                        );
                        let slashing = build_proposer_slashing(&harness, *validator_index);
                        harness.add_proposer_slashing(slashing);
                    }
                }
            }

            // Feed attester slashings to the BeaconChain.
            if let Some(ref slashings) = self.config.attester_slashings {
                for (slot, validator_indices) in slashings {
                    if *slot == slot_height {
                        info!(
                            "Including attester slashing at slot height {} for validators {:?}.",
                            slot_height, validator_indices
                        );
                        let slashing =
                            build_double_vote_attester_slashing(&harness, &validator_indices[..]);
                        harness.add_attester_slashing(slashing);
                    }
                }
            }

            // Build a block or skip a slot.
            match self.config.skip_slots {
                Some(ref skip_slots) if skip_slots.contains(&slot_height) => {
                    warn!("Skipping slot at height {}.", slot_height);
                    harness.increment_beacon_chain_slot();
                }
                _ => {
                    info!("Producing block at slot height {}.", slot_height);
                    harness.advance_chain_with_block();
                }
            }
        }

        harness.run_fork_choice();

        info!("Test execution complete!");

        info!("Building chain dump for analysis...");

        ExecutionResult {
            chain: harness.chain_dump().expect("Chain dump failed."),
            spec: (*harness.spec).clone(),
        }
    }

    pub fn assert_result_valid(&self, execution_result: ExecutionResult) {
        info!("Verifying test results...");
        let spec = &execution_result.spec;

        if let Some(num_skipped_slots) = self.results.num_skipped_slots {
            assert_eq!(
                execution_result.chain.len(),
                self.config.num_slots as usize - num_skipped_slots,
                "actual skipped slots != expected."
            );
            info!(
                "OK: Chain length is {} ({} skipped slots).",
                execution_result.chain.len(),
                num_skipped_slots
            );
        }

        if let Some(ref state_checks) = self.results.state_checks {
            for checkpoint in &execution_result.chain {
                let state = &checkpoint.beacon_state;

                for state_check in state_checks {
                    let adjusted_state_slot =
                        state.slot - spec.genesis_epoch.start_slot(spec.epoch_length);

                    if state_check.slot == adjusted_state_slot {
                        state_check.assert_valid(state, spec);
                    }
                }
            }
        }
    }
}

fn build_double_vote_attester_slashing(
    harness: &BeaconChainHarness,
    validator_indices: &[u64],
) -> AttesterSlashing {
    let signer = |validator_index: u64, message: &[u8], epoch: Epoch, domain: u64| {
        harness
            .validator_sign(validator_index as usize, message, epoch, domain)
            .expect("Unable to sign AttesterSlashing")
    };

    AttesterSlashingBuilder::double_vote(validator_indices, signer, &harness.spec)
}

fn build_proposer_slashing(harness: &BeaconChainHarness, validator_index: u64) -> ProposerSlashing {
    let signer = |validator_index: u64, message: &[u8], epoch: Epoch, domain: u64| {
        harness
            .validator_sign(validator_index as usize, message, epoch, domain)
            .expect("Unable to sign AttesterSlashing")
    };

    ProposerSlashingBuilder::double_vote(validator_index, signer, &harness.spec)
}
<|endoftext|>


<commit_before>//! An adapter providing time-related services, such as the current
//! timestamp or the current time of day.

use foxbox_taxonomy::api::{ Error, InternalError, Operation, User };
use foxbox_taxonomy::channel::*;
use foxbox_taxonomy::manager::*;
use foxbox_taxonomy::services::*;
use foxbox_taxonomy::values::{ Duration as ValDuration, Range, TimeStamp, Type, Value };

use transformable_channels::mpsc::*;

use std::collections::HashMap;
use std::sync::{ Arc, Mutex };

use chrono;
use chrono::*;
use timer;

static ADAPTER_NAME: &'static str = "Clock adapter (built-in)";
static ADAPTER_VENDOR: &'static str = "team@link.mozilla.org";
static ADAPTER_VERSION: [u32;4] = [0, 0, 0, 0];

#[derive(Clone)]
enum Op {
    Enter(Id<Channel>, Value),
    Exit(Id<Channel>, Value),
}

enum Movement { Enter, Exit }

pub struct Clock {
    /// Timer used to dispatch `register_watch` requests.
    timer: Mutex<timer::Timer>,

    getter_timestamp_id: Id<Channel>,
    getter_time_of_day_id: Id<Channel>,
    getter_interval_id: Id<Channel>,
}

/// A guard used to cancel watching for values.
struct Guard(Vec<timer::Guard>);
impl AdapterWatchGuard for Guard {
}

impl Clock {
    pub fn id() -> Id<AdapterId> {
        Id::new("clock@link.mozilla.org")
    }
    pub fn service_clock_id() -> Id<ServiceId> {
        Id::new("service:clock@link.mozilla.org")
    }
    pub fn getter_timestamp_id() -> Id<Channel> {
        Id::new("getter:timestamp.clock@link.mozilla.org")
    }
    pub fn getter_time_of_day_id() -> Id<Channel> {
        Id::new("getter:timeofday.clock@link.mozilla.org")
    }
    pub fn getter_interval_id() -> Id<Channel> {
        Id::new("getter:interval.clock@link.mozilla.org")
    }
}
impl Adapter for Clock {
    fn id(&self) -> Id<AdapterId> {
        Self::id()
    }

    fn name(&self) -> &str {
        ADAPTER_NAME
    }

    fn vendor(&self) -> &str {
        ADAPTER_VENDOR
    }

    fn version(&self) -> &[u32;4] {
        &ADAPTER_VERSION
    }

    fn fetch_values(&self, mut set: Vec<Id<Channel>>, _: User) -> ResultMap<Id<Channel>, Option<Value>, Error> {
        set.drain(..).map(|id| {
            if id == self.getter_timestamp_id {
                let date = TimeStamp::from_datetime(chrono::UTC::now());
                (id, Ok(Some(Value::TimeStamp(date))))
            } else if id == self.getter_time_of_day_id {
                use chrono::Timelike;
                let date = chrono::Local::now();
                let duration = chrono::Duration::seconds(date.num_seconds_from_midnight() as i64);
                (id, Ok(Some(Value::Duration(ValDuration::from(duration)))))
            } else {
                (id.clone(), Err(Error::InternalError(InternalError::NoSuchChannel(id))))
            }
        }).collect()
    }

    fn send_values(&self, mut values: HashMap<Id<Channel>, Value>, _: User) -> ResultMap<Id<Channel>, (), Error> {
        values.drain()
            .map(|(id, _)| {
                (id.clone(), Err(Error::InternalError(InternalError::NoSuchChannel(id))))
            })
            .collect()
    }

    fn register_watch(&self, mut watch: Vec<WatchTarget>) -> WatchResult
    {
        watch.drain(..).map(|(id, filter, tx)| {
            let tx = tx.map(|msg| {
                match msg {
                    Op::Enter(id, value) => {
                        WatchEvent::Enter {
                            id: id,
                            value: value
                        }
                    },
                    Op::Exit(id, value) => {
                        WatchEvent::Exit {
                            id: id,
                            value: value
                        }
                    },
                }
            });
            (id.clone(), match filter {
                Some(Value::Range(range)) => self.aux_register_watch(&id, &*range, Box::new(tx.clone())),
                _ => Err(Error::GetterRequiresThresholdForWatching(id)),
            })
        }).collect()
    }
}

impl Clock {
    fn aux_register_watch(&self, id: &Id<Channel>, range: &Range, tx: Box<ExtSender<Op>>)
        -> Result<Box<AdapterWatchGuard>, Error>
    {
        match () {
            _ if *id == self.getter_time_of_day_id => self.aux_register_watch_timeofday(id, range, tx),
            _ if *id == self.getter_timestamp_id => self.aux_register_watch_timestamp(id, range, tx),
            _ if *id == self.getter_interval_id => self.aux_register_watch_interval(id, range, tx),
            _ => Err(Error::OperationNotSupported(Operation::Watch, id.clone()))
        }
    }

    fn aux_register_watch_interval(&self, id: &Id<Channel>, range: &Range, tx: Box<ExtSender<Op>>)
        -> Result<Box<AdapterWatchGuard>, Error>
    {
        use foxbox_taxonomy::values::Range::*;

        // Determine when to call the trigger.
        let duration = match *range {
            Eq (ref val) | Geq (ref val) => {
                try!(val.as_duration().map_err(Error::TypeError))
                    .clone().into()
            }
            _ => return Err(Error::InvalidValue)
        };

        debug!(target: "clock@link.mozilla.org", "[clock@link.mozilla.org] Scheduling a repeating watch with a duration of {}", duration);

        let id = id.clone();
        let guard = self.timer.lock().unwrap().schedule_repeating(duration, move || {
            // Send Enter followed immediately by Exit, to make sure that Thinkerbell
            // rules reset themselves.
            let _ = tx.send(Op::Enter(id.clone(),
                Value::Duration(ValDuration::from(duration))));
            let _ = tx.send(Op::Exit(id.clone(),
                Value::Duration(ValDuration::from(duration))));
        });
        Ok(Box::new(Guard(vec![guard])))
    }

    fn aux_register_watch_timeofday(&self, id: &Id<Channel>, range: &Range, tx: Box<ExtSender<Op>>)
        -> Result<Box<AdapterWatchGuard>, Error>
    {
        use foxbox_taxonomy::values::Range::*;

        // Determine when to call the trigger. Repeat duration is always one day.
        let mut thresholds = match *range {
            Leq (ref val) => {
                // Equivalent to BetweenEq { min: 0am, max: val }
                let ts : chrono::Duration = try!(val.as_duration().map_err(Error::TypeError))
                    .clone().into();
                vec![(Movement::Enter, chrono::Duration::seconds(0)), (Movement::Exit, ts)]
            }
            Geq (ref val) => {
                // Equivalent to BetweenEq { min: val, max: 0am }
                let ts = try!(val.as_duration().map_err(Error::TypeError))
                    .clone().into();
                vec![(Movement::Enter, ts), (Movement::Exit, Duration::days(1))]
            }
            BetweenEq { ref min, ref max } => {
                let ts_min = try!(min.as_duration().map_err(Error::TypeError))
                    .clone().into();
                let ts_max = try!(max.as_duration().map_err(Error::TypeError))
                    .clone().into();
                vec![(Movement::Enter, ts_min), (Movement::Exit, ts_max)]
            }
            OutOfStrict { ref min, ref max } => {
                // Equivalent to BetweenEq {min: 0am, max: min} and BetweenEq {min: max, max: 0am}
                let ts_min = try!(min.as_duration().map_err(Error::TypeError))
                    .clone().into();
                let ts_max = try!(max.as_duration().map_err(Error::TypeError))
                    .clone().into();
                vec![(Movement::Exit, ts_min), (Movement::Enter, ts_max)]
            }
            Eq (ref val) => {
                let ts : chrono::Duration = try!(val.as_duration().map_err(Error::TypeError))
                    .clone().into();
                vec![(Movement::Enter, ts.clone()), (Movement::Exit, ts)]
            }
        };

        // Determine when the next timers needs to launch.
        let now = chrono::Local::now();
        let guards : Vec<timer::Guard> = thresholds.drain(..).filter_map(|(movement, threshold)| {
            let date = match Self::get_next_date(&now, threshold) {
                Err(_) => return None,
                Ok(date) => date,
            };
            let id = id.clone();
            let tx = tx.clone();
            let guard = self.timer.lock().unwrap().schedule(date, Some(Duration::days(1)), move || {
                let naive_time = chrono::Local::now().time();
                let duration = Duration::hours(naive_time.hour() as i64)
                    + Duration::minutes(naive_time.minute() as i64)
                    + Duration::seconds(naive_time.second() as i64);

                let event = match movement {
                    Movement::Enter => Op::Enter(id.clone(),
                        Value::Duration(ValDuration::from(duration))),
                    Movement::Exit => Op::Exit(id.clone(),
                        Value::Duration(ValDuration::from(duration))),
                };
                let _ = tx.send(event);
            });
            Some(guard)
        }).collect();
        Ok(Box::new(Guard(guards)))
    }

    fn get_next_date(now: &DateTime<Local>, time_of_day: Duration)
        -> Result<DateTime<Local>, Error>
    {
        match chrono::Local::today().and_time(NaiveTime::from_hms(0, 0, 0) + time_of_day) {
            None => Err(Error::InvalidValue),
            Some(date) => {
                if date >= *now  {
                    Ok(date)
                } else {
                    // Otherwise, shift to tomorrow.
                    match date.checked_add(Duration::days(1)) {
                        None => Err(Error::InvalidValue),
                        Some(date) => Ok(date)
                    }
                }
            }
        }
    }

    fn aux_register_watch_timestamp(&self, id: &Id<Channel>, range: &Range, tx: Box<ExtSender<Op>>)
        -> Result<Box<AdapterWatchGuard>, Error>
    {
        use foxbox_taxonomy::values::Range::*;

        // Now determine when/if to call the trigger.
        let mut thresholds = match *range {
            Leq (_) => {
                // This variant doesn't make sense.
                return Ok(Box::new(Guard(vec![])))
            }
            Geq (ref val) | Eq (ref val) => {
                let ts = *try!(val.as_timestamp().map_err(Error::TypeError))
                    .as_datetime();
                vec![(Movement::Enter, ts)]
            }
            OutOfStrict { ref min, ref max } => {
                let ts_min = *try!(min.as_timestamp().map_err(Error::TypeError))
                    .as_datetime();
                let ts_max = *try!(max.as_timestamp().map_err(Error::TypeError))
                    .as_datetime();
                vec![(Movement::Exit, ts_min), (Movement::Enter, ts_max)]
            }
            BetweenEq { ref min, ref max } => {
                let ts_min = *try!(min.as_timestamp().map_err(Error::TypeError))
                    .as_datetime();
                let ts_max = *try!(max.as_timestamp().map_err(Error::TypeError))
                    .as_datetime();
                vec![(Movement::Enter, ts_min), (Movement::Exit, ts_max)]
            }
        };

        // Determine when/if the next timers needs to launch.
        let now = chrono::UTC::now();
        let guards : Vec<timer::Guard> = thresholds.drain(..).filter_map(|(movement, date)| {
            if date < now {
                return None
            }
            let id = id.clone();
            let tx = tx.clone();
            let guard = self.timer.lock().unwrap().schedule_with_date(date, move || {
                let naive_time = chrono::Local::now().time();
                let duration = Duration::hours(naive_time.hour() as i64)
                    + Duration::minutes(naive_time.minute() as i64)
                    + Duration::seconds(naive_time.second() as i64);

                let event = match movement {
                    Movement::Enter => Op::Enter(id.clone(),
                        Value::Duration(ValDuration::from(duration))),
                    Movement::Exit => Op::Exit(id.clone(),
                        Value::Duration(ValDuration::from(duration))),
                };
                let _ = tx.send(event);
            });
            Some(guard)
        }).collect();
        Ok(Box::new(Guard(guards)))
    }
}

impl Clock {
    pub fn init(adapt: &Arc<AdapterManager>) -> Result<(), Error> {
        let getter_timestamp_id = Clock::getter_timestamp_id();
        let getter_time_of_day_id = Clock::getter_time_of_day_id();
        let getter_interval_id = Clock::getter_interval_id();
        let service_clock_id = Clock::service_clock_id();
        let adapter_id = Clock::id();
        let clock = Arc::new(Clock {
            timer: Mutex::new(timer::Timer::new()),
            getter_timestamp_id: getter_timestamp_id.clone(),
            getter_time_of_day_id: getter_time_of_day_id.clone(),
            getter_interval_id: getter_interval_id.clone(),
        });
        try!(adapt.add_adapter(clock));
        let mut service = Service::empty(&service_clock_id, &adapter_id);
        service.properties.insert("model".to_owned(), "Mozilla clock v1".to_owned());
        try!(adapt.add_service(service));
        try!(adapt.add_channel(Channel {
            feature: Id::new("clock/time-of-day-seconds"),
            supports_fetch: Some(Signature::returns(Maybe::Required(Type::Duration))),
            supports_watch: Some(Signature {
                accepts: Maybe::Required(Type::Duration),
                returns: Maybe::Required(Type::Duration)
            }),
            id: getter_time_of_day_id,
            service: service_clock_id.clone(),
            adapter: adapter_id.clone(),
            ..Channel::default()
        }));
        try!(adapt.add_channel(Channel {
            feature: Id::new("clock/time-timestamp-rfc-3339"),
            supports_fetch: Some(Signature::returns(Maybe::Required(Type::TimeStamp))),
            supports_watch: Some(Signature {
                accepts: Maybe::Required(Type::TimeStamp),
                returns: Maybe::Required(Type::TimeStamp)
            }),
            id: getter_timestamp_id,
            service: service_clock_id.clone(),
            adapter: adapter_id.clone(),
            ..Channel::default()
        }));
        try!(adapt.add_channel(Channel {
            feature: Id::new("clock/time-interval-seconds"),
            supports_watch: Some(Signature {
                accepts: Maybe::Required(Type::Duration),
                returns: Maybe::Required(Type::TimeStamp)
            }),
            id: getter_interval_id,
            service: service_clock_id.clone(),
            adapter: adapter_id.clone(),
            ..Channel::default()
        }));
        Ok(())
    }
}
<commit_msg>Clock: Getting rid of centralized Type<commit_after>//! An adapter providing time-related services, such as the current
//! timestamp or the current time of day.

use foxbox_taxonomy::api::{ Error, InternalError, Operation, User };
use foxbox_taxonomy::channel::*;
use foxbox_taxonomy::manager::*;
use foxbox_taxonomy::services::*;
use foxbox_taxonomy::values::{ format, Duration as ValDuration, Range, TimeStamp, Value };

use transformable_channels::mpsc::*;

use std::collections::HashMap;
use std::sync::{ Arc, Mutex };

use chrono;
use chrono::{ DateTime, Duration, Local, NaiveTime, Timelike };
use timer;

static ADAPTER_NAME: &'static str = "Clock adapter (built-in)";
static ADAPTER_VENDOR: &'static str = "team@link.mozilla.org";
static ADAPTER_VERSION: [u32;4] = [0, 0, 0, 0];

#[derive(Clone)]
enum Op {
    Enter(Id<Channel>, Value),
    Exit(Id<Channel>, Value),
}

enum Movement { Enter, Exit }

pub struct Clock {
    /// Timer used to dispatch `register_watch` requests.
    timer: Mutex<timer::Timer>,

    getter_timestamp_id: Id<Channel>,
    getter_time_of_day_id: Id<Channel>,
    getter_interval_id: Id<Channel>,
}

/// A guard used to cancel watching for values.
struct Guard(Vec<timer::Guard>);
impl AdapterWatchGuard for Guard {
}

impl Clock {
    pub fn id() -> Id<AdapterId> {
        Id::new("clock@link.mozilla.org")
    }
    pub fn service_clock_id() -> Id<ServiceId> {
        Id::new("service:clock@link.mozilla.org")
    }
    pub fn getter_timestamp_id() -> Id<Channel> {
        Id::new("getter:timestamp.clock@link.mozilla.org")
    }
    pub fn getter_time_of_day_id() -> Id<Channel> {
        Id::new("getter:timeofday.clock@link.mozilla.org")
    }
    pub fn getter_interval_id() -> Id<Channel> {
        Id::new("getter:interval.clock@link.mozilla.org")
    }
}
impl Adapter for Clock {
    fn id(&self) -> Id<AdapterId> {
        Self::id()
    }

    fn name(&self) -> &str {
        ADAPTER_NAME
    }

    fn vendor(&self) -> &str {
        ADAPTER_VENDOR
    }

    fn version(&self) -> &[u32;4] {
        &ADAPTER_VERSION
    }

    fn fetch_values(&self, mut set: Vec<Id<Channel>>, _: User) -> ResultMap<Id<Channel>, Option<Value>, Error> {
        set.drain(..).map(|id| {
            if id == self.getter_timestamp_id {
                let date = TimeStamp::from_datetime(chrono::UTC::now());
                (id, Ok(Some(Value::TimeStamp(date))))
            } else if id == self.getter_time_of_day_id {
                use chrono::Timelike;
                let date = chrono::Local::now();
                let duration = chrono::Duration::seconds(date.num_seconds_from_midnight() as i64);
                (id, Ok(Some(Value::Duration(ValDuration::from(duration)))))
            } else {
                (id.clone(), Err(Error::InternalError(InternalError::NoSuchChannel(id))))
            }
        }).collect()
    }

    fn send_values(&self, mut values: HashMap<Id<Channel>, Value>, _: User) -> ResultMap<Id<Channel>, (), Error> {
        values.drain()
            .map(|(id, _)| {
                (id.clone(), Err(Error::InternalError(InternalError::NoSuchChannel(id))))
            })
            .collect()
    }

    fn register_watch(&self, mut watch: Vec<WatchTarget>) -> WatchResult
    {
        watch.drain(..).map(|(id, filter, tx)| {
            let tx = tx.map(|msg| {
                match msg {
                    Op::Enter(id, value) => {
                        WatchEvent::Enter {
                            id: id,
                            value: value
                        }
                    },
                    Op::Exit(id, value) => {
                        WatchEvent::Exit {
                            id: id,
                            value: value
                        }
                    },
                }
            });
            (id.clone(), match filter {
                Some(Value::Range(range)) => self.aux_register_watch(&id, &*range, Box::new(tx.clone())),
                _ => Err(Error::GetterRequiresThresholdForWatching(id)),
            })
        }).collect()
    }
}

impl Clock {
    fn aux_register_watch(&self, id: &Id<Channel>, range: &Range, tx: Box<ExtSender<Op>>)
        -> Result<Box<AdapterWatchGuard>, Error>
    {
        match () {
            _ if *id == self.getter_time_of_day_id => self.aux_register_watch_timeofday(id, range, tx),
            _ if *id == self.getter_timestamp_id => self.aux_register_watch_timestamp(id, range, tx),
            _ if *id == self.getter_interval_id => self.aux_register_watch_interval(id, range, tx),
            _ => Err(Error::OperationNotSupported(Operation::Watch, id.clone()))
        }
    }

    fn aux_register_watch_interval(&self, id: &Id<Channel>, range: &Range, tx: Box<ExtSender<Op>>)
        -> Result<Box<AdapterWatchGuard>, Error>
    {
        use foxbox_taxonomy::values::Range::*;

        // Determine when to call the trigger.
        let duration = match *range {
            Eq (ref val) | Geq (ref val) => {
                try!(val.as_duration().map_err(Error::TypeError))
                    .clone().into()
            }
            _ => return Err(Error::InvalidValue)
        };

        debug!(target: "clock@link.mozilla.org", "[clock@link.mozilla.org] Scheduling a repeating watch with a duration of {}", duration);

        let id = id.clone();
        let guard = self.timer.lock().unwrap().schedule_repeating(duration, move || {
            // Send Enter followed immediately by Exit, to make sure that Thinkerbell
            // rules reset themselves.
            let _ = tx.send(Op::Enter(id.clone(),
                Value::Duration(ValDuration::from(duration))));
            let _ = tx.send(Op::Exit(id.clone(),
                Value::Duration(ValDuration::from(duration))));
        });
        Ok(Box::new(Guard(vec![guard])))
    }

    fn aux_register_watch_timeofday(&self, id: &Id<Channel>, range: &Range, tx: Box<ExtSender<Op>>)
        -> Result<Box<AdapterWatchGuard>, Error>
    {
        use foxbox_taxonomy::values::Range::*;

        // Determine when to call the trigger. Repeat duration is always one day.
        let mut thresholds = match *range {
            Leq (ref val) => {
                // Equivalent to BetweenEq { min: 0am, max: val }
                let ts : chrono::Duration = try!(val.as_duration().map_err(Error::TypeError))
                    .clone().into();
                vec![(Movement::Enter, chrono::Duration::seconds(0)), (Movement::Exit, ts)]
            }
            Geq (ref val) => {
                // Equivalent to BetweenEq { min: val, max: 0am }
                let ts = try!(val.as_duration().map_err(Error::TypeError))
                    .clone().into();
                vec![(Movement::Enter, ts), (Movement::Exit, Duration::days(1))]
            }
            BetweenEq { ref min, ref max } => {
                let ts_min = try!(min.as_duration().map_err(Error::TypeError))
                    .clone().into();
                let ts_max = try!(max.as_duration().map_err(Error::TypeError))
                    .clone().into();
                vec![(Movement::Enter, ts_min), (Movement::Exit, ts_max)]
            }
            OutOfStrict { ref min, ref max } => {
                // Equivalent to BetweenEq {min: 0am, max: min} and BetweenEq {min: max, max: 0am}
                let ts_min = try!(min.as_duration().map_err(Error::TypeError))
                    .clone().into();
                let ts_max = try!(max.as_duration().map_err(Error::TypeError))
                    .clone().into();
                vec![(Movement::Exit, ts_min), (Movement::Enter, ts_max)]
            }
            Eq (ref val) => {
                let ts : chrono::Duration = try!(val.as_duration().map_err(Error::TypeError))
                    .clone().into();
                vec![(Movement::Enter, ts.clone()), (Movement::Exit, ts)]
            }
        };

        // Determine when the next timers needs to launch.
        let now = chrono::Local::now();
        let guards : Vec<timer::Guard> = thresholds.drain(..).filter_map(|(movement, threshold)| {
            let date = match Self::get_next_date(&now, threshold) {
                Err(_) => return None,
                Ok(date) => date,
            };
            let id = id.clone();
            let tx = tx.clone();
            let guard = self.timer.lock().unwrap().schedule(date, Some(Duration::days(1)), move || {
                let naive_time = chrono::Local::now().time();
                let duration = Duration::hours(naive_time.hour() as i64)
                    + Duration::minutes(naive_time.minute() as i64)
                    + Duration::seconds(naive_time.second() as i64);

                let event = match movement {
                    Movement::Enter => Op::Enter(id.clone(),
                        Value::Duration(ValDuration::from(duration))),
                    Movement::Exit => Op::Exit(id.clone(),
                        Value::Duration(ValDuration::from(duration))),
                };
                let _ = tx.send(event);
            });
            Some(guard)
        }).collect();
        Ok(Box::new(Guard(guards)))
    }

    fn get_next_date(now: &DateTime<Local>, time_of_day: Duration)
        -> Result<DateTime<Local>, Error>
    {
        match chrono::Local::today().and_time(NaiveTime::from_hms(0, 0, 0) + time_of_day) {
            None => Err(Error::InvalidValue),
            Some(date) => {
                if date >= *now  {
                    Ok(date)
                } else {
                    // Otherwise, shift to tomorrow.
                    match date.checked_add(Duration::days(1)) {
                        None => Err(Error::InvalidValue),
                        Some(date) => Ok(date)
                    }
                }
            }
        }
    }

    fn aux_register_watch_timestamp(&self, id: &Id<Channel>, range: &Range, tx: Box<ExtSender<Op>>)
        -> Result<Box<AdapterWatchGuard>, Error>
    {
        use foxbox_taxonomy::values::Range::*;

        // Now determine when/if to call the trigger.
        let mut thresholds = match *range {
            Leq (_) => {
                // This variant doesn't make sense.
                return Ok(Box::new(Guard(vec![])))
            }
            Geq (ref val) | Eq (ref val) => {
                let ts = *try!(val.as_timestamp().map_err(Error::TypeError))
                    .as_datetime();
                vec![(Movement::Enter, ts)]
            }
            OutOfStrict { ref min, ref max } => {
                let ts_min = *try!(min.as_timestamp().map_err(Error::TypeError))
                    .as_datetime();
                let ts_max = *try!(max.as_timestamp().map_err(Error::TypeError))
                    .as_datetime();
                vec![(Movement::Exit, ts_min), (Movement::Enter, ts_max)]
            }
            BetweenEq { ref min, ref max } => {
                let ts_min = *try!(min.as_timestamp().map_err(Error::TypeError))
                    .as_datetime();
                let ts_max = *try!(max.as_timestamp().map_err(Error::TypeError))
                    .as_datetime();
                vec![(Movement::Enter, ts_min), (Movement::Exit, ts_max)]
            }
        };

        // Determine when/if the next timers needs to launch.
        let now = chrono::UTC::now();
        let guards : Vec<timer::Guard> = thresholds.drain(..).filter_map(|(movement, date)| {
            if date < now {
                return None
            }
            let id = id.clone();
            let tx = tx.clone();
            let guard = self.timer.lock().unwrap().schedule_with_date(date, move || {
                let naive_time = chrono::Local::now().time();
                let duration = Duration::hours(naive_time.hour() as i64)
                    + Duration::minutes(naive_time.minute() as i64)
                    + Duration::seconds(naive_time.second() as i64);

                let event = match movement {
                    Movement::Enter => Op::Enter(id.clone(),
                        Value::Duration(ValDuration::from(duration))),
                    Movement::Exit => Op::Exit(id.clone(),
                        Value::Duration(ValDuration::from(duration))),
                };
                let _ = tx.send(event);
            });
            Some(guard)
        }).collect();
        Ok(Box::new(Guard(guards)))
    }
}

impl Clock {
    pub fn init(adapt: &Arc<AdapterManager>) -> Result<(), Error> {
        let getter_timestamp_id = Clock::getter_timestamp_id();
        let getter_time_of_day_id = Clock::getter_time_of_day_id();
        let getter_interval_id = Clock::getter_interval_id();
        let service_clock_id = Clock::service_clock_id();
        let adapter_id = Clock::id();
        let clock = Arc::new(Clock {
            timer: Mutex::new(timer::Timer::new()),
            getter_timestamp_id: getter_timestamp_id.clone(),
            getter_time_of_day_id: getter_time_of_day_id.clone(),
            getter_interval_id: getter_interval_id.clone(),
        });
        try!(adapt.add_adapter(clock));
        let mut service = Service::empty(&service_clock_id, &adapter_id);
        service.properties.insert("model".to_owned(), "Mozilla clock v1".to_owned());
        try!(adapt.add_service(service));
        try!(adapt.add_channel(Channel {
            feature: Id::new("clock/time-of-day-seconds"),
            supports_fetch: Some(Signature::returns(Maybe::Required(format::DURATION.clone()))),
            supports_watch: Some(Signature {
                accepts: Maybe::Required(format::DURATION.clone()),
                returns: Maybe::Required(format::DURATION.clone())
            }),
            id: getter_time_of_day_id,
            service: service_clock_id.clone(),
            adapter: adapter_id.clone(),
            ..Channel::default()
        }));
        try!(adapt.add_channel(Channel {
            feature: Id::new("clock/time-timestamp-rfc-3339"),
            supports_fetch: Some(Signature::returns(Maybe::Required(format::TIMESTAMP.clone()))),
            supports_watch: Some(Signature {
                accepts: Maybe::Required(format::TIMESTAMP.clone()),
                returns: Maybe::Required(format::TIMESTAMP.clone())
            }),
            id: getter_timestamp_id,
            service: service_clock_id.clone(),
            adapter: adapter_id.clone(),
            ..Channel::default()
        }));
        try!(adapt.add_channel(Channel {
            feature: Id::new("clock/time-interval-seconds"),
            supports_watch: Some(Signature {
                accepts: Maybe::Required(format::DURATION.clone()),
                returns: Maybe::Required(format::TIMESTAMP.clone())
            }),
            id: getter_interval_id,
            service: service_clock_id.clone(),
            adapter: adapter_id.clone(),
            ..Channel::default()
        }));
        Ok(())
    }
}
<|endoftext|>