Skip to main content

formualizer_eval/
function_registry.rs

1use crate::function::{FnCaps, Function};
2use crate::function_contract::{
3    FunctionDependencySemantics, FunctionEnvironmentSemantics, FunctionEvaluationSemantics,
4    FunctionResultSemantics, FunctionSemanticContract, FunctionSemanticIdentity,
5};
6use once_cell::sync::Lazy;
7use std::collections::{HashMap, VecDeque};
8use std::panic::{AssertUnwindSafe, catch_unwind};
9use std::sync::atomic::{AtomicU64, Ordering};
10use std::sync::{Arc, RwLock};
11
12type RegistryKey = (String, String);
13
14#[derive(Clone)]
15struct RegistryEntry {
16    function: Arc<dyn Function>,
17    generation: u64,
18    trusted_builtin: bool,
19    semantics: SemanticContractResolution,
20    semantics_by_arity: Arc<RwLock<HashMap<usize, SemanticContractResolution>>>,
21}
22
23#[derive(Clone)]
24struct AliasEntry {
25    target: RegistryKey,
26    owner: Option<(RegistryKey, u64)>,
27}
28
29struct RegistryState {
30    registrations: HashMap<RegistryKey, RegistryEntry>,
31    aliases: HashMap<RegistryKey, AliasEntry>,
32    semantic_epoch: u64,
33    semantic_changes: VecDeque<(u64, Vec<RegistryKey>)>,
34}
35
36impl Default for RegistryState {
37    fn default() -> Self {
38        Self {
39            registrations: HashMap::new(),
40            aliases: HashMap::new(),
41            semantic_epoch: 1,
42            semantic_changes: VecDeque::new(),
43        }
44    }
45}
46
47#[derive(Clone, Debug, PartialEq, Eq)]
48#[non_exhaustive]
49pub enum SemanticConformanceIssue {
50    CapabilityPanicked,
51    DependencyContractPanicked,
52    SemanticContractPanicked,
53    ArityMetadataPanicked,
54    VariadicMetadataPanicked,
55    AliasMetadataPanicked,
56    ArgumentSchemaPanicked,
57    AritySchemaMismatch,
58    DynamicDependencyMismatch,
59    ShortCircuitMismatch,
60    ReferenceResultMismatch,
61    LocalEnvironmentMismatch,
62    SpillResultMismatch,
63    PrecisionContractMismatch,
64    PrecisionContractInvalid,
65}
66
67#[derive(Clone, Debug)]
68#[non_exhaustive]
69pub struct SemanticContractResolution {
70    pub contract: Option<FunctionSemanticContract>,
71    pub generation: u64,
72    pub trusted_builtin: bool,
73    pub issues: Vec<SemanticConformanceIssue>,
74}
75
76impl SemanticContractResolution {
77    pub fn conforms(&self) -> bool {
78        self.contract.is_some() && self.issues.is_empty()
79    }
80}
81
82#[derive(Clone)]
83#[non_exhaustive]
84pub struct ResolvedFunction {
85    pub namespace: String,
86    pub canonical_name: String,
87    pub function: Arc<dyn Function>,
88    pub semantics: SemanticContractResolution,
89}
90
91static REGISTRY: Lazy<RwLock<RegistryState>> = Lazy::new(|| RwLock::new(RegistryState::default()));
92static NEXT_GENERATION: AtomicU64 = AtomicU64::new(1);
93
94/// Lock-free mirror of `RegistryState::semantic_epoch`.
95///
96/// Published only by `publish_semantic_change`, which takes the global
97/// `REGISTRY` write guard by type, so any holder of a registry read guard (for
98/// example a `SemanticEpochReadGuard`) observes exactly the guarded epoch here.
99/// Without a guard it is a monotonic, possibly stale lower bound. State-only log
100/// advancement on a local `RegistryState` (`advance_semantic_log`) never
101/// touches it.
102static SEMANTIC_EPOCH_MIRROR: AtomicU64 = AtomicU64::new(1);
103
104/// Displacement count at which the `load_builtins` shortcut is permanently
105/// disabled; the counter never wraps.
106const BUILTIN_DISPLACEMENTS_EXHAUSTED: u64 = u64::MAX;
107
108/// Bookkeeping for the `load_builtins` shortcut.
109struct BuiltinLoadState {
110    /// Registrations that replaced a trusted entry with a non-builtin function.
111    /// Monotonic, bumped under the `REGISTRY` write lock; sticky at
112    /// `BUILTIN_DISPLACEMENTS_EXHAUSTED`.
113    displacements: AtomicU64,
114    /// One plus the largest `displacements` value read at the start of a pass
115    /// that ran to completion; 0 until one has.
116    complete_since: AtomicU64,
117}
118
119impl BuiltinLoadState {
120    const fn new(displacements: u64) -> Self {
121        Self {
122            displacements: AtomicU64::new(displacements),
123            complete_since: AtomicU64::new(0),
124        }
125    }
126
127    fn record_displacement(&self) {
128        // `fetch_update` is deprecated in newer Rust and its replacement
129        // `try_update` is missing from the Pyodide wheel's Rust 1.93, so use
130        // a compare-exchange loop that builds on both.
131        let mut count = self.displacements.load(Ordering::Acquire);
132        loop {
133            let next = count.saturating_add(1);
134            match self.displacements.compare_exchange_weak(
135                count,
136                next,
137                Ordering::AcqRel,
138                Ordering::Acquire,
139            ) {
140                Ok(_) => break,
141                Err(observed) => count = observed,
142            }
143        }
144    }
145
146    fn loaded(&self) -> bool {
147        let complete_since = self.complete_since.load(Ordering::Acquire);
148        let displacements = self.displacements.load(Ordering::Acquire);
149        complete_since != 0
150            && displacements != BUILTIN_DISPLACEMENTS_EXHAUSTED
151            && displacements + 1 == complete_since
152    }
153
154    fn begin_pass(&self) -> u64 {
155        self.displacements.load(Ordering::Acquire)
156    }
157
158    fn finish_pass(&self, token: u64) {
159        // Every completed pass's token is sound: a displacement after its start
160        // leaves the counter above it, so `loaded` stays false until a later
161        // pass completes. Keeping the maximum stops a slow pass that started
162        // before a displacement from hiding a newer pass's completion. An
163        // exhausted token publishes nothing.
164        if token != BUILTIN_DISPLACEMENTS_EXHAUSTED {
165            self.complete_since.fetch_max(token + 1, Ordering::AcqRel);
166        }
167    }
168}
169
170static BUILTIN_LOAD: BuiltinLoadState = BuiltinLoadState::new(0);
171
172/// Per-thread registration call counts, used as untimed cost evidence by
173/// tests and probes. Not part of the product API.
174#[cfg(any(test, feature = "test-support"))]
175#[doc(hidden)]
176#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
177pub struct RegistrationCallCounts {
178    /// Calls to `builtins::load_builtins`.
179    pub load_builtins: u64,
180    /// Calls to the internal `register` (builtin or user).
181    pub register: u64,
182    /// Semantic metadata inspections (`inspect_semantics*`).
183    pub inspect_semantics: u64,
184}
185
186#[cfg(any(test, feature = "test-support"))]
187thread_local! {
188    static CALL_COUNTS: std::cell::Cell<RegistrationCallCounts> =
189        const { std::cell::Cell::new(RegistrationCallCounts { load_builtins: 0, register: 0, inspect_semantics: 0 }) };
190}
191
192/// Registration call counts observed on the current thread.
193#[cfg(any(test, feature = "test-support"))]
194#[doc(hidden)]
195pub fn registration_call_counts() -> RegistrationCallCounts {
196    CALL_COUNTS.with(std::cell::Cell::get)
197}
198
199/// Bump one per-thread call counter; compiles to nothing without test support.
200macro_rules! count_call {
201    ($field:ident) => {
202        #[cfg(any(test, feature = "test-support"))]
203        CALL_COUNTS.with(|counts| {
204            let mut current = counts.get();
205            current.$field += 1;
206            counts.set(current);
207        });
208    };
209}
210
211pub(crate) fn count_load_builtins_call() {
212    count_call!(load_builtins);
213}
214
215#[inline]
216fn norm<S: AsRef<str>>(s: S) -> String {
217    s.as_ref().to_uppercase()
218}
219
220/// Current registry semantic epoch without taking the registry lock.
221///
222/// Equal to `SemanticEpochReadGuard::epoch` while such a guard is held; safe to
223/// call under one (unlike `semantic_epoch`, which may queue behind a writer).
224pub(crate) fn semantic_epoch_lock_free() -> u64 {
225    SEMANTIC_EPOCH_MIRROR.load(Ordering::Acquire)
226}
227
228/// Whether every builtin is currently registered as a trusted builtin, so a
229/// `load_builtins` pass would change nothing.
230///
231/// True once a pass has completed and no registration has displaced a trusted
232/// builtin since that pass started. Registration never removes entries and a
233/// trusted builtin entry can only be replaced by a displacing registration, so
234/// every key the completed pass made trusted is still trusted.
235///
236/// This is a linearizable shortcut, not a guarantee that outlives the call: a
237/// concurrent displacement may land right after it returns.
238pub(crate) fn builtins_loaded() -> bool {
239    BUILTIN_LOAD.loaded()
240}
241
242/// Start a full `load_builtins` pass; pass the token to `finish_builtin_load_pass`
243/// only after every builtin registration returned.
244pub(crate) fn begin_builtin_load_pass() -> u64 {
245    BUILTIN_LOAD.begin_pass()
246}
247
248pub(crate) fn finish_builtin_load_pass(token: u64) {
249    BUILTIN_LOAD.finish_pass(token);
250}
251
252pub fn semantic_epoch() -> u64 {
253    REGISTRY
254        .read()
255        .unwrap_or_else(|poisoned| poisoned.into_inner())
256        .semantic_epoch
257}
258
259pub(crate) struct SemanticEpochReadGuard(std::sync::RwLockReadGuard<'static, RegistryState>);
260
261impl SemanticEpochReadGuard {
262    pub(crate) fn epoch(&self) -> u64 {
263        self.0.semantic_epoch
264    }
265
266    pub(crate) fn semantic_changes_affect_requests_since(
267        &self,
268        epoch: u64,
269        requests: impl IntoIterator<Item = (String, String, usize)>,
270    ) -> bool {
271        semantic_changes_affect_requests_in_state(&self.0, epoch, requests)
272    }
273}
274
275pub(crate) fn semantic_epoch_read_guard() -> SemanticEpochReadGuard {
276    SemanticEpochReadGuard(
277        REGISTRY
278            .read()
279            .unwrap_or_else(|poisoned| poisoned.into_inner()),
280    )
281}
282
283pub(crate) struct SemanticChanges {
284    pub(crate) epoch: u64,
285    pub(crate) complete: bool,
286    pub(crate) keys: Vec<(String, String)>,
287}
288
289/// Record a semantic change on the global registry and publish the epoch mirror.
290///
291/// Takes the global write guard by type so the mirror is only ever published
292/// while `REGISTRY` is write-locked.
293fn publish_semantic_change(
294    state: &mut std::sync::RwLockWriteGuard<'static, RegistryState>,
295    keys: impl IntoIterator<Item = RegistryKey>,
296) {
297    let epoch = advance_semantic_log(state, keys);
298    let previous = SEMANTIC_EPOCH_MIRROR.fetch_max(epoch, Ordering::AcqRel);
299    debug_assert!(previous <= epoch, "semantic epoch mirror moved backward");
300}
301
302/// Advance a registry state's epoch and change log without publishing the
303/// global mirror; returns the new epoch.
304fn advance_semantic_log(
305    state: &mut RegistryState,
306    keys: impl IntoIterator<Item = RegistryKey>,
307) -> u64 {
308    state.semantic_epoch = state.semantic_epoch.saturating_add(1);
309    let epoch = state.semantic_epoch;
310    state
311        .semantic_changes
312        .push_back((epoch, keys.into_iter().collect()));
313    if state.semantic_changes.len() > 1_024 {
314        state.semantic_changes.pop_front();
315    }
316    epoch
317}
318
319pub(crate) fn semantic_changes_since(epoch: u64) -> SemanticChanges {
320    let state = REGISTRY
321        .read()
322        .unwrap_or_else(|poisoned| poisoned.into_inner());
323    semantic_changes_since_in_state(&state, epoch)
324}
325
326fn semantic_changes_since_in_state(state: &RegistryState, epoch: u64) -> SemanticChanges {
327    let complete = state
328        .semantic_changes
329        .front()
330        .is_none_or(|(oldest, _)| epoch.saturating_add(1) >= *oldest);
331    let keys = state
332        .semantic_changes
333        .iter()
334        .filter(|(changed_epoch, _)| *changed_epoch > epoch)
335        .flat_map(|(_, keys)| keys.iter().cloned())
336        .collect();
337    SemanticChanges {
338        epoch: state.semantic_epoch,
339        complete,
340        keys,
341    }
342}
343
344#[derive(Clone, Debug, PartialEq, Eq)]
345#[non_exhaustive]
346pub enum RegistrationError {
347    NameMetadataPanicked,
348    NamespaceMetadataPanicked,
349}
350
351pub fn try_register_function(function: Arc<dyn Function>) -> Result<(), RegistrationError> {
352    register(function, false)
353}
354
355pub fn register_function(function: Arc<dyn Function>) {
356    let _ = try_register_function(function);
357}
358pub(crate) fn register_builtin(function: Arc<dyn Function>) {
359    register(function, true).expect("builtin name and namespace metadata must not panic");
360}
361
362fn register(function: Arc<dyn Function>, trusted_builtin: bool) -> Result<(), RegistrationError> {
363    count_call!(register);
364    let namespace = catch_unwind(AssertUnwindSafe(|| function.namespace()))
365        .map_err(|_| RegistrationError::NamespaceMetadataPanicked)?;
366    let name = catch_unwind(AssertUnwindSafe(|| function.name()))
367        .map_err(|_| RegistrationError::NameMetadataPanicked)?;
368    let key = (norm(namespace), norm(name));
369    #[cfg(test)]
370    if trusted_builtin {
371        tests::record_builtin_key(&key);
372    }
373    // Re-registering an already-registered trusted builtin is a no-op (see the
374    // same check under the write lock below). Decide that under a read lock
375    // before allocating a generation or inspecting metadata; the write-lock
376    // check stays authoritative for registrations that race this one.
377    if trusted_builtin
378        && REGISTRY
379            .read()
380            .unwrap_or_else(|poisoned| poisoned.into_inner())
381            .registrations
382            .get(&key)
383            .is_some_and(|entry| entry.trusted_builtin)
384    {
385        return Ok(());
386    }
387    let generation = NEXT_GENERATION.fetch_add(1, Ordering::Relaxed);
388    let aliases = catch_unwind(AssertUnwindSafe(|| function.aliases().to_vec()));
389    let min_args = catch_unwind(AssertUnwindSafe(|| function.min_args()));
390    let initial_arity = min_args.as_ref().copied().unwrap_or(0);
391    let mut semantics = match min_args {
392        Ok(arity) => inspect_semantics(&function, trusted_builtin, generation, arity),
393        Err(_) => failed_resolution(
394            generation,
395            trusted_builtin,
396            SemanticConformanceIssue::ArityMetadataPanicked,
397        ),
398    };
399    let aliases = match aliases {
400        Ok(aliases) => aliases,
401        Err(_) => {
402            semantics
403                .issues
404                .push(SemanticConformanceIssue::AliasMetadataPanicked);
405            semantics.contract = None;
406            Vec::new()
407        }
408    };
409
410    let mut state = REGISTRY
411        .write()
412        .unwrap_or_else(|poisoned| poisoned.into_inner());
413    if trusted_builtin
414        && state
415            .registrations
416            .get(&key)
417            .is_some_and(|entry| entry.trusted_builtin)
418    {
419        return Ok(());
420    }
421    let previous = state
422        .registrations
423        .get(&key)
424        .map(|entry| (entry.generation, entry.trusted_builtin));
425    if !trusted_builtin && previous.is_some_and(|(_, previous_trusted)| previous_trusted) {
426        BUILTIN_LOAD.record_displacement();
427    }
428    let mut changed_spellings = Vec::new();
429    if let Some((previous_generation, _)) = previous {
430        changed_spellings.extend(
431            state
432                .aliases
433                .iter()
434                .filter(|(_, alias)| {
435                    alias.owner.as_ref() == Some(&(key.clone(), previous_generation))
436                })
437                .map(|(alias_key, _)| alias_key.clone()),
438        );
439        state
440            .aliases
441            .retain(|_, alias| alias.owner.as_ref() != Some(&(key.clone(), previous_generation)));
442    }
443    state.registrations.insert(
444        key.clone(),
445        RegistryEntry {
446            function: Arc::clone(&function),
447            generation,
448            trusted_builtin,
449            semantics: semantics.clone(),
450            semantics_by_arity: Arc::new(RwLock::new(HashMap::from([(initial_arity, semantics)]))),
451        },
452    );
453    for alias in aliases {
454        if !alias.eq_ignore_ascii_case(&key.1) {
455            let alias_key = (key.0.clone(), norm(alias));
456            changed_spellings.push(alias_key.clone());
457            state.aliases.insert(
458                alias_key,
459                AliasEntry {
460                    target: key.clone(),
461                    owner: Some((key.clone(), generation)),
462                },
463            );
464        }
465    }
466    changed_spellings.push(key);
467    publish_semantic_change(&mut state, changed_spellings);
468    Ok(())
469}
470
471fn failed_resolution(
472    generation: u64,
473    trusted_builtin: bool,
474    issue: SemanticConformanceIssue,
475) -> SemanticContractResolution {
476    SemanticContractResolution {
477        contract: None,
478        generation,
479        trusted_builtin,
480        issues: vec![issue],
481    }
482}
483
484fn inspect_semantics(
485    function: &Arc<dyn Function>,
486    trusted_builtin: bool,
487    generation: u64,
488    arity: usize,
489) -> SemanticContractResolution {
490    inspect_semantics_with_identity_metadata(function, trusted_builtin, generation, arity).0
491}
492
493fn inspect_semantics_with_identity_metadata(
494    function: &Arc<dyn Function>,
495    trusted_builtin: bool,
496    generation: u64,
497    arity: usize,
498) -> (SemanticContractResolution, Option<(FnCaps, Vec<bool>)>) {
499    count_call!(inspect_semantics);
500    let mut issues = Vec::new();
501    let inspected_caps = inspected(
502        function.as_ref(),
503        &mut issues,
504        SemanticConformanceIssue::CapabilityPanicked,
505        || function.caps(),
506    );
507    let caps = inspected_caps.unwrap_or_else(FnCaps::empty);
508    let precision = inspected(
509        function.as_ref(),
510        &mut issues,
511        SemanticConformanceIssue::DependencyContractPanicked,
512        || function.dependency_contract(arity),
513    )
514    .flatten();
515    let explicit = inspected(
516        function.as_ref(),
517        &mut issues,
518        SemanticConformanceIssue::SemanticContractPanicked,
519        || function.semantic_contract(arity),
520    )
521    .flatten();
522    let schema = inspected(
523        function.as_ref(),
524        &mut issues,
525        SemanticConformanceIssue::ArgumentSchemaPanicked,
526        || function.arg_schema(),
527    );
528    let min_args = inspected(
529        function.as_ref(),
530        &mut issues,
531        SemanticConformanceIssue::ArityMetadataPanicked,
532        || function.min_args(),
533    );
534    let variadic = inspected(
535        function.as_ref(),
536        &mut issues,
537        SemanticConformanceIssue::VariadicMetadataPanicked,
538        || function.variadic(),
539    );
540    if let (Some(schema), Some(min_args), Some(variadic)) = (schema, min_args, variadic)
541        && !schema_allows_arity(schema, min_args, variadic, arity, !trusted_builtin)
542    {
543        issues.push(SemanticConformanceIssue::AritySchemaMismatch);
544    }
545    let contract =
546        explicit.or_else(|| trusted_builtin.then(|| trusted_contract_from_caps(caps, precision)));
547    if let Some(contract) = contract {
548        if contract.precision != precision {
549            issues.push(SemanticConformanceIssue::PrecisionContractMismatch);
550        }
551        if !precision_is_valid(contract, arity) {
552            issues.push(SemanticConformanceIssue::PrecisionContractInvalid);
553        }
554        check_capability(
555            &mut issues,
556            caps.contains(FnCaps::DYNAMIC_DEPENDENCY),
557            contract.dependency == FunctionDependencySemantics::Dynamic,
558            SemanticConformanceIssue::DynamicDependencyMismatch,
559        );
560        check_capability(
561            &mut issues,
562            caps.contains(FnCaps::SHORT_CIRCUIT),
563            contract.evaluation == FunctionEvaluationSemantics::ShortCircuit,
564            SemanticConformanceIssue::ShortCircuitMismatch,
565        );
566        check_capability(
567            &mut issues,
568            caps.contains(FnCaps::RETURNS_REFERENCE),
569            contract.result.may_return_reference(),
570            SemanticConformanceIssue::ReferenceResultMismatch,
571        );
572        check_capability(
573            &mut issues,
574            caps.contains(FnCaps::LOCAL_ENVIRONMENT),
575            contract.environment == FunctionEnvironmentSemantics::LocalBindings,
576            SemanticConformanceIssue::LocalEnvironmentMismatch,
577        );
578        check_capability(
579            &mut issues,
580            caps.contains(FnCaps::MAY_SPILL),
581            contract.result.may_spill(),
582            SemanticConformanceIssue::SpillResultMismatch,
583        );
584    }
585    let identity_metadata = inspected_caps.zip(schema).map(|(caps, schema)| {
586        let repeating = schema.iter().find(|argument| argument.repeating.is_some());
587        let argument_by_ref = (0..arity)
588            .map(|index| {
589                schema
590                    .get(index)
591                    .or(repeating)
592                    .is_some_and(|argument| argument.by_ref)
593            })
594            .collect();
595        (caps, argument_by_ref)
596    });
597    (
598        SemanticContractResolution {
599            contract: issues.is_empty().then_some(contract).flatten(),
600            generation,
601            trusted_builtin,
602            issues,
603        },
604        identity_metadata,
605    )
606}
607
608fn precision_is_valid(contract: FunctionSemanticContract, arity: usize) -> bool {
609    use crate::function_contract::{
610        CriteriaValueRange, FunctionArgumentDependencyContract as Arguments,
611        FunctionArgumentDependencyRole as Role,
612    };
613    let Some(precision) = contract.precision else {
614        return true;
615    };
616    if !precision.arity.allows(arity)
617        || contract.dependency != FunctionDependencySemantics::RecursiveSyntacticArgs
618    {
619        return false;
620    }
621    match precision.arguments {
622        Arguments::AllArgs(role) | Arguments::Variadic(role) => {
623            !matches!(role, Role::IgnoredLiteral | Role::Unsupported)
624        }
625        Arguments::CriteriaPairs(criteria) => {
626            let value_valid = match criteria.value_range {
627                CriteriaValueRange::None => true,
628                CriteriaValueRange::Fixed(index) => index < arity,
629                CriteriaValueRange::Optional {
630                    provided_index,
631                    fallback_criteria_range_index,
632                } => provided_index <= arity && fallback_criteria_range_index < arity,
633            };
634            let pair_end = match criteria.value_range {
635                CriteriaValueRange::Fixed(index) if index >= criteria.first_criteria_pair => index,
636                CriteriaValueRange::Optional { provided_index, .. }
637                    if provided_index >= criteria.first_criteria_pair =>
638                {
639                    provided_index
640                }
641                _ => arity,
642            };
643            value_valid
644                && criteria.first_criteria_pair < pair_end
645                && (pair_end - criteria.first_criteria_pair).is_multiple_of(2)
646        }
647        Arguments::LocalBindingPairs => {
648            contract.environment == FunctionEnvironmentSemantics::LocalBindings
649                && arity >= 3
650                && !arity.is_multiple_of(2)
651        }
652        Arguments::LambdaParameters => {
653            contract.environment == FunctionEnvironmentSemantics::LocalBindings && arity >= 1
654        }
655    }
656}
657
658fn schema_allows_arity(
659    schema: &[crate::args::ArgSchema],
660    min_args: usize,
661    variadic: bool,
662    arity: usize,
663    strict_required_count: bool,
664) -> bool {
665    if schema.is_empty() {
666        return min_args == 0 && arity == 0;
667    }
668
669    let mut optional_seen = false;
670    let mut required_count = 0usize;
671    let mut repeating = None;
672    for (index, argument) in schema.iter().enumerate() {
673        if argument.required {
674            if optional_seen {
675                return false;
676            }
677            required_count += 1;
678        } else {
679            optional_seen = true;
680        }
681        if let Some(width) = argument.repeating {
682            if width == 0 || repeating.is_some() || index + 1 != schema.len() {
683                return false;
684            }
685            repeating = Some(width);
686        }
687    }
688    let represented_minimum = min_args.min(schema.len());
689    if (strict_required_count && required_count != min_args)
690        || (!strict_required_count && required_count < represented_minimum)
691        || schema
692            .iter()
693            .take(represented_minimum)
694            .any(|argument| !argument.required)
695        || (!variadic && schema.len() > 1 && min_args > schema.len())
696        || arity < min_args
697    {
698        return false;
699    }
700    if let Some(width) = repeating {
701        if width > schema.len() {
702            return false;
703        }
704        let fixed_prefix = schema.len() - width;
705        return arity >= schema.len() && (arity - fixed_prefix).is_multiple_of(width);
706    }
707    if variadic {
708        return true;
709    }
710    arity <= schema.len().max(min_args)
711}
712
713fn inspected<T>(
714    function: &dyn Function,
715    issues: &mut Vec<SemanticConformanceIssue>,
716    issue: SemanticConformanceIssue,
717    inspect: impl FnOnce() -> T,
718) -> Option<T> {
719    match crate::external_callbacks::inspect_metadata(function, inspect) {
720        Ok(value) => Some(value),
721        Err(_) => {
722            issues.push(issue);
723            None
724        }
725    }
726}
727
728fn check_capability(
729    issues: &mut Vec<SemanticConformanceIssue>,
730    capability: bool,
731    semantic: bool,
732    issue: SemanticConformanceIssue,
733) {
734    if capability != semantic {
735        issues.push(issue);
736    }
737}
738
739fn trusted_contract_from_caps(
740    caps: FnCaps,
741    precision: Option<crate::function_contract::FunctionDependencyContract>,
742) -> FunctionSemanticContract {
743    let mut contract = FunctionSemanticContract::trusted_builtin_default(precision);
744    if caps.contains(FnCaps::DYNAMIC_DEPENDENCY) {
745        contract.dependency = FunctionDependencySemantics::Dynamic;
746    }
747    if caps.contains(FnCaps::SHORT_CIRCUIT) {
748        contract.evaluation = FunctionEvaluationSemantics::ShortCircuit;
749    }
750    contract.result = FunctionResultSemantics::from_capabilities(
751        caps.contains(FnCaps::RETURNS_REFERENCE),
752        caps.contains(FnCaps::MAY_SPILL),
753    );
754    if caps.contains(FnCaps::LOCAL_ENVIRONMENT) {
755        contract.environment = FunctionEnvironmentSemantics::LocalBindings;
756    }
757    contract
758}
759
760const EXCEL_PREFIXES: &[&str] = &["_XLFN.", "_XLL.", "_XLWS."];
761
762fn resolve_registered(
763    state: &RegistryState,
764    key: &RegistryKey,
765) -> Option<(RegistryKey, RegistryEntry)> {
766    if let Some(entry) = state.registrations.get(key) {
767        return Some((key.clone(), entry.clone()));
768    }
769    let alias = state.aliases.get(key)?;
770    state
771        .registrations
772        .get(&alias.target)
773        .map(|entry| (alias.target.clone(), entry.clone()))
774}
775
776#[cfg(test)]
777thread_local! {
778    static RESOLUTION_WRITES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
779}
780
781fn resolve_entry(ns: &str, name: &str) -> Option<(RegistryKey, RegistryEntry)> {
782    #[cfg(test)]
783    RESOLUTION_WRITES.with(|c| c.set(c.get() + 1));
784    let ns = norm(ns);
785    let normalized_name = norm(name);
786    let key = (ns.clone(), normalized_name.clone());
787    let mut state = REGISTRY
788        .write()
789        .unwrap_or_else(|poisoned| poisoned.into_inner());
790    resolve_entry_in_state(&mut state, ns, normalized_name, key)
791}
792
793fn resolve_entry_in_state(
794    state: &mut RegistryState,
795    ns: String,
796    normalized_name: String,
797    key: RegistryKey,
798) -> Option<(RegistryKey, RegistryEntry)> {
799    if let Some(entry) = resolve_registered(state, &key) {
800        return Some(entry);
801    }
802    let mut candidate = normalized_name.as_str();
803    loop {
804        let mut stripped_any = false;
805        for prefix in EXCEL_PREFIXES {
806            if let Some(rest) = candidate.strip_prefix(prefix) {
807                candidate = rest;
808                stripped_any = true;
809                let stripped_key = (ns.clone(), candidate.to_string());
810                if let Some((canonical, entry)) = resolve_registered(state, &stripped_key) {
811                    state.aliases.insert(
812                        key.clone(),
813                        AliasEntry {
814                            target: canonical.clone(),
815                            owner: Some((canonical.clone(), entry.generation)),
816                        },
817                    );
818                    return Some((canonical, entry));
819                }
820                break;
821            }
822        }
823        if !stripped_any {
824            break;
825        }
826    }
827    None
828}
829
830fn resolve_key_read_only(
831    state: &RegistryState,
832    key: &RegistryKey,
833) -> Option<(RegistryKey, RegistryEntry)> {
834    if let Some(entry) = resolve_registered(state, key) {
835        return Some(entry);
836    }
837    let mut candidate = key.1.as_str();
838    loop {
839        let rest = EXCEL_PREFIXES
840            .iter()
841            .find_map(|prefix| candidate.strip_prefix(prefix))?;
842        candidate = rest;
843        let stripped = (key.0.clone(), candidate.to_string());
844        if let Some(entry) = resolve_registered(state, &stripped) {
845            return Some(entry);
846        }
847    }
848}
849
850fn resolve_entry_read_only(ns: &str, name: &str) -> Option<(RegistryKey, RegistryEntry)> {
851    let state = REGISTRY
852        .read()
853        .unwrap_or_else(|poisoned| poisoned.into_inner());
854    resolve_key_read_only(&state, &(norm(ns), norm(name)))
855}
856
857pub fn get(ns: &str, name: &str) -> Option<Arc<dyn Function>> {
858    let key = (norm(ns), norm(name));
859    {
860        let state = REGISTRY
861            .read()
862            .unwrap_or_else(|poisoned| poisoned.into_inner());
863        let entry = state.registrations.get(&key).or_else(|| {
864            let alias = state.aliases.get(&key)?;
865            state.registrations.get(&alias.target)
866        });
867        if let Some(entry) = entry {
868            return Some(Arc::clone(&entry.function));
869        }
870        if !EXCEL_PREFIXES
871            .iter()
872            .any(|prefix| key.1.starts_with(prefix))
873        {
874            return None;
875        }
876    }
877    // Prefix misses may publish an alias. Recheck under the write lock, since a
878    // registration or alias owner could have changed after releasing the read lock.
879    resolve_entry(&key.0, &key.1).map(|(_, entry)| entry.function)
880}
881
882/// Read-only registry lookup for planning providers. Unlike [`get`], this does
883/// not populate the global Excel-prefix alias cache.
884#[doc(hidden)]
885pub fn get_for_planning(ns: &str, name: &str) -> Option<Arc<dyn Function>> {
886    resolve_entry_read_only(ns, name).map(|(_, entry)| entry.function)
887}
888
889pub(crate) struct GlobalRegistryFunctionProvider;
890
891impl crate::traits::FunctionProvider for GlobalRegistryFunctionProvider {
892    fn planning_semantic_revision(&self) -> Option<u64> {
893        Some(0)
894    }
895
896    fn get_function(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
897        get(ns, name)
898    }
899
900    fn get_function_for_planning(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
901        get_for_planning(ns, name)
902    }
903}
904
905#[derive(Clone)]
906struct PlanningRegistration {
907    canonical: RegistryKey,
908    function: Arc<dyn Function>,
909    generation: u64,
910    trusted_builtin: bool,
911}
912
913type RawCapabilities = HashMap<String, HashMap<String, Option<FnCaps>>>;
914
915/// Immutable registry view used by one formula-planning operation.
916///
917/// Function metadata is inspected before publication of the snapshot. Resolution
918/// afterward is lock-free and never populates the global prefix-alias cache.
919#[derive(Clone)]
920pub(crate) struct RegistryPlanningSnapshot {
921    epoch: u64,
922    provider_revision: Option<u64>,
923    requests: Arc<Vec<(String, String, usize)>>,
924    functions: Arc<HashMap<RegistryKey, Arc<dyn Function>>>,
925    capabilities: Arc<HashMap<RegistryKey, FnCaps>>,
926    raw_capabilities: Option<Arc<RawCapabilities>>,
927    identities: Arc<HashMap<(String, String, usize), FunctionSemanticIdentity>>,
928}
929
930#[derive(Clone, Copy, Debug, PartialEq, Eq)]
931pub(crate) enum PlanningSnapshotError {
932    RegistryChangedDuringCapture,
933    ProviderRevisionUnavailable,
934}
935
936impl RegistryPlanningSnapshot {
937    const CAPTURE_ATTEMPTS: usize = 16;
938
939    pub(crate) fn capture_for_requests(
940        runtime_provider: &dyn crate::traits::FunctionProvider,
941        requests: impl IntoIterator<Item = (String, String, usize)>,
942    ) -> Result<Self, PlanningSnapshotError> {
943        crate::builtins::load_builtins();
944        let mut requests: Vec<_> = requests.into_iter().collect();
945        requests.sort();
946        requests.dedup();
947        Self::capture_with_hook(runtime_provider, &requests, Self::CAPTURE_ATTEMPTS, |_| {})
948    }
949
950    fn capture_with_hook(
951        runtime_provider: &dyn crate::traits::FunctionProvider,
952        requests: &[(String, String, usize)],
953        attempts: usize,
954        mut after_registry_copy: impl FnMut(usize),
955    ) -> Result<Self, PlanningSnapshotError> {
956        for attempt in 0..attempts {
957            let provider_revision = if requests.is_empty() {
958                None
959            } else {
960                Some(
961                    runtime_provider
962                        .planning_semantic_revision()
963                        .ok_or(PlanningSnapshotError::ProviderRevisionUnavailable)?,
964                )
965            };
966            let start_epoch = REGISTRY
967                .read()
968                .unwrap_or_else(|poisoned| poisoned.into_inner())
969                .semantic_epoch;
970            let mut runtime_functions = HashMap::new();
971            let mut resolved_names = std::collections::HashSet::new();
972            for (namespace, name, _) in requests {
973                let key = (norm(namespace), norm(name));
974                if resolved_names.insert(key.clone())
975                    && let Some(function) =
976                        runtime_provider.get_function_for_planning(namespace, name)
977                {
978                    runtime_functions.insert(key, function);
979                }
980            }
981            let (epoch, registrations) = {
982                let state = REGISTRY
983                    .read()
984                    .unwrap_or_else(|poisoned| poisoned.into_inner());
985                let registrations = requests
986                    .iter()
987                    .filter_map(|(namespace, name, arity)| {
988                        let request_key = (norm(namespace), norm(name));
989                        resolve_key_read_only(&state, &request_key).map(|(canonical, entry)| {
990                            (
991                                (request_key.0, request_key.1, *arity),
992                                PlanningRegistration {
993                                    canonical,
994                                    function: entry.function,
995                                    generation: entry.generation,
996                                    trusted_builtin: entry.trusted_builtin,
997                                },
998                            )
999                        })
1000                    })
1001                    .collect::<HashMap<_, _>>();
1002                (state.semantic_epoch, registrations)
1003            };
1004
1005            after_registry_copy(attempt);
1006
1007            let mut capabilities = HashMap::new();
1008            let mut identities = HashMap::new();
1009            for (namespace, name, arity) in requests {
1010                let request_key = (norm(namespace), norm(name));
1011                let Some(runtime) = runtime_functions.get(&request_key) else {
1012                    continue;
1013                };
1014                let Some(registration) =
1015                    registrations.get(&(request_key.0.clone(), request_key.1.clone(), *arity))
1016                else {
1017                    continue;
1018                };
1019                if !Arc::ptr_eq(runtime, &registration.function) {
1020                    continue;
1021                }
1022                let (semantics, identity_metadata) = inspect_semantics_with_identity_metadata(
1023                    &registration.function,
1024                    registration.trusted_builtin,
1025                    registration.generation,
1026                    *arity,
1027                );
1028                let Some(contract) = semantics.contract else {
1029                    continue;
1030                };
1031                let Some((caps, argument_by_ref)) = identity_metadata else {
1032                    continue;
1033                };
1034                capabilities.insert(request_key.clone(), caps);
1035                identities.insert(
1036                    (request_key.0, request_key.1, *arity),
1037                    FunctionSemanticIdentity {
1038                        namespace: registration.canonical.0.clone(),
1039                        canonical_name: registration.canonical.1.clone(),
1040                        generation: registration.generation,
1041                        caps,
1042                        contract,
1043                        argument_by_ref,
1044                    },
1045                );
1046            }
1047
1048            for (key, function) in &runtime_functions {
1049                if !capabilities.contains_key(key)
1050                    && let Ok(caps) =
1051                        crate::external_callbacks::inspect_metadata(function.as_ref(), || {
1052                            function.caps()
1053                        })
1054                {
1055                    capabilities.insert(key.clone(), caps);
1056                }
1057            }
1058
1059            let provider_unchanged = provider_revision.is_none_or(|revision| {
1060                runtime_provider.planning_semantic_revision() == Some(revision)
1061            });
1062            // Validate the capture against the functions this snapshot actually
1063            // requested, not against the global registry epoch.
1064            //
1065            // Registering any function anywhere bumps `semantic_epoch`, so a
1066            // global equality check treated a completely unrelated registration
1067            // -- a different namespace, a different name -- as "the registry
1068            // changed underneath us". Under concurrent registration that
1069            // produced spurious `RegistryChangedDuringCapture` results and, in
1070            // authoritative mode, spurious FormulaPlane family fallbacks whose
1071            // reported reason blamed the provider.
1072            //
1073            // `semantic_changes_affect_requests_in_state` consults the change
1074            // log across the whole capture window [start_epoch, now] and only
1075            // reports a conflict when a *requested* key changed. It stays
1076            // conservative when the bounded change log has been truncated past
1077            // `start_epoch`, so precision never costs correctness.
1078            let requests_unchanged = {
1079                let state = REGISTRY
1080                    .read()
1081                    .unwrap_or_else(|poisoned| poisoned.into_inner());
1082                !semantic_changes_affect_requests_in_state(
1083                    &state,
1084                    start_epoch,
1085                    requests.iter().cloned(),
1086                )
1087            };
1088            let unchanged = requests_unchanged && provider_unchanged;
1089            if unchanged {
1090                // AST planning has no interned name id. Retain exact requested
1091                // spellings so repeated capability reads neither normalize nor
1092                // clone function Arcs. Missing functions are cached as well.
1093                let mut raw_capabilities: HashMap<String, HashMap<String, Option<FnCaps>>> =
1094                    HashMap::new();
1095                for (namespace, name, _) in requests {
1096                    let caps = capabilities.get(&(norm(namespace), norm(name))).copied();
1097                    raw_capabilities
1098                        .entry(namespace.clone())
1099                        .or_default()
1100                        .insert(name.clone(), caps);
1101                }
1102                return Ok(Self {
1103                    epoch,
1104                    provider_revision,
1105                    requests: Arc::new(requests.to_vec()),
1106                    functions: Arc::new(runtime_functions),
1107                    capabilities: Arc::new(capabilities),
1108                    raw_capabilities: (!raw_capabilities.is_empty())
1109                        .then(|| Arc::new(raw_capabilities)),
1110                    identities: Arc::new(identities),
1111                });
1112            }
1113        }
1114        Err(PlanningSnapshotError::RegistryChangedDuringCapture)
1115    }
1116
1117    pub(crate) fn epoch(&self) -> u64 {
1118        self.epoch
1119    }
1120
1121    pub(crate) fn provider_revision(&self) -> Option<u64> {
1122        self.provider_revision
1123    }
1124
1125    pub(crate) fn semantic_changes_affect_requests_since(&self, epoch: u64) -> bool {
1126        semantic_changes_affect_requests_since(epoch, self.requests.iter().cloned())
1127    }
1128
1129    pub(crate) fn semantic_changes_affect_requests_since_guarded(
1130        &self,
1131        guard: &SemanticEpochReadGuard,
1132        epoch: u64,
1133    ) -> bool {
1134        guard.semantic_changes_affect_requests_since(epoch, self.requests.iter().cloned())
1135    }
1136}
1137
1138pub(crate) fn semantic_changes_affect_requests_since(
1139    epoch: u64,
1140    requests: impl IntoIterator<Item = (String, String, usize)>,
1141) -> bool {
1142    let state = REGISTRY
1143        .read()
1144        .unwrap_or_else(|poisoned| poisoned.into_inner());
1145    semantic_changes_affect_requests_in_state(&state, epoch, requests)
1146}
1147
1148fn semantic_changes_affect_requests_in_state(
1149    state: &RegistryState,
1150    epoch: u64,
1151    requests: impl IntoIterator<Item = (String, String, usize)>,
1152) -> bool {
1153    let changes = semantic_changes_since_in_state(state, epoch);
1154    if changes.epoch == epoch {
1155        return false;
1156    }
1157    if !changes.complete {
1158        return true;
1159    }
1160    let requests = requests
1161        .into_iter()
1162        .flat_map(|(namespace, name, _)| {
1163            let namespace = norm(namespace);
1164            let normalized = norm(name);
1165            let mut spellings = vec![(namespace.clone(), normalized.clone())];
1166            let mut stripped = normalized.as_str();
1167            while let Some(rest) = EXCEL_PREFIXES
1168                .iter()
1169                .find_map(|prefix| stripped.strip_prefix(prefix))
1170            {
1171                stripped = rest;
1172                spellings.push((namespace.clone(), stripped.to_string()));
1173            }
1174            spellings
1175        })
1176        .collect::<std::collections::BTreeSet<_>>();
1177    changes.keys.into_iter().any(|key| requests.contains(&key))
1178}
1179
1180impl crate::traits::FunctionProvider for RegistryPlanningSnapshot {
1181    fn planning_semantic_revision(&self) -> Option<u64> {
1182        Some(self.provider_revision.unwrap_or(0))
1183    }
1184
1185    fn get_function(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
1186        self.functions.get(&(norm(ns), norm(name))).cloned()
1187    }
1188
1189    fn get_function_for_planning(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
1190        self.get_function(ns, name)
1191    }
1192
1193    fn function_capabilities(&self, ns: &str, name: &str) -> Option<FnCaps> {
1194        if let Some(caps) = self
1195            .raw_capabilities
1196            .as_deref()
1197            .and_then(|namespaces| namespaces.get(ns))
1198            .and_then(|names| names.get(name))
1199        {
1200            return *caps;
1201        }
1202        self.capabilities.get(&(norm(ns), norm(name))).copied()
1203    }
1204
1205    fn function_semantic_identity(
1206        &self,
1207        ns: &str,
1208        name: &str,
1209        arity: usize,
1210    ) -> Option<FunctionSemanticIdentity> {
1211        self.identities.get(&(norm(ns), norm(name), arity)).cloned()
1212    }
1213}
1214
1215/// Cached reporting provenance, independent of public optimization promises.
1216#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1217pub(crate) enum ProviderReporting {
1218    #[default]
1219    Unverified,
1220    ExplicitProvider,
1221    SealedBuiltin,
1222}
1223/// Inspect once when capturing the executing callable. A local override cannot
1224/// inherit the sealed builtin's reporting witness from its name or flags.
1225pub(crate) fn operation_reporting_contract(
1226    ns: &str,
1227    name: &str,
1228    function: &Arc<dyn Function>,
1229) -> ProviderReporting {
1230    let builtin = {
1231        let state = REGISTRY
1232            .read()
1233            .unwrap_or_else(|poisoned| poisoned.into_inner());
1234        resolve_key_read_only(&state, &(norm(ns), norm(name))).is_some_and(|(_, registered)| {
1235            registered.trusted_builtin && Arc::ptr_eq(function, &registered.function)
1236        })
1237    };
1238    // Sealed provenance takes precedence over an instance's public promise.
1239    if builtin {
1240        ProviderReporting::SealedBuiltin
1241    } else if crate::external_callbacks::metadata(
1242        function.as_ref(),
1243        || false,
1244        || function.reports_known_limitations(),
1245    ) {
1246        ProviderReporting::ExplicitProvider
1247    } else {
1248        ProviderReporting::Unverified
1249    }
1250}
1251
1252pub fn resolve(ns: &str, name: &str) -> Option<ResolvedFunction> {
1253    resolve_entry(ns, name).map(to_resolved)
1254}
1255
1256pub fn resolve_with_epoch(ns: &str, name: &str) -> Option<(u64, ResolvedFunction)> {
1257    let key = (norm(ns), norm(name));
1258    let state = REGISTRY
1259        .read()
1260        .unwrap_or_else(|poisoned| poisoned.into_inner());
1261    resolve_registered(&state, &key).map(|entry| (state.semantic_epoch, to_resolved(entry)))
1262}
1263pub fn resolve_for_arity(ns: &str, name: &str, arity: usize) -> Option<ResolvedFunction> {
1264    resolve_entry(ns, name).map(|((namespace, canonical_name), entry)| {
1265        let semantics = {
1266            let cached = entry
1267                .semantics_by_arity
1268                .read()
1269                .unwrap_or_else(|poisoned| poisoned.into_inner())
1270                .get(&arity)
1271                .cloned();
1272            cached.unwrap_or_else(|| {
1273                let inspected = inspect_semantics(
1274                    &entry.function,
1275                    entry.trusted_builtin,
1276                    entry.generation,
1277                    arity,
1278                );
1279                entry
1280                    .semantics_by_arity
1281                    .write()
1282                    .unwrap_or_else(|poisoned| poisoned.into_inner())
1283                    .entry(arity)
1284                    .or_insert_with(|| inspected.clone())
1285                    .clone()
1286            })
1287        };
1288        ResolvedFunction {
1289            semantics,
1290            namespace,
1291            canonical_name,
1292            function: entry.function,
1293        }
1294    })
1295}
1296
1297pub(crate) fn resolve_semantic_identity<P: crate::traits::FunctionProvider + ?Sized>(
1298    provider: &P,
1299    ns: &str,
1300    name: &str,
1301    arity: usize,
1302) -> Option<FunctionSemanticIdentity> {
1303    let runtime = provider.get_function(ns, name)?;
1304    let resolved = resolve_for_arity(ns, name, arity)?;
1305    if !Arc::ptr_eq(&runtime, &resolved.function) {
1306        return None;
1307    }
1308    let contract = resolved.semantics.contract?;
1309    let argument_by_ref = crate::external_callbacks::inspect_metadata(runtime.as_ref(), || {
1310        let schema = runtime.arg_schema();
1311        let repeating = schema.iter().find(|argument| argument.repeating.is_some());
1312        (0..arity)
1313            .map(|index| {
1314                schema
1315                    .get(index)
1316                    .or(repeating)
1317                    .is_some_and(|argument| argument.by_ref)
1318            })
1319            .collect()
1320    })
1321    .ok()?;
1322    Some(FunctionSemanticIdentity {
1323        namespace: resolved.namespace,
1324        canonical_name: resolved.canonical_name,
1325        generation: resolved.semantics.generation,
1326        caps: crate::external_callbacks::metadata(runtime.as_ref(), FnCaps::empty, || {
1327            runtime.caps()
1328        }),
1329        contract,
1330        argument_by_ref,
1331    })
1332}
1333
1334fn to_resolved(
1335    ((namespace, canonical_name), entry): (RegistryKey, RegistryEntry),
1336) -> ResolvedFunction {
1337    ResolvedFunction {
1338        namespace,
1339        canonical_name,
1340        function: entry.function,
1341        semantics: entry.semantics,
1342    }
1343}
1344
1345pub fn register_alias(ns: &str, alias: &str, target_ns: &str, target_name: &str) {
1346    let mut state = REGISTRY
1347        .write()
1348        .unwrap_or_else(|poisoned| poisoned.into_inner());
1349    let alias_key = (norm(ns), norm(alias));
1350    let target = (norm(target_ns), norm(target_name));
1351    let old_target = state
1352        .aliases
1353        .get(&alias_key)
1354        .map(|entry| entry.target.clone());
1355    if old_target.as_ref() == Some(&target) {
1356        return;
1357    }
1358    state.aliases.insert(
1359        alias_key.clone(),
1360        AliasEntry {
1361            target: target.clone(),
1362            owner: None,
1363        },
1364    );
1365    // Retargeting changes resolution through the alias spelling, not resolution
1366    // of either directly named target.
1367    publish_semantic_change(&mut state, [alias_key]);
1368}
1369
1370pub fn snapshot_registered() -> Vec<(String, String, Arc<dyn Function>)> {
1371    let state = REGISTRY
1372        .read()
1373        .unwrap_or_else(|poisoned| poisoned.into_inner());
1374    state
1375        .registrations
1376        .iter()
1377        .map(|((ns, name), entry)| (ns.clone(), name.clone(), Arc::clone(&entry.function)))
1378        .collect()
1379}
1380pub fn snapshot_semantics() -> Vec<ResolvedFunction> {
1381    let state = REGISTRY
1382        .read()
1383        .unwrap_or_else(|poisoned| poisoned.into_inner());
1384    state
1385        .registrations
1386        .iter()
1387        .map(|((namespace, canonical_name), entry)| ResolvedFunction {
1388            namespace: namespace.clone(),
1389            canonical_name: canonical_name.clone(),
1390            function: Arc::clone(&entry.function),
1391            semantics: entry.semantics.clone(),
1392        })
1393        .collect()
1394}
1395
1396#[cfg(test)]
1397pub(crate) mod tests {
1398    use super::*;
1399    use crate::traits::FunctionProvider;
1400
1401    struct TestFn {
1402        ns: &'static str,
1403        name: &'static str,
1404        aliases: &'static [&'static str],
1405    }
1406
1407    impl Function for TestFn {
1408        fn name(&self) -> &'static str {
1409            self.name
1410        }
1411        fn namespace(&self) -> &'static str {
1412            self.ns
1413        }
1414        fn aliases(&self) -> &'static [&'static str] {
1415            self.aliases
1416        }
1417        fn eval<'a, 'b, 'c>(
1418            &self,
1419            _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
1420            _ctx: &dyn crate::traits::FunctionContext<'b>,
1421        ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
1422            Ok(crate::traits::CalcValue::Scalar(
1423                formualizer_common::LiteralValue::Number(1.0),
1424            ))
1425        }
1426    }
1427
1428    struct PlanningFn {
1429        ns: &'static str,
1430        name: &'static str,
1431        aliases: &'static [&'static str],
1432        caps: FnCaps,
1433    }
1434
1435    impl Function for PlanningFn {
1436        fn name(&self) -> &'static str {
1437            self.name
1438        }
1439        fn namespace(&self) -> &'static str {
1440            self.ns
1441        }
1442        fn aliases(&self) -> &'static [&'static str] {
1443            self.aliases
1444        }
1445        fn caps(&self) -> FnCaps {
1446            self.caps
1447        }
1448        fn min_args(&self) -> usize {
1449            1
1450        }
1451        fn variadic(&self) -> bool {
1452            true
1453        }
1454        fn arg_schema(&self) -> &'static [crate::args::ArgSchema] {
1455            static SCHEMA: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
1456                std::sync::LazyLock::new(|| {
1457                    let mut argument = crate::args::ArgSchema::any();
1458                    argument.repeating = Some(1);
1459                    vec![argument]
1460                });
1461            &SCHEMA
1462        }
1463        fn eval<'a, 'b, 'c>(
1464            &self,
1465            _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
1466            _ctx: &dyn crate::traits::FunctionContext<'b>,
1467        ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
1468            unreachable!()
1469        }
1470    }
1471
1472    fn planning_fn(
1473        ns: &'static str,
1474        name: &'static str,
1475        aliases: &'static [&'static str],
1476        caps: FnCaps,
1477    ) -> Arc<dyn Function> {
1478        Arc::new(PlanningFn {
1479            ns,
1480            name,
1481            aliases,
1482            caps,
1483        })
1484    }
1485
1486    #[test]
1487    fn runtime_hits_only_clone_current_function_without_resolution_writes() {
1488        let ns = "__RUNTIME_READ_FAST_PATH__";
1489        let first = planning_fn(ns, "TARGET", &["ALIAS"], FnCaps::empty());
1490        register_function(first.clone());
1491        assert!(Arc::ptr_eq(&get(ns, "_xlfn._xlws.alias").unwrap(), &first));
1492        RESOLUTION_WRITES.with(|c| c.set(0));
1493        for name in ["target", "ALIAS", "_XLFN._XLWS.ALIAS"] {
1494            assert!(Arc::ptr_eq(&get(ns, name).unwrap(), &first));
1495        }
1496        assert!(get(ns, "MISSING").is_none());
1497        assert_eq!(RESOLUTION_WRITES.with(|c| c.get()), 0);
1498        let second = planning_fn(ns, "TARGET", &["ALIAS"], FnCaps::empty());
1499        register_function(second.clone());
1500        for name in ["target", "ALIAS", "_XLFN._XLWS.ALIAS"] {
1501            assert!(Arc::ptr_eq(&get(ns, name).unwrap(), &second));
1502        }
1503        std::thread::scope(|scope| {
1504            for _ in 0..8 {
1505                let second = &second;
1506                scope.spawn(move || {
1507                    RESOLUTION_WRITES.with(|c| c.set(0));
1508                    for _ in 0..1000 {
1509                        assert!(Arc::ptr_eq(&get(ns, "TARGET").unwrap(), second));
1510                        assert!(Arc::ptr_eq(&get(ns, "_XLFN._XLWS.ALIAS").unwrap(), second));
1511                    }
1512                    assert_eq!(RESOLUTION_WRITES.with(|c| c.get()), 0);
1513                });
1514            }
1515        });
1516    }
1517
1518    #[test]
1519    fn planning_snapshot_resolves_direct_alias_namespace_and_prefix_without_cache_mutation() {
1520        let ns = "__PLANNING_PARITY__";
1521        register_builtin(planning_fn(ns, "TARGET", &["ALIAS"], FnCaps::empty()));
1522        let requests = [
1523            (ns.to_string(), "TARGET".to_string(), 1),
1524            (ns.to_string(), "alias".to_string(), 1),
1525            (ns.to_string(), "_xlfn._xlws.alias".to_string(), 1),
1526        ];
1527        let prefixed_key = (ns.to_string(), "_XLFN._XLWS.ALIAS".to_string());
1528        assert!(!REGISTRY.read().unwrap().aliases.contains_key(&prefixed_key));
1529        let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1530            &GlobalRegistryFunctionProvider,
1531            requests,
1532        )
1533        .unwrap();
1534        assert!(!REGISTRY.read().unwrap().aliases.contains_key(&prefixed_key));
1535
1536        let direct = snapshot
1537            .function_semantic_identity(ns, "TARGET", 1)
1538            .unwrap();
1539        for spelling in ["alias", "_xlfn._xlws.alias"] {
1540            let resolved = snapshot
1541                .function_semantic_identity(ns, spelling, 1)
1542                .unwrap();
1543            assert_eq!(resolved.namespace, ns);
1544            assert_eq!(resolved.canonical_name, "TARGET");
1545            assert_eq!(resolved.generation, direct.generation);
1546            assert!(Arc::ptr_eq(
1547                &snapshot.get_function(ns, spelling).unwrap(),
1548                &snapshot.get_function(ns, "TARGET").unwrap(),
1549            ));
1550        }
1551    }
1552
1553    // FORM-000138 test coordination.
1554    //
1555    // `BUILTIN_DISPLACEMENT_LOCK` is held by every test that displaces a
1556    // builtin in the global (empty) namespace (only the FORM-000138 tests do)
1557    // and by every test that asserts builtin completeness or no-op call counts.
1558    // Tests that merely call `load_builtins`, and tests that displace their own
1559    // trusted fixtures in private namespaces, are not coordinated. Assertions
1560    // below therefore only rely on facts those tests cannot invalidate: the
1561    // monotonic displacement counter, the change log, per-thread call counts,
1562    // and "after `load_builtins` returns, every builtin key is trusted"
1563    // (which only a global-namespace displacement could break).
1564    pub(crate) static BUILTIN_DISPLACEMENT_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
1565
1566    pub(crate) fn lock_builtin_displacement() -> std::sync::MutexGuard<'static, ()> {
1567        BUILTIN_DISPLACEMENT_LOCK
1568            .lock()
1569            .unwrap_or_else(|poisoned| poisoned.into_inner())
1570    }
1571
1572    /// Displacements of any trusted entry so far. Uncoordinated tests displace
1573    /// their own trusted fixtures in private namespaces, which conservatively
1574    /// forces one extra full builtin pass; call-count assertions allow for that.
1575    pub(crate) fn builtin_displacements() -> u64 {
1576        BUILTIN_LOAD.displacements.load(Ordering::Acquire)
1577    }
1578
1579    thread_local! {
1580        static RECORDED_BUILTIN_KEYS: std::cell::RefCell<Option<std::collections::BTreeSet<RegistryKey>>> =
1581            const { std::cell::RefCell::new(None) };
1582    }
1583
1584    pub(super) fn record_builtin_key(key: &RegistryKey) {
1585        RECORDED_BUILTIN_KEYS.with(|keys| {
1586            if let Some(keys) = keys.borrow_mut().as_mut() {
1587                keys.insert(key.clone());
1588            }
1589        });
1590    }
1591
1592    /// Every key a full builtin pass registers, observed by running one on this
1593    /// thread (idempotent: already-trusted keys take the no-op fast path).
1594    fn expected_builtin_keys() -> std::collections::BTreeSet<RegistryKey> {
1595        RECORDED_BUILTIN_KEYS.with(|keys| *keys.borrow_mut() = Some(Default::default()));
1596        crate::builtins::register_all_builtins();
1597        let keys = RECORDED_BUILTIN_KEYS
1598            .with(|keys| keys.borrow_mut().take())
1599            .unwrap();
1600        assert!(keys.len() > 400, "{}", keys.len());
1601        assert!(keys.contains(&(String::new(), "SUM".to_string())));
1602        keys
1603    }
1604
1605    /// Load until the shortcut reports loaded, then check that every expected
1606    /// builtin key is registered and trusted in one registry snapshot.
1607    fn assert_builtins_complete(expected: &std::collections::BTreeSet<RegistryKey>) {
1608        while !builtins_loaded() {
1609            crate::builtins::load_builtins();
1610        }
1611        let state = REGISTRY
1612            .read()
1613            .unwrap_or_else(|poisoned| poisoned.into_inner());
1614        for key in expected {
1615            let entry = state
1616                .registrations
1617                .get(key)
1618                .unwrap_or_else(|| panic!("builtin {key:?} missing"));
1619            assert!(entry.trusted_builtin, "builtin {key:?} not trusted");
1620        }
1621    }
1622
1623    fn builtin_entry(name: &str) -> (Arc<dyn Function>, u64, bool) {
1624        let state = REGISTRY
1625            .read()
1626            .unwrap_or_else(|poisoned| poisoned.into_inner());
1627        let entry = &state.registrations[&(String::new(), norm(name))];
1628        (
1629            Arc::clone(&entry.function),
1630            entry.generation,
1631            entry.trusted_builtin,
1632        )
1633    }
1634
1635    // FORM-000138: shortcut bookkeeping over a local instance, so every
1636    // interleaving below is deterministic.
1637    #[test]
1638    fn builtin_load_state_interleavings() {
1639        // First load.
1640        let state = BuiltinLoadState::new(0);
1641        assert!(!state.loaded());
1642        let pass = state.begin_pass();
1643        state.finish_pass(pass);
1644        assert!(state.loaded());
1645
1646        // Displacement after a completed pass reopens until the next pass.
1647        state.record_displacement();
1648        assert!(!state.loaded());
1649        let pass = state.begin_pass();
1650        state.finish_pass(pass);
1651        assert!(state.loaded());
1652
1653        // Displacement during a pass: that pass does not count.
1654        let pass = state.begin_pass();
1655        state.record_displacement();
1656        state.finish_pass(pass);
1657        assert!(!state.loaded());
1658
1659        // Overlapping passes: a stale pass finishing late does not hide a newer
1660        // completion, and a newer completion is not hidden by it.
1661        let stale = state.begin_pass();
1662        state.record_displacement();
1663        let fresh = state.begin_pass();
1664        state.finish_pass(fresh);
1665        assert!(state.loaded());
1666        state.finish_pass(stale);
1667        assert!(state.loaded());
1668
1669        // A pass that never finishes (panicked) publishes nothing.
1670        state.record_displacement();
1671        let _abandoned = state.begin_pass();
1672        assert!(!state.loaded());
1673    }
1674
1675    // FORM-000138: the displacement counter never wraps; exhaustion permanently
1676    // disables the shortcut.
1677    #[test]
1678    fn builtin_load_state_exhaustion_is_sticky() {
1679        let state = BuiltinLoadState::new(BUILTIN_DISPLACEMENTS_EXHAUSTED - 2);
1680        let pass = state.begin_pass();
1681        state.finish_pass(pass);
1682        assert!(state.loaded());
1683        state.record_displacement();
1684        assert!(!state.loaded());
1685        let pass = state.begin_pass();
1686        assert_eq!(pass, BUILTIN_DISPLACEMENTS_EXHAUSTED - 1);
1687        state.finish_pass(pass);
1688        assert!(state.loaded());
1689
1690        state.record_displacement();
1691        assert_eq!(
1692            state.displacements.load(Ordering::Acquire),
1693            BUILTIN_DISPLACEMENTS_EXHAUSTED
1694        );
1695        assert!(!state.loaded());
1696        for _ in 0..3 {
1697            let pass = state.begin_pass();
1698            state.finish_pass(pass);
1699            assert!(!state.loaded());
1700            state.record_displacement();
1701            assert_eq!(
1702                state.displacements.load(Ordering::Acquire),
1703                BUILTIN_DISPLACEMENTS_EXHAUSTED
1704            );
1705        }
1706    }
1707
1708    #[test]
1709    fn builtin_load_state_displacements_are_atomic_and_saturating() {
1710        for (initial, expected) in [(0, 8_000), (u64::MAX - 10, BUILTIN_DISPLACEMENTS_EXHAUSTED)] {
1711            let state = BuiltinLoadState::new(initial);
1712            std::thread::scope(|scope| {
1713                for _ in 0..8 {
1714                    let state = &state;
1715                    scope.spawn(move || {
1716                        for _ in 0..1_000 {
1717                            state.record_displacement();
1718                        }
1719                    });
1720                }
1721            });
1722            assert_eq!(state.displacements.load(Ordering::Acquire), expected);
1723        }
1724    }
1725
1726    // FORM-000138: once loaded, `load_builtins` neither registers nor inspects
1727    // anything, and the registered builtins are complete and unchanged.
1728    #[test]
1729    fn repeated_builtin_loading_is_a_counted_no_op() {
1730        let _serial = lock_builtin_displacement();
1731        let expected = expected_builtin_keys();
1732        assert_builtins_complete(&expected);
1733        let (function, generation, _) = builtin_entry("SUM");
1734        let before = registration_call_counts();
1735        let displacements = builtin_displacements();
1736        for _ in 0..100 {
1737            crate::builtins::load_builtins();
1738        }
1739        let after = registration_call_counts();
1740        assert_eq!(after.load_builtins - before.load_builtins, 100);
1741        if builtin_displacements() == displacements {
1742            assert_eq!(after.register, before.register);
1743        }
1744        assert_eq!(after.inspect_semantics, before.inspect_semantics);
1745        let (current, current_generation, current_trusted) = builtin_entry("SUM");
1746        assert!(Arc::ptr_eq(&function, &current));
1747        assert_eq!(current_generation, generation);
1748        assert!(current_trusted);
1749
1750        // A direct builtin re-registration takes the read-lock fast path: no
1751        // metadata inspection and the entry is untouched.
1752        let before = registration_call_counts();
1753        register_builtin(Arc::clone(&function));
1754        let after = registration_call_counts();
1755        assert_eq!(after.register - before.register, 1);
1756        assert_eq!(after.inspect_semantics, before.inspect_semantics);
1757        assert_eq!(builtin_entry("SUM").1, generation);
1758        assert_builtins_complete(&expected);
1759    }
1760
1761    // FORM-000138: repeated planning snapshots (one per ordered-fallback
1762    // proposal in production) register nothing after the first load. Metadata
1763    // inspection is request-scoped: none for an empty request set, one per
1764    // requested function otherwise, never a builtin-set-sized pass.
1765    #[test]
1766    fn repeated_planning_snapshots_do_not_reregister_builtins() {
1767        let _serial = lock_builtin_displacement();
1768        let expected = expected_builtin_keys();
1769        assert_builtins_complete(&expected);
1770        let displacements = builtin_displacements();
1771
1772        let before = registration_call_counts();
1773        for _ in 0..100 {
1774            RegistryPlanningSnapshot::capture_for_requests(
1775                &GlobalRegistryFunctionProvider,
1776                std::iter::empty(),
1777            )
1778            .unwrap();
1779        }
1780        let after = registration_call_counts();
1781        assert_eq!(after.load_builtins - before.load_builtins, 100);
1782        if builtin_displacements() == displacements {
1783            assert_eq!(after.register, before.register);
1784            assert_eq!(after.inspect_semantics, before.inspect_semantics);
1785        }
1786
1787        let before = registration_call_counts();
1788        for _ in 0..100 {
1789            let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1790                &GlobalRegistryFunctionProvider,
1791                [(String::new(), "SUM".to_string(), 2)],
1792            )
1793            .unwrap();
1794            assert!(snapshot.function_semantic_identity("", "SUM", 2).is_some());
1795        }
1796        let after = registration_call_counts();
1797        if builtin_displacements() == displacements {
1798            assert_eq!(after.register, before.register);
1799            assert_eq!(after.inspect_semantics - before.inspect_semantics, 100);
1800        }
1801    }
1802
1803    // FORM-000138: a user registration that displaces a trusted builtin still
1804    // takes effect (counted displacement, published change), and once
1805    // `load_builtins` returns the builtin is trusted again, as before the
1806    // shortcut existed. Uncoordinated loaders may restore it earlier, so trust
1807    // is only asserted after our own load. Uses the same function object so
1808    // concurrent tests evaluating IMCOSH are unaffected.
1809    #[test]
1810    fn displaced_builtin_is_restored_by_next_load() {
1811        let _serial = lock_builtin_displacement();
1812        let expected = expected_builtin_keys();
1813        assert_builtins_complete(&expected);
1814        let (builtin, _, _) = builtin_entry("IMCOSH");
1815
1816        let epoch = semantic_epoch();
1817        let displacements = builtin_displacements();
1818        register_function(Arc::clone(&builtin));
1819        assert!(builtin_displacements() > displacements);
1820        assert!(semantic_changes_affect_requests_since(
1821            epoch,
1822            [(String::new(), "IMCOSH".to_string(), 1)]
1823        ));
1824
1825        crate::builtins::load_builtins();
1826        // The pass installs a fresh builtin object, so compare trust, not identity.
1827        assert!(builtin_entry("IMCOSH").2);
1828        assert_builtins_complete(&expected);
1829
1830        // A same-name registration in another namespace is not a displacement.
1831        let displacements = builtin_displacements();
1832        let before = registration_call_counts();
1833        register_function(planning_fn(
1834            "__DISPLACE_OTHER__",
1835            "IMCOSH",
1836            &[],
1837            FnCaps::empty(),
1838        ));
1839        crate::builtins::load_builtins();
1840        if builtin_displacements() == displacements {
1841            assert_eq!(registration_call_counts().register, before.register + 1);
1842        }
1843    }
1844
1845    // FORM-000138: registrations and builtin overrides racing repeated
1846    // `load_builtins` calls keep today's semantics: every user registration
1847    // lands and publishes, and loading afterwards leaves every builtin
1848    // registered and trusted.
1849    #[test]
1850    fn concurrent_loading_and_registration_preserve_semantics() {
1851        let _serial = lock_builtin_displacement();
1852        let expected = expected_builtin_keys();
1853        assert_builtins_complete(&expected);
1854        let (builtin, _, _) = builtin_entry("IMCOSH");
1855        let epoch = semantic_epoch();
1856        let barrier = Arc::new(std::sync::Barrier::new(4));
1857        let mut workers = Vec::new();
1858        for _ in 0..2 {
1859            let barrier = Arc::clone(&barrier);
1860            workers.push(std::thread::spawn(move || {
1861                barrier.wait();
1862                for _ in 0..200 {
1863                    crate::builtins::load_builtins();
1864                }
1865            }));
1866        }
1867        {
1868            let barrier = Arc::clone(&barrier);
1869            workers.push(std::thread::spawn(move || {
1870                barrier.wait();
1871                for index in 0..50 {
1872                    let name: &'static str = Box::leak(format!("F{index}").into_boxed_str());
1873                    register_function(planning_fn(
1874                        "__CONCURRENT_LOAD__",
1875                        name,
1876                        &[],
1877                        FnCaps::empty(),
1878                    ));
1879                }
1880            }));
1881        }
1882        {
1883            let barrier = Arc::clone(&barrier);
1884            let builtin = Arc::clone(&builtin);
1885            workers.push(std::thread::spawn(move || {
1886                barrier.wait();
1887                for _ in 0..20 {
1888                    register_function(Arc::clone(&builtin));
1889                    std::thread::yield_now();
1890                }
1891            }));
1892        }
1893        for worker in workers {
1894            worker.join().unwrap();
1895        }
1896        for index in 0..50 {
1897            assert!(get("__CONCURRENT_LOAD__", &format!("F{index}")).is_some());
1898        }
1899        assert!(semantic_epoch() >= epoch + 70);
1900        assert_builtins_complete(&expected);
1901        assert!(builtin_entry("IMCOSH").2);
1902    }
1903
1904    // FORM-000138: the mirror equals the guarded epoch while a guard is held,
1905    // including while a writer is (possibly) waiting, and local-state log
1906    // advancement never touches it. No `load_builtins` here.
1907    #[test]
1908    fn lock_free_epoch_matches_guard() {
1909        let ns = "__LOCK_FREE_EPOCH__";
1910        register_function(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1911        let guard = semantic_epoch_read_guard();
1912        assert_eq!(semantic_epoch_lock_free(), guard.epoch());
1913
1914        let mut local = RegistryState::default();
1915        for index in 0..=1_024 {
1916            advance_semantic_log(&mut local, [(String::new(), format!("LOCAL_{index}"))]);
1917        }
1918        assert_eq!(semantic_epoch_lock_free(), guard.epoch());
1919
1920        let (started_tx, started_rx) = std::sync::mpsc::sync_channel(0);
1921        let writer = std::thread::spawn(move || {
1922            started_tx.send(()).unwrap();
1923            register_function(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1924        });
1925        started_rx.recv().unwrap();
1926        std::thread::yield_now();
1927        assert_eq!(semantic_epoch_lock_free(), guard.epoch());
1928        let guarded = guard.epoch();
1929        drop(guard);
1930        writer.join().unwrap();
1931        let mirrored = semantic_epoch_lock_free();
1932        assert!(mirrored > guarded);
1933        assert!(mirrored <= semantic_epoch());
1934    }
1935
1936    // FORM-000138: the mirror never decreases under concurrent registration
1937    // and concurrent local-state log advancement.
1938    #[test]
1939    fn lock_free_epoch_never_decreases() {
1940        let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
1941        let observer = {
1942            let stop = Arc::clone(&stop);
1943            std::thread::spawn(move || {
1944                let mut last = semantic_epoch_lock_free();
1945                let mut samples = 0u64;
1946                while !stop.load(Ordering::Acquire) || samples < 1_000 {
1947                    let current = semantic_epoch_lock_free();
1948                    assert!(
1949                        current >= last,
1950                        "mirror moved backward: {last} -> {current}"
1951                    );
1952                    last = current;
1953                    samples += 1;
1954                }
1955                last
1956            })
1957        };
1958        let local = std::thread::spawn(|| {
1959            let mut state = RegistryState::default();
1960            for index in 0..5_000 {
1961                advance_semantic_log(&mut state, [(String::new(), format!("LOCAL_{index}"))]);
1962            }
1963        });
1964        let before = semantic_epoch_lock_free();
1965        for index in 0..200 {
1966            let name: &'static str = Box::leak(format!("M{index}").into_boxed_str());
1967            register_function(planning_fn(
1968                "__MIRROR_MONOTONIC__",
1969                name,
1970                &[],
1971                FnCaps::empty(),
1972            ));
1973        }
1974        local.join().unwrap();
1975        stop.store(true, Ordering::Release);
1976        let last = observer.join().unwrap();
1977        assert!(semantic_epoch_lock_free() >= before + 200);
1978        assert!(semantic_epoch_lock_free() >= last);
1979    }
1980
1981    #[test]
1982    fn guarded_request_change_check_does_not_relock_behind_queued_writer() {
1983        let ns = "__GUARDED_REQUEST_CHANGE__";
1984        register_function(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1985        let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1986            &GlobalRegistryFunctionProvider,
1987            [(ns.to_string(), "TARGET".to_string(), 1)],
1988        )
1989        .unwrap();
1990        register_function(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1991
1992        let guard = semantic_epoch_read_guard();
1993        let (queued_tx, queued_rx) = std::sync::mpsc::sync_channel(0);
1994        let writer = std::thread::spawn(move || {
1995            assert!(REGISTRY.try_write().is_err());
1996            queued_tx.send(()).unwrap();
1997            let mut state = REGISTRY
1998                .write()
1999                .unwrap_or_else(|poisoned| poisoned.into_inner());
2000            publish_semantic_change(&mut state, [(ns.to_string(), "QUEUED_WRITER".to_string())]);
2001        });
2002        queued_rx.recv().unwrap();
2003        std::thread::yield_now();
2004
2005        assert!(snapshot.semantic_changes_affect_requests_since_guarded(&guard, snapshot.epoch(),));
2006        drop(guard);
2007        writer.join().unwrap();
2008    }
2009
2010    #[test]
2011    fn workbook_planning_fallback_does_not_populate_prefix_alias_cache() {
2012        let ns = "__PLANNING_WORKBOOK_PREFIX__";
2013        register_builtin(planning_fn(ns, "TARGET", &["ALIAS"], FnCaps::empty()));
2014        let prefixed_key = (ns.to_string(), "_XLFN.ALIAS".to_string());
2015        assert!(!REGISTRY.read().unwrap().aliases.contains_key(&prefixed_key));
2016
2017        let snapshot = RegistryPlanningSnapshot::capture_for_requests(
2018            &crate::test_workbook::TestWorkbook::default(),
2019            [(ns.to_string(), "_xlfn.alias".to_string(), 1)],
2020        )
2021        .unwrap();
2022
2023        assert!(
2024            snapshot
2025                .function_semantic_identity(ns, "_xlfn.alias", 1)
2026                .is_some()
2027        );
2028        assert!(!REGISTRY.read().unwrap().aliases.contains_key(&prefixed_key));
2029    }
2030
2031    #[test]
2032    fn planning_snapshot_is_immutable_across_replacement() {
2033        let ns = "__PLANNING_IMMUTABLE__";
2034        register_builtin(planning_fn(ns, "TARGET", &["OLD_ALIAS"], FnCaps::empty()));
2035        let requests = [
2036            (ns.to_string(), "TARGET".to_string(), 1),
2037            (ns.to_string(), "OLD_ALIAS".to_string(), 1),
2038        ];
2039        let snapshot = RegistryPlanningSnapshot::capture_with_hook(
2040            &GlobalRegistryFunctionProvider,
2041            &requests,
2042            10_000,
2043            |_| {},
2044        )
2045        .unwrap();
2046        let old_function = snapshot.get_function(ns, "TARGET").unwrap();
2047        let old_identity = snapshot
2048            .function_semantic_identity(ns, "TARGET", 1)
2049            .unwrap();
2050
2051        register_function(planning_fn(ns, "TARGET", &[], FnCaps::MAY_SPILL));
2052        let current = resolve_for_arity(ns, "TARGET", 1).unwrap();
2053        assert!(current.semantics.generation > old_identity.generation);
2054        assert!(!Arc::ptr_eq(&old_function, &current.function));
2055        assert_eq!(
2056            snapshot
2057                .function_semantic_identity(ns, "TARGET", 1)
2058                .unwrap(),
2059            old_identity
2060        );
2061        assert!(Arc::ptr_eq(
2062            &old_function,
2063            &snapshot.get_function(ns, "TARGET").unwrap(),
2064        ));
2065        assert!(get(ns, "OLD_ALIAS").is_none());
2066        assert_eq!(
2067            snapshot
2068                .function_semantic_identity(ns, "OLD_ALIAS", 1)
2069                .unwrap(),
2070            old_identity
2071        );
2072        assert!(Arc::ptr_eq(
2073            &old_function,
2074            &snapshot.get_function(ns, "OLD_ALIAS").unwrap(),
2075        ));
2076    }
2077
2078    #[test]
2079    fn planning_snapshot_requires_explicit_side_effect_free_provider_opt_in() {
2080        struct RuntimeOnlyProvider(Arc<dyn Function>);
2081        impl FunctionProvider for RuntimeOnlyProvider {
2082            fn get_function(&self, _ns: &str, _name: &str) -> Option<Arc<dyn Function>> {
2083                Some(Arc::clone(&self.0))
2084            }
2085        }
2086
2087        let ns = "__PLANNING_FAIL_CLOSED__";
2088        register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
2089        let result = RegistryPlanningSnapshot::capture_for_requests(
2090            &RuntimeOnlyProvider(planning_fn(ns, "TARGET", &[], FnCaps::empty())),
2091            [(ns.to_string(), "TARGET".to_string(), 1)],
2092        );
2093        assert_eq!(
2094            result.err(),
2095            Some(PlanningSnapshotError::ProviderRevisionUnavailable)
2096        );
2097    }
2098
2099    #[test]
2100    fn planning_snapshot_preserves_runtime_override_without_global_semantics() {
2101        struct OverrideProvider(Arc<dyn Function>);
2102        impl FunctionProvider for OverrideProvider {
2103            fn planning_semantic_revision(&self) -> Option<u64> {
2104                Some(0)
2105            }
2106            fn get_function(&self, _ns: &str, _name: &str) -> Option<Arc<dyn Function>> {
2107                Some(Arc::clone(&self.0))
2108            }
2109            fn get_function_for_planning(
2110                &self,
2111                _ns: &str,
2112                _name: &str,
2113            ) -> Option<Arc<dyn Function>> {
2114                Some(Arc::clone(&self.0))
2115            }
2116        }
2117
2118        let ns = "__PLANNING_OVERRIDE__";
2119        register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
2120        let global = get(ns, "TARGET").unwrap();
2121        let runtime = planning_fn(ns, "TARGET", &[], FnCaps::MAY_SPILL);
2122        let provider = OverrideProvider(Arc::clone(&runtime));
2123        let snapshot = RegistryPlanningSnapshot::capture_for_requests(
2124            &provider,
2125            [(ns.to_string(), "TARGET".to_string(), 1)],
2126        )
2127        .unwrap();
2128
2129        let captured = snapshot.get_function(ns, "TARGET").unwrap();
2130        assert!(Arc::ptr_eq(&captured, &runtime));
2131        assert!(!Arc::ptr_eq(&captured, &global));
2132        assert!(
2133            snapshot
2134                .function_semantic_identity(ns, "TARGET", 1)
2135                .is_none()
2136        );
2137        assert_eq!(snapshot.functions.len(), 1);
2138        assert_eq!(
2139            snapshot.function_capabilities(ns, "TARGET"),
2140            Some(FnCaps::MAY_SPILL)
2141        );
2142        assert_eq!(snapshot.capabilities.len(), 1);
2143        assert!(snapshot.identities.is_empty());
2144        assert!(snapshot.get_function(ns, "UNREQUESTED").is_none());
2145    }
2146
2147    #[test]
2148    fn planning_snapshot_retries_provider_revision_flip_to_runtime_override() {
2149        struct FlippingProvider {
2150            function: Arc<RwLock<Arc<dyn Function>>>,
2151            revision: Arc<AtomicU64>,
2152        }
2153        impl FunctionProvider for FlippingProvider {
2154            fn planning_semantic_revision(&self) -> Option<u64> {
2155                Some(self.revision.load(Ordering::Acquire))
2156            }
2157            fn get_function(&self, _ns: &str, _name: &str) -> Option<Arc<dyn Function>> {
2158                Some(Arc::clone(&self.function.read().unwrap()))
2159            }
2160            fn get_function_for_planning(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
2161                self.get_function(ns, name)
2162            }
2163        }
2164
2165        let ns = "__PLANNING_PROVIDER_FLIP__";
2166        register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
2167        let global = get(ns, "TARGET").unwrap();
2168        let override_function = planning_fn(ns, "TARGET", &[], FnCaps::MAY_SPILL);
2169        let function = Arc::new(RwLock::new(global));
2170        let revision = Arc::new(AtomicU64::new(0));
2171        let provider = FlippingProvider {
2172            function: Arc::clone(&function),
2173            revision: Arc::clone(&revision),
2174        };
2175        let requests = [(ns.to_string(), "TARGET".to_string(), 1)];
2176        let snapshot =
2177            RegistryPlanningSnapshot::capture_with_hook(&provider, &requests, 2, |attempt| {
2178                if attempt == 0 {
2179                    *function.write().unwrap() = Arc::clone(&override_function);
2180                    revision.fetch_add(1, Ordering::AcqRel);
2181                }
2182            })
2183            .unwrap();
2184
2185        assert_eq!(snapshot.provider_revision(), Some(1));
2186        assert!(Arc::ptr_eq(
2187            &snapshot.get_function(ns, "TARGET").unwrap(),
2188            &override_function
2189        ));
2190        assert!(
2191            snapshot
2192                .function_semantic_identity(ns, "TARGET", 1)
2193                .is_none()
2194        );
2195    }
2196
2197    #[test]
2198    fn planning_snapshot_capture_tolerates_unrelated_concurrent_registrations() {
2199        // Regression test for the flake in the provider-revision test family.
2200        //
2201        // A capture used to be validated against the global `semantic_epoch`,
2202        // so registering *any* function on another thread invalidated it. In
2203        // the test suite that surfaced as an intermittent failure that moved
2204        // between tests depending on which happened to be running in parallel;
2205        // in production it would surface as spurious FormulaPlane fallbacks in
2206        // any application that registers custom functions while evaluating.
2207        //
2208        // The churn below never touches the requested key, so a correct capture
2209        // must succeed even with the tightest useful attempt budget.
2210        // The capture hook fires between the registry copy and the validation
2211        // step -- exactly where a concurrent registration would land -- so this
2212        // reproduces the race deterministically rather than relying on thread
2213        // interleaving.
2214        let ns = "__PLANNING_UNRELATED_CHURN__";
2215        register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
2216        let requests = [(ns.to_string(), "TARGET".to_string(), 1)];
2217
2218        let mut unrelated_registrations = 0usize;
2219        let snapshot = RegistryPlanningSnapshot::capture_with_hook(
2220            &GlobalRegistryFunctionProvider,
2221            &requests,
2222            2,
2223            |_| {
2224                // `register_function` is untrusted, so unlike a repeated builtin
2225                // registration it always publishes a semantic change. Firing on
2226                // every attempt means a global-epoch check can never converge.
2227                register_function(planning_fn(
2228                    "__PLANNING_UNRELATED_CHURN_OTHER__",
2229                    "OTHER",
2230                    &[],
2231                    FnCaps::empty(),
2232                ));
2233                unrelated_registrations += 1;
2234            },
2235        )
2236        .expect("unrelated registrations must not invalidate a planning snapshot capture");
2237
2238        assert!(unrelated_registrations > 0, "hook must have registered");
2239        assert!(Arc::ptr_eq(
2240            &snapshot.get_function(ns, "TARGET").unwrap(),
2241            &get(ns, "TARGET").unwrap(),
2242        ));
2243    }
2244
2245    #[test]
2246    fn planning_snapshot_capture_retries_and_fails_deterministically() {
2247        let ns = "__PLANNING_RACE__";
2248        register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
2249        let requests = [(ns.to_string(), "TARGET".to_string(), 1)];
2250        let before = semantic_epoch();
2251        let retried = RegistryPlanningSnapshot::capture_with_hook(
2252            &GlobalRegistryFunctionProvider,
2253            &requests,
2254            100,
2255            |attempt| {
2256                if attempt == 0 {
2257                    register_function(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
2258                }
2259            },
2260        )
2261        .unwrap();
2262        assert!(retried.epoch() > before);
2263
2264        let failed = RegistryPlanningSnapshot::capture_with_hook(
2265            &GlobalRegistryFunctionProvider,
2266            &requests,
2267            2,
2268            |_| register_function(planning_fn(ns, "TARGET", &[], FnCaps::empty())),
2269        );
2270        assert_eq!(
2271            failed.err(),
2272            Some(PlanningSnapshotError::RegistryChangedDuringCapture)
2273        );
2274    }
2275
2276    #[test]
2277    fn planning_snapshot_nested_function_authority_matches_global_registry() {
2278        let ns = "";
2279        register_builtin(planning_fn(ns, "__PLAN_OUTER__", &[], FnCaps::empty()));
2280        register_builtin(planning_fn(ns, "__PLAN_INNER__", &[], FnCaps::empty()));
2281        let requests = [
2282            (String::new(), "__PLAN_OUTER__".to_string(), 1),
2283            (String::new(), "_xlfn.__PLAN_INNER__".to_string(), 1),
2284        ];
2285        let snapshot = RegistryPlanningSnapshot::capture_for_requests(
2286            &GlobalRegistryFunctionProvider,
2287            requests,
2288        )
2289        .unwrap();
2290        let ast =
2291            formualizer_parse::parser::parse("=__PLAN_OUTER__(_xlfn.__PLAN_INNER__(A1))").unwrap();
2292        let frozen = crate::engine::template::canonical::canonicalize_template_with_provider(
2293            &ast,
2294            2,
2295            2,
2296            Some(&snapshot),
2297        );
2298        let global = crate::engine::template::canonical::canonicalize_template_with_provider(
2299            &ast,
2300            2,
2301            2,
2302            Some(&GlobalRegistryFunctionProvider),
2303        );
2304        assert_eq!(frozen, global);
2305        assert!(frozen.labels.is_authority_supported());
2306    }
2307
2308    #[test]
2309    fn parallel_snapshot_capture_and_prefix_resolution_does_not_deadlock() {
2310        let ns = "__PLANNING_PARALLEL__";
2311        register_builtin(planning_fn(ns, "TARGET", &["ALIAS"], FnCaps::empty()));
2312        let (send, receive) = std::sync::mpsc::channel();
2313        std::thread::spawn(move || {
2314            let mut workers = Vec::new();
2315            for worker in 0..4 {
2316                workers.push(std::thread::spawn(move || {
2317                    for iteration in 0..100 {
2318                        if worker == 0 && iteration % 10 == 0 {
2319                            register_function(planning_fn(
2320                                ns,
2321                                "TARGET",
2322                                &["ALIAS"],
2323                                FnCaps::empty(),
2324                            ));
2325                        }
2326                        let _ = RegistryPlanningSnapshot::capture_for_requests(
2327                            &GlobalRegistryFunctionProvider,
2328                            [(ns.to_string(), "_xlfn.alias".to_string(), 1)],
2329                        );
2330                        let _ = get(ns, "_xlfn.alias");
2331                    }
2332                }));
2333            }
2334            for worker in workers {
2335                worker.join().unwrap();
2336            }
2337            send.send(()).unwrap();
2338        });
2339        receive
2340            .recv_timeout(std::time::Duration::from_secs(10))
2341            .expect("parallel registry planning timed out (possible lock inversion)");
2342    }
2343
2344    #[test]
2345    fn prefix_alias_publishing_does_not_bump_epoch() {
2346        let ns = "__PREFIX_EPOCH__";
2347        register_function(Arc::new(TestFn {
2348            ns,
2349            name: "TARGET",
2350            aliases: &[],
2351        }));
2352        let mut state = REGISTRY
2353            .write()
2354            .unwrap_or_else(|poisoned| poisoned.into_inner());
2355        let before = state.semantic_epoch;
2356        let name = "_XLFN.TARGET".to_string();
2357        let key = (ns.to_string(), name.clone());
2358        assert!(resolve_entry_in_state(&mut state, ns.to_string(), name, key.clone()).is_some());
2359        assert!(state.aliases.contains_key(&key));
2360        assert_eq!(state.semantic_epoch, before);
2361    }
2362
2363    #[test]
2364    fn empty_planning_snapshot_has_no_raw_capability_allocation() {
2365        let snapshot = RegistryPlanningSnapshot::capture_for_requests(
2366            &GlobalRegistryFunctionProvider,
2367            std::iter::empty::<(String, String, usize)>(),
2368        )
2369        .unwrap();
2370        assert!(snapshot.raw_capabilities.is_none());
2371    }
2372
2373    #[test]
2374    fn resolves_prefixes_aliases_and_direct_registration() {
2375        let ns = "__REG_PREFIX__";
2376        register_function(Arc::new(TestFn {
2377            ns,
2378            name: "FILTER",
2379            aliases: &["LEGACY"],
2380        }));
2381        assert_eq!(get(ns, "_xlfn._xlws.legacy").unwrap().name(), "FILTER");
2382        register_function(Arc::new(TestFn {
2383            ns,
2384            name: "_XLFN.FILTER",
2385            aliases: &[],
2386        }));
2387        assert_eq!(get(ns, "_xlfn.filter").unwrap().name(), "_XLFN.FILTER");
2388    }
2389
2390    #[test]
2391    fn trusted_replacement_records_removed_owned_alias_spelling() {
2392        let namespace = "__REG_STALE_ALIAS__";
2393        register_builtin(Arc::new(TestFn {
2394            ns: namespace,
2395            name: "TARGET",
2396            aliases: &["STALE_OWNED_ALIAS"],
2397        }));
2398        let before = semantic_epoch();
2399        register_function(Arc::new(TestFn {
2400            ns: namespace,
2401            name: "TARGET",
2402            aliases: &["NEW_OWNED_ALIAS"],
2403        }));
2404        let changes = semantic_changes_since(before);
2405        assert!(
2406            changes
2407                .keys
2408                .contains(&(namespace.to_string(), "STALE_OWNED_ALIAS".to_string()))
2409        );
2410        assert!(
2411            changes
2412                .keys
2413                .contains(&(namespace.to_string(), "NEW_OWNED_ALIAS".to_string()))
2414        );
2415    }
2416
2417    #[test]
2418    fn alias_requests_keep_the_spelling_used_by_the_formula() {
2419        let ns = "__ALIAS_REQUEST_SPELLING__";
2420        register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
2421        register_alias(ns, "FORMULA_ALIAS", ns, "TARGET");
2422        let request = (ns.to_string(), "FORMULA_ALIAS".to_string(), 1);
2423        let snapshot = RegistryPlanningSnapshot::capture_for_requests(
2424            &GlobalRegistryFunctionProvider,
2425            [request.clone()],
2426        )
2427        .unwrap();
2428
2429        assert_eq!(snapshot.requests.as_ref(), &[request]);
2430        assert_eq!(
2431            snapshot
2432                .function_semantic_identity(ns, "FORMULA_ALIAS", 1)
2433                .unwrap()
2434                .canonical_name,
2435            "TARGET"
2436        );
2437    }
2438
2439    #[test]
2440    fn alias_mutations_affect_alias_requests_but_not_direct_target_requests() {
2441        let ns = "__ALIAS_CHANGE_SCOPE__";
2442        register_builtin(planning_fn(ns, "OLD_TARGET", &[], FnCaps::empty()));
2443        register_builtin(planning_fn(ns, "NEW_TARGET", &[], FnCaps::empty()));
2444
2445        let add_epoch = semantic_epoch();
2446        register_alias(ns, "ADDED_ALIAS", ns, "OLD_TARGET");
2447        assert!(semantic_changes_affect_requests_since(
2448            add_epoch,
2449            [(ns.to_string(), "ADDED_ALIAS".to_string(), 1)]
2450        ));
2451        assert!(!semantic_changes_affect_requests_since(
2452            add_epoch,
2453            [(ns.to_string(), "OLD_TARGET".to_string(), 1)]
2454        ));
2455
2456        register_alias(ns, "RETARGETED_ALIAS", ns, "OLD_TARGET");
2457        let retarget_epoch = semantic_epoch();
2458        register_alias(ns, "RETARGETED_ALIAS", ns, "NEW_TARGET");
2459        assert!(semantic_changes_affect_requests_since(
2460            retarget_epoch,
2461            [(ns.to_string(), "RETARGETED_ALIAS".to_string(), 1)]
2462        ));
2463        assert!(semantic_changes_affect_requests_since(
2464            retarget_epoch,
2465            [(ns.to_string(), "_xlfn.RETARGETED_ALIAS".to_string(), 1)]
2466        ));
2467        for target in ["OLD_TARGET", "NEW_TARGET"] {
2468            assert!(!semantic_changes_affect_requests_since(
2469                retarget_epoch,
2470                [(ns.to_string(), target.to_string(), 1)]
2471            ));
2472        }
2473
2474        register_alias(ns, "REMOVED_ALIAS", ns, "OLD_TARGET");
2475        let remove_epoch = semantic_epoch();
2476        {
2477            let mut state = REGISTRY
2478                .write()
2479                .unwrap_or_else(|poisoned| poisoned.into_inner());
2480            let alias_key = (ns.to_string(), "REMOVED_ALIAS".to_string());
2481            assert!(state.aliases.remove(&alias_key).is_some());
2482            publish_semantic_change(&mut state, [alias_key]);
2483        }
2484        assert!(semantic_changes_affect_requests_since(
2485            remove_epoch,
2486            [(ns.to_string(), "REMOVED_ALIAS".to_string(), 1)]
2487        ));
2488        assert!(!semantic_changes_affect_requests_since(
2489            remove_epoch,
2490            [(ns.to_string(), "OLD_TARGET".to_string(), 1)]
2491        ));
2492    }
2493
2494    #[test]
2495    fn request_change_check_stays_conservative_after_log_truncation() {
2496        let mut state = RegistryState::default();
2497        let before = state.semantic_epoch;
2498        for index in 0..=1_024 {
2499            advance_semantic_log(&mut state, [(String::new(), format!("UNRELATED_{index}"))]);
2500        }
2501
2502        assert!(semantic_changes_affect_requests_in_state(
2503            &state,
2504            before,
2505            [(String::new(), "TARGET".to_string(), 1)]
2506        ));
2507    }
2508
2509    #[test]
2510    fn replacement_advances_semantic_generation() {
2511        let ns = "__REG_GENERATION__";
2512        register_function(Arc::new(TestFn {
2513            ns,
2514            name: "F",
2515            aliases: &[],
2516        }));
2517        let first = resolve(ns, "F").unwrap().semantics.generation;
2518        let epoch = semantic_epoch();
2519        register_function(Arc::new(TestFn {
2520            ns,
2521            name: "F",
2522            aliases: &[],
2523        }));
2524        let second = resolve(ns, "F").unwrap().semantics.generation;
2525        assert!(second > first);
2526        let changes = semantic_changes_since(epoch);
2527        assert!(changes.epoch > epoch);
2528        assert!(changes.keys.contains(&(ns.to_string(), "F".to_string())));
2529    }
2530
2531    struct PanickingSchemaFn;
2532
2533    impl Function for PanickingSchemaFn {
2534        fn name(&self) -> &'static str {
2535            "PANICKING_SCHEMA"
2536        }
2537        fn semantic_contract(&self, _arity: usize) -> Option<FunctionSemanticContract> {
2538            Some(FunctionSemanticContract::trusted_builtin_default(None))
2539        }
2540        fn arg_schema(&self) -> &'static [crate::args::ArgSchema] {
2541            panic!("bad schema")
2542        }
2543        fn eval<'a, 'b, 'c>(
2544            &self,
2545            _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
2546            _ctx: &dyn crate::traits::FunctionContext<'b>,
2547        ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
2548            unreachable!()
2549        }
2550    }
2551
2552    #[test]
2553    fn schema_panic_as_sole_defect_is_non_panicking_and_fails_closed() {
2554        register_function(Arc::new(PanickingSchemaFn));
2555        let semantics = resolve("", "PANICKING_SCHEMA").unwrap().semantics;
2556        assert!(semantics.contract.is_none());
2557        assert!(
2558            semantics
2559                .issues
2560                .contains(&SemanticConformanceIssue::ArgumentSchemaPanicked)
2561        );
2562        assert_eq!(
2563            semantics.issues,
2564            vec![SemanticConformanceIssue::ArgumentSchemaPanicked]
2565        );
2566        assert!(!semantics.conforms());
2567    }
2568
2569    #[test]
2570    fn every_registered_builtin_has_a_conforming_semantic_contract() {
2571        crate::builtins::load_builtins();
2572        let builtins: Vec<_> = snapshot_semantics()
2573            .into_iter()
2574            .filter(|entry| entry.semantics.trusted_builtin)
2575            .collect();
2576        assert!(builtins.len() > 100);
2577        let rejected: Vec<_> = builtins
2578            .iter()
2579            .filter(|entry| !entry.semantics.conforms())
2580            .map(|entry| {
2581                (
2582                    &entry.namespace,
2583                    &entry.canonical_name,
2584                    &entry.semantics.issues,
2585                )
2586            })
2587            .collect();
2588        assert!(rejected.is_empty(), "non-conforming builtins: {rejected:?}");
2589    }
2590
2591    #[test]
2592    fn semantic_contract_is_context_and_arity_aware() {
2593        crate::builtins::lookup::register_builtins();
2594        let row_without_arg = resolve_for_arity("", "ROW", 0).unwrap();
2595        let row_with_arg = resolve_for_arity("", "ROW", 1).unwrap();
2596        assert_eq!(
2597            row_without_arg.semantics.contract.unwrap().context,
2598            crate::function_contract::FunctionContextDependence::PlacementDependent
2599        );
2600        assert_eq!(
2601            row_with_arg.semantics.contract.unwrap().context,
2602            crate::function_contract::FunctionContextDependence::None
2603        );
2604    }
2605
2606    #[test]
2607    fn semantic_identity_encodes_effective_by_reference_roles_for_call_arity() {
2608        crate::builtins::load_builtins();
2609        let provider = GlobalRegistryFunctionProvider;
2610        let sum = resolve_semantic_identity(&provider, "", "SUM", 3).unwrap();
2611        assert_eq!(sum.argument_by_ref, vec![false, false, false]);
2612
2613        let row = resolve_semantic_identity(&provider, "", "ROW", 1).unwrap();
2614        assert_eq!(row.argument_by_ref, vec![true]);
2615    }
2616
2617    struct ExplicitSafeCustomFn;
2618
2619    impl Function for ExplicitSafeCustomFn {
2620        fn name(&self) -> &'static str {
2621            "EXPLICIT_SAFE_CUSTOM"
2622        }
2623        fn semantic_contract(&self, _arity: usize) -> Option<FunctionSemanticContract> {
2624            Some(FunctionSemanticContract::trusted_builtin_default(None))
2625        }
2626        fn eval<'a, 'b, 'c>(
2627            &self,
2628            _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
2629            _ctx: &dyn crate::traits::FunctionContext<'b>,
2630        ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
2631            unreachable!()
2632        }
2633    }
2634
2635    struct MismatchedPrecisionCustomFn;
2636
2637    impl Function for MismatchedPrecisionCustomFn {
2638        fn name(&self) -> &'static str {
2639            "MISMATCHED_PRECISION_CUSTOM"
2640        }
2641        fn dependency_contract(
2642            &self,
2643            arity: usize,
2644        ) -> Option<crate::function_contract::FunctionDependencyContract> {
2645            crate::function_contract::FunctionDependencyContract::static_scalar_all_args(arity)
2646        }
2647        fn semantic_contract(&self, _arity: usize) -> Option<FunctionSemanticContract> {
2648            Some(FunctionSemanticContract::trusted_builtin_default(None))
2649        }
2650        fn min_args(&self) -> usize {
2651            1
2652        }
2653        fn arg_schema(&self) -> &'static [crate::args::ArgSchema] {
2654            static SCHEMA: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
2655                std::sync::LazyLock::new(|| vec![crate::args::ArgSchema::any()]);
2656            &SCHEMA
2657        }
2658        fn eval<'a, 'b, 'c>(
2659            &self,
2660            _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
2661            _ctx: &dyn crate::traits::FunctionContext<'b>,
2662        ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
2663            unreachable!()
2664        }
2665    }
2666
2667    #[test]
2668    fn explicit_precision_must_equal_dependency_contract() {
2669        register_function(Arc::new(MismatchedPrecisionCustomFn));
2670        let semantics = resolve_for_arity("", "MISMATCHED_PRECISION_CUSTOM", 1)
2671            .unwrap()
2672            .semantics;
2673        assert!(semantics.contract.is_none());
2674        assert!(
2675            semantics
2676                .issues
2677                .contains(&SemanticConformanceIssue::PrecisionContractMismatch)
2678        );
2679    }
2680
2681    #[test]
2682    fn explicit_custom_semantics_can_conform_without_becoming_trusted() {
2683        register_function(Arc::new(ExplicitSafeCustomFn));
2684        let semantics = resolve_for_arity("", "EXPLICIT_SAFE_CUSTOM", 0)
2685            .unwrap()
2686            .semantics;
2687        assert!(!semantics.trusted_builtin);
2688        assert!(semantics.conforms());
2689    }
2690
2691    #[test]
2692    fn concurrent_replacements_leave_only_final_owned_alias() {
2693        let ns = "__REG_CONCURRENT__";
2694        register_function(Arc::new(TestFn {
2695            ns,
2696            name: "TARGET",
2697            aliases: &["INITIAL"],
2698        }));
2699        let mut workers = Vec::new();
2700        for alias in ["A", "B", "C", "D"] {
2701            workers.push(std::thread::spawn(move || {
2702                for _ in 0..100 {
2703                    let aliases: &'static [&'static str] = Box::leak(Box::new([alias]));
2704                    register_function(Arc::new(TestFn {
2705                        ns,
2706                        name: "TARGET",
2707                        aliases,
2708                    }));
2709                    assert_eq!(get(ns, "TARGET").unwrap().name(), "TARGET");
2710                }
2711            }));
2712        }
2713        for worker in workers {
2714            worker.join().unwrap();
2715        }
2716        register_function(Arc::new(TestFn {
2717            ns,
2718            name: "TARGET",
2719            aliases: &["FINAL"],
2720        }));
2721        for stale in ["INITIAL", "A", "B", "C", "D"] {
2722            assert!(get(ns, stale).is_none());
2723        }
2724        assert!(get(ns, "FINAL").is_some());
2725    }
2726
2727    #[test]
2728    fn independent_exception_inventory_matches_builtin_caps_and_context() {
2729        crate::builtins::load_builtins();
2730        for name in [
2731            "RAND",
2732            "RANDBETWEEN",
2733            "RANDARRAY",
2734            "TODAY",
2735            "NOW",
2736            "OFFSET",
2737            "INDIRECT",
2738        ] {
2739            assert!(
2740                get("", name).unwrap().caps().contains(FnCaps::VOLATILE),
2741                "{name}"
2742            );
2743        }
2744        for name in ["OFFSET", "INDIRECT"] {
2745            assert!(
2746                get("", name)
2747                    .unwrap()
2748                    .caps()
2749                    .contains(FnCaps::DYNAMIC_DEPENDENCY),
2750                "{name}"
2751            );
2752        }
2753        for name in ["INDEX", "OFFSET", "INDIRECT", "CHOOSE"] {
2754            assert!(
2755                get("", name)
2756                    .unwrap()
2757                    .caps()
2758                    .contains(FnCaps::RETURNS_REFERENCE),
2759                "{name}"
2760            );
2761        }
2762        for name in ["LET", "LAMBDA"] {
2763            assert!(
2764                get("", name)
2765                    .unwrap()
2766                    .caps()
2767                    .contains(FnCaps::LOCAL_ENVIRONMENT),
2768                "{name}"
2769            );
2770        }
2771        for name in [
2772            "IF",
2773            "IFERROR",
2774            "IFNA",
2775            "IFS",
2776            "SWITCH",
2777            "CHOOSE",
2778            "FILTER",
2779            "UNIQUE",
2780            "SEQUENCE",
2781            "TRANSPOSE",
2782            "TAKE",
2783            "DROP",
2784            "SORT",
2785            "SORTBY",
2786            "RANDARRAY",
2787            "HSTACK",
2788            "VSTACK",
2789            "TOCOL",
2790            "TOROW",
2791            "CHOOSECOLS",
2792            "CHOOSEROWS",
2793            "FREQUENCY",
2794            "LINEST",
2795            "TREND",
2796            "GROWTH",
2797            "LOGEST",
2798            "MODE.MULT",
2799            "TEXTSPLIT",
2800        ] {
2801            assert!(
2802                get("", name).unwrap().caps().contains(FnCaps::MAY_SPILL),
2803                "{name}"
2804            );
2805        }
2806        const SHORT_CIRCUIT: &[&str] = &[
2807            "IF", "IFERROR", "IFNA", "IFS", "SWITCH", "CHOOSE", "LET", "LAMBDA", "AND", "OR",
2808        ];
2809        let observed_short_circuit: std::collections::BTreeSet<_> = snapshot_registered()
2810            .into_iter()
2811            .filter(|(namespace, _, function)| {
2812                namespace.is_empty() && function.caps().contains(FnCaps::SHORT_CIRCUIT)
2813            })
2814            .map(|(_, name, _)| name)
2815            .collect();
2816        let expected_short_circuit: std::collections::BTreeSet<_> = SHORT_CIRCUIT
2817            .iter()
2818            .map(|name| (*name).to_string())
2819            .collect();
2820        assert_eq!(observed_short_circuit, expected_short_circuit);
2821        for name in SHORT_CIRCUIT {
2822            let contract = resolve_for_arity("", name, get("", name).unwrap().min_args())
2823                .unwrap()
2824                .semantics
2825                .contract
2826                .unwrap();
2827            assert_eq!(
2828                contract.evaluation,
2829                FunctionEvaluationSemantics::ShortCircuit,
2830                "{name}"
2831            );
2832        }
2833        assert_eq!(
2834            resolve_for_arity("", "CHOOSE", 2)
2835                .unwrap()
2836                .semantics
2837                .contract
2838                .unwrap()
2839                .result,
2840            FunctionResultSemantics::MayReturnReferenceAndSpill
2841        );
2842        for name in ["ROW", "COLUMN"] {
2843            let contract = resolve_for_arity("", name, 0)
2844                .unwrap()
2845                .semantics
2846                .contract
2847                .unwrap();
2848            assert_eq!(
2849                contract.context,
2850                crate::function_contract::FunctionContextDependence::PlacementDependent,
2851                "{name}"
2852            );
2853            assert_eq!(
2854                resolve_for_arity("", name, 1)
2855                    .unwrap()
2856                    .semantics
2857                    .contract
2858                    .unwrap()
2859                    .context,
2860                crate::function_contract::FunctionContextDependence::None,
2861                "{name} with argument"
2862            );
2863        }
2864        for name in ["CELL", "ISFORMULA", "FORMULATEXT", "SHEET", "SHEETS"] {
2865            let contract = resolve_for_arity("", name, get("", name).unwrap().min_args())
2866                .unwrap()
2867                .semantics
2868                .contract
2869                .unwrap();
2870            assert_eq!(
2871                contract.context,
2872                crate::function_contract::FunctionContextDependence::WorkbookMetadata,
2873                "{name}"
2874            );
2875        }
2876    }
2877
2878    struct NamePanicFn;
2879    impl Function for NamePanicFn {
2880        fn name(&self) -> &'static str {
2881            panic!("name")
2882        }
2883        fn eval<'a, 'b, 'c>(
2884            &self,
2885            _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
2886            _ctx: &dyn crate::traits::FunctionContext<'b>,
2887        ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
2888            unreachable!()
2889        }
2890    }
2891    struct NamespacePanicFn;
2892    impl Function for NamespacePanicFn {
2893        fn name(&self) -> &'static str {
2894            "NS_PANIC"
2895        }
2896        fn namespace(&self) -> &'static str {
2897            panic!("namespace")
2898        }
2899        fn eval<'a, 'b, 'c>(
2900            &self,
2901            _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
2902            _ctx: &dyn crate::traits::FunctionContext<'b>,
2903        ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
2904            unreachable!()
2905        }
2906    }
2907
2908    #[test]
2909    fn canonical_metadata_panics_decline_registration_without_unwinding() {
2910        assert_eq!(
2911            try_register_function(Arc::new(NamePanicFn)),
2912            Err(RegistrationError::NameMetadataPanicked)
2913        );
2914        assert_eq!(
2915            try_register_function(Arc::new(NamespacePanicFn)),
2916            Err(RegistrationError::NamespaceMetadataPanicked)
2917        );
2918        register_function(Arc::new(NamePanicFn));
2919        register_function(Arc::new(NamespacePanicFn));
2920        assert!(get("", "NS_PANIC").is_none());
2921    }
2922
2923    #[derive(Clone, Copy)]
2924    enum BadSchemaKind {
2925        TooLarge,
2926        Repeating,
2927        MinDisagreement,
2928        RequiredCount,
2929        TooManyRequired,
2930        RepeatWidth,
2931    }
2932    struct BadSchemaFn {
2933        name: &'static str,
2934        kind: BadSchemaKind,
2935    }
2936    impl Function for BadSchemaFn {
2937        fn name(&self) -> &'static str {
2938            self.name
2939        }
2940        fn min_args(&self) -> usize {
2941            if matches!(
2942                self.kind,
2943                BadSchemaKind::MinDisagreement
2944                    | BadSchemaKind::RequiredCount
2945                    | BadSchemaKind::RepeatWidth
2946            ) {
2947                2
2948            } else {
2949                1
2950            }
2951        }
2952        fn variadic(&self) -> bool {
2953            matches!(self.kind, BadSchemaKind::RepeatWidth)
2954        }
2955        fn semantic_contract(&self, _arity: usize) -> Option<FunctionSemanticContract> {
2956            Some(FunctionSemanticContract::trusted_builtin_default(None))
2957        }
2958        fn arg_schema(&self) -> &'static [crate::args::ArgSchema] {
2959            static ONE: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
2960                std::sync::LazyLock::new(|| vec![crate::args::ArgSchema::any()]);
2961            static BAD_REPEAT: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
2962                std::sync::LazyLock::new(|| {
2963                    let mut arg = crate::args::ArgSchema::any();
2964                    arg.repeating = Some(0);
2965                    vec![arg]
2966                });
2967            static REQUIRED_COUNT: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
2968                std::sync::LazyLock::new(|| {
2969                    let mut optional = crate::args::ArgSchema::any();
2970                    optional.required = false;
2971                    vec![crate::args::ArgSchema::any(), optional]
2972                });
2973            static REPEAT_WIDTH: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
2974                std::sync::LazyLock::new(|| {
2975                    let first = crate::args::ArgSchema::any();
2976                    let mut second = crate::args::ArgSchema::any();
2977                    second.repeating = Some(2);
2978                    vec![first, second]
2979                });
2980            match self.kind {
2981                BadSchemaKind::TooLarge => &ONE,
2982                BadSchemaKind::Repeating => &BAD_REPEAT,
2983                BadSchemaKind::MinDisagreement => &[],
2984                BadSchemaKind::RequiredCount => &REQUIRED_COUNT,
2985                BadSchemaKind::TooManyRequired => &REPEAT_WIDTH,
2986                BadSchemaKind::RepeatWidth => &REPEAT_WIDTH,
2987            }
2988        }
2989        fn eval<'a, 'b, 'c>(
2990            &self,
2991            _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
2992            _ctx: &dyn crate::traits::FunctionContext<'b>,
2993        ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
2994            unreachable!()
2995        }
2996    }
2997
2998    #[test]
2999    fn malformed_arity_and_schema_contracts_fail_closed() {
3000        for (name, kind, arity) in [
3001            ("TOO_LARGE", BadSchemaKind::TooLarge, 2),
3002            ("BAD_REPEAT", BadSchemaKind::Repeating, 1),
3003            ("MIN_DISAGREEMENT", BadSchemaKind::MinDisagreement, 2),
3004            ("REQUIRED_COUNT", BadSchemaKind::RequiredCount, 2),
3005            ("TOO_MANY_REQUIRED", BadSchemaKind::TooManyRequired, 1),
3006            ("REPEAT_WIDTH", BadSchemaKind::RepeatWidth, 3),
3007        ] {
3008            register_function(Arc::new(BadSchemaFn { name, kind }));
3009            let semantics = resolve_for_arity("", name, arity).unwrap().semantics;
3010            assert!(semantics.contract.is_none(), "{name}");
3011            assert!(
3012                semantics
3013                    .issues
3014                    .contains(&SemanticConformanceIssue::AritySchemaMismatch),
3015                "{name}: {:?}",
3016                semantics.issues
3017            );
3018        }
3019    }
3020
3021    #[test]
3022    fn valid_optional_and_width_n_repeating_schemas_conform() {
3023        let required = crate::args::ArgSchema::any();
3024        let mut optional = crate::args::ArgSchema::any();
3025        optional.required = false;
3026        assert!(schema_allows_arity(
3027            &[required.clone(), optional],
3028            1,
3029            false,
3030            2,
3031            true
3032        ));
3033
3034        let mut repeat_end = crate::args::ArgSchema::any();
3035        repeat_end.repeating = Some(2);
3036        let repeating = [required, repeat_end];
3037        assert!(schema_allows_arity(&repeating, 2, true, 4, true));
3038        assert!(!schema_allows_arity(&repeating, 2, true, 3, true));
3039    }
3040
3041    #[test]
3042    fn replacement_readers_observe_generation_and_epoch_atomically() {
3043        let ns = "__REG_SNAPSHOT_RACE__";
3044        register_builtin(Arc::new(TestFn {
3045            ns,
3046            name: "TARGET",
3047            aliases: &[],
3048        }));
3049        let (initial_epoch, initial) = resolve_with_epoch(ns, "TARGET").unwrap();
3050        let initial_generation = initial.semantics.generation;
3051        let barrier = Arc::new(std::sync::Barrier::new(5));
3052        let mut readers = Vec::new();
3053        for _ in 0..4 {
3054            let barrier = Arc::clone(&barrier);
3055            readers.push(std::thread::spawn(move || {
3056                barrier.wait();
3057                for _ in 0..1_000 {
3058                    let (epoch, resolved) = resolve_with_epoch(ns, "TARGET").unwrap();
3059                    if resolved.semantics.generation != initial_generation {
3060                        assert!(epoch > initial_epoch);
3061                    }
3062                }
3063            }));
3064        }
3065        barrier.wait();
3066        register_function(Arc::new(TestFn {
3067            ns,
3068            name: "TARGET",
3069            aliases: &[],
3070        }));
3071        for reader in readers {
3072            reader.join().unwrap();
3073        }
3074        let (epoch, resolved) = resolve_with_epoch(ns, "TARGET").unwrap();
3075        assert!(epoch > initial_epoch);
3076        assert!(resolved.semantics.generation > initial_generation);
3077    }
3078}