Repository: typst/typst
File: crates/typst-library/src/foundations/symbol.rs (Rust)

Code before:
    /// #envelope.stamped
    /// #envelope.stamped.pen
    /// #envelope.lightning
    /// #envelope.fly
    /// ```
    #[func(constructor)]
    pub fn construct(
        /// The callsite span.
        span: Span,
        /// The variants of the symbol.
        ///
        /// Can be a just a string consisting of a single character for the
        /// modifierless variant or an array with two strings specifying the modifiers
        /// and the symbol. Individual modifiers should be separated by dots. When
        /// displaying a symbol, Typst selects the first from the variants that have
        /// all attached modifiers and the minimum number of other modifiers.
        #[variadic]
        variants: Vec<Spanned<SymbolVariant>>,
    ) -> SourceResult<Symbol> {
        let mut list = Vec::<(EcoString, _)>::new();
        if variants.is_empty() {
            bail!(span, "expected at least one variant");
        }
        for Spanned { v, span } in variants {
            if list.iter().any(|(prev, _)| same_modifiers(&v.0, prev)) {
                bail!(span, "duplicate variant");
            }
            if !v.0.is_empty() {
                for modifier in v.0.split('.') {
                    if !is_ident(modifier) {
                        bail!(span, "invalid symbol modifier: {}", modifier.repr());
                    }
                }
            }
            list.push((v.0, v.1));
        }
        Ok(Symbol::runtime(list.into_boxed_slice()))
    }
}

impl Display for Symbol {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        f.write_char(self.get())
    }
}

impl Debug for Repr {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match self {
            Self::Single(c) => Debug::fmt(c, f),
            Self::Complex(list) => list.fmt(f),

Review comment (refactor):
Maybe we should improve the error message (and/or hints) to point out which variant is duplicate? And also include a hint, if `&v.0 != prev` (we can check this by replacing `.any` with `.find`), that the order of variants doesn't matter, so people who were confused like me don't remain so afterwards.

Code after:
    /// #envelope
    /// #envelope.stamped
    /// #envelope.stamped.pen
    /// #envelope.lightning
    /// #envelope.fly
    /// ```
    #[func(constructor)]
    pub fn construct(
        /// The callsite span.
        span: Span,
        /// The variants of the symbol.
        ///
        /// Can be a just a string consisting of a single character for the
        /// modifierless variant or an array with two strings specifying the modifiers
        /// and the symbol. Individual modifiers should be separated by dots. When
        /// displaying a symbol, Typst selects the first from the variants that have
        /// all attached modifiers and the minimum number of other modifiers.
        #[variadic]
        variants: Vec<Spanned<SymbolVariant>>,
    ) -> SourceResult<Symbol> {
        if variants.is_empty() {
            bail!(span, "expected at least one variant");
        }

        // Maps from canonicalized 128-bit hashes to indices of variants we've
        // seen before.
        let mut seen = HashMap::<u128, usize>::new();

        // A list of modifiers, cleared & reused in each iteration.
        let mut modifiers = Vec::new();

        // Validate the variants.
        for (i, &Spanned { ref v, span }) in variants.iter().enumerate() {
            modifiers.clear();

            if !v.0.is_empty() {
                // Collect all modifiers.
                for modifier in v.0.split('.') {
                    if !is_ident(modifier) {
                        bail!(span, "invalid symbol modifier: {}", modifier.repr());
                    }
                    modifiers.push(modifier);
                }
            }

            // Canonicalize the modifier order.
            modifiers.sort();

            // Ensure that there are no duplicate modifiers.
            if let Some(ms) = modifiers.windows(2).find(|ms| ms[0] == ms[1]) {
                bail!(