Repository: typst/typst
File: crates/typst-library/src/introspection/counter.rs (Rust)

Code before:
    }

    fn diagnose(&self, history: &History<Self::Output>) -> SourceDiagnostic {
        format_convergence_warning(&self.0, self.1, history)
    }
}

/// Retrieves a counter at a specific location.
#[derive(Debug, Clone, PartialEq, Hash)]
struct NumberingAtIntrospection(Element, Location, Span);

impl Introspect for NumberingAtIntrospection {
    type Output = SourceResult<Option<Numbering>>;

    fn introspect(
        &self,
        _: &mut Engine,
        introspector: Tracked<Introspector>,
    ) -> Self::Output {
        let Some(content) = introspector.content_at(self.1) else {
            return Ok(None);
        };

        let numbering = if self.0 == HeadingElem::ELEM {
            content
                .to_packed::<HeadingElem>()
                .and_then(|elem| elem.numbering.as_option().clone())
                .flatten()
        } else if self.0 == FigureElem::ELEM {
            content
                .to_packed::<FigureElem>()
                .and_then(|elem| elem.numbering.as_option().clone())
                .flatten()
        } else if self.0 == EquationElem::ELEM {
            content
                .to_packed::<EquationElem>()
                .and_then(|elem| elem.numbering.as_option().clone())
                .flatten()
        } else if self.0 == FootnoteElem::ELEM {
            content
                .to_packed::<FootnoteElem>()
                .and_then(|elem| elem.numbering.as_option().clone())
        } else {
            None
        };

        Ok(numbering)
    }

    fn diagnose(&self, _: &History<Self::Output>) -> SourceDiagnostic {
        warning!(self.2, "is a warning even needed here")

Review comment (performance):
I suppose we could directly chain the `to_packed` checks instead of comparing `self.0`. One could hope that Rust can optimize that, but even if it doesn't, it's not exactly on the hot path compared to other places where we do exactly this.

Code after:
        if counter.is_page() {
            let at_delta = introspector.page(*loc).get().saturating_sub(at_page.get());
            at_state.step(NonZeroUsize::ONE, at_delta as u64);
            let final_delta = introspector.pages().get().saturating_sub(final_page.get());
            final_state.step(NonZeroUsize::ONE, final_delta as u64);
        }
        Ok(CounterState(smallvec![at_state.first(), final_state.first()]))
    }

    fn diagnose(&self, history: &History<Self::Output>) -> SourceDiagnostic {
        format_convergence_warning(&self.0, self.2, history)
    }
}

/// Retrieves the final state of a counter.
#[derive(Debug, Clone, PartialEq, Hash)]
struct CounterFinalIntrospection(Counter, Span);

impl Introspect for CounterFinalIntrospection {
    type Output = SourceResult<CounterState>;

    fn introspect(
        &self,
        engine: &mut Engine,
        introspector: Tracked<Introspector>,
    ) -> Self::Output {
        let Self(counter, _) = self;
        let sequence = sequence(counter, engine, introspector)?;
        let (mut state, page) = sequence.last().unwrap().clone();
        if counter.is_page() {
            let delta = introspector.pages().get().saturating_sub(page.get());
            state.step(NonZeroUsize::ONE, delta as u64);
        }
        Ok(state)
    }

    fn diagnose(&self, history: &History<Self::Output>) -> SourceDiagnostic {
        format_convergence_warning(&self.0, self.1, history)
    }
}

/// Produces the whole sequence of a counter.
///
/// Due to memoization, this has to happen just once for all retrievals of the
/// same counter, cutting down the number of computations from quadratic to
/// linear.
fn sequence(
    counter: &Counter,
    engine: &mut Engine,
    introspector: Tracked<Introspector>,
) -> SourceResult<EcoVec<(CounterState, NonZeroUsize)>> {