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