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 let Some((prev, _)) =
                list.iter().find(|(prev, _)| same_modifiers(&v.0, prev))
            {
                if &v.0 == prev {
                    bail!(span, "duplicate variant: {:?}", v.0);
                } else {
                    bail!(
                        span, "duplicate variant: {:?}", v.0;
                        hint: "modifiers are a set, meaning order and repetition do not matter"
                    )
                }
            }
            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 {

Review comment (performance):
A bit unfortunate to have quadratic complexity on the find here. Together with the complexity of `same_modifiers` we are on $O(n^2 m^2)$ where $n$ is the number of variants and $m$ the number of modifiers.

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!(