diff --git a/crates/forms/src/calc_order.rs b/crates/forms/src/calc_order.rs index e75a05d47..428c1724d 100644 --- a/crates/forms/src/calc_order.rs +++ b/crates/forms/src/calc_order.rs @@ -122,6 +122,12 @@ pub(crate) fn run_order(doc: &Document, fields: &[Field]) -> (Vec<(usize, bool)> (run, extra) } +#[cfg(test)] +thread_local! { + /// Read names compared while merging a field's reads. + pub(crate) static NAMES_COMPARED: std::cell::Cell = const { std::cell::Cell::new(0) }; +} + /// The form's calculation order (`fields` as [`fields`] reads them), with each calculated field's /// reads and issues, and Fix order's order. pub fn calculation_order(doc: &Document, fields: &[Field]) -> CalculationOrder { @@ -132,8 +138,11 @@ pub fn calculation_order(doc: &Document, fields: &[Field]) -> CalculationOrder { .map(|(f, _)| { let mut names = f.actions.calculate.reads(); if let Some(js) = &f.actions.scripts.calculate { + let mut known: HashSet = names.iter().cloned().collect(); for name in crate::af::calculation_reads(js) { - if !names.contains(&name) { + #[cfg(test)] + NAMES_COMPARED.with(|n| n.set(n.get() + 1)); + if known.insert(name.clone()) { names.push(name); } } diff --git a/crates/forms/src/tests.rs b/crates/forms/src/tests.rs index b2e4994ec..2b2042865 100644 --- a/crates/forms/src/tests.rs +++ b/crates/forms/src/tests.rs @@ -2981,3 +2981,20 @@ fn page_walk_skips_repeated_and_cyclic_kids_without_queueing_them() { assert_eq!(walk.next(), None); assert_eq!(page_refs(&doc), [page]); } + +/// A Calculate script naming thousands of fields is merged with the field's other reads in one +/// pass, not by searching the names so far for each one. +#[test] +fn a_script_reading_many_fields_merges_its_reads_in_one_pass() { + use crate::calc_order::NAMES_COMPARED; + let mut doc = one_page(); + add_field(&mut doc, 0, [50.0, 700.0, 250.0, 720.0], &NewField::Text { multiline: false }, Some("total")).unwrap(); + let n = 2_000; + let script: String = (0..n).map(|i| format!("getField(\"f{i}\").value + ")).collect::() + "0"; + set_field_script(&mut doc, "total", "calculate", Some(&format!("event.value = {script};"))).unwrap(); + NAMES_COMPARED.with(|c| c.set(0)); + let order = calculation_order(&doc, &fields(&doc)); + assert_eq!(order.fields[0].reads.len(), n); + let compared = NAMES_COMPARED.with(std::cell::Cell::get); + assert!(compared <= 4 * n, "{compared} names compared for {n} reads"); +}