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#[inline]
95fn norm<S: AsRef<str>>(s: S) -> String {
96    s.as_ref().to_uppercase()
97}
98
99pub fn semantic_epoch() -> u64 {
100    REGISTRY
101        .read()
102        .unwrap_or_else(|poisoned| poisoned.into_inner())
103        .semantic_epoch
104}
105
106pub(crate) struct SemanticEpochReadGuard(std::sync::RwLockReadGuard<'static, RegistryState>);
107
108impl SemanticEpochReadGuard {
109    pub(crate) fn epoch(&self) -> u64 {
110        self.0.semantic_epoch
111    }
112
113    pub(crate) fn semantic_changes_affect_requests_since(
114        &self,
115        epoch: u64,
116        requests: impl IntoIterator<Item = (String, String, usize)>,
117    ) -> bool {
118        semantic_changes_affect_requests_in_state(&self.0, epoch, requests)
119    }
120}
121
122pub(crate) fn semantic_epoch_read_guard() -> SemanticEpochReadGuard {
123    SemanticEpochReadGuard(
124        REGISTRY
125            .read()
126            .unwrap_or_else(|poisoned| poisoned.into_inner()),
127    )
128}
129
130pub(crate) struct SemanticChanges {
131    pub(crate) epoch: u64,
132    pub(crate) complete: bool,
133    pub(crate) keys: Vec<(String, String)>,
134}
135
136fn publish_semantic_change(state: &mut RegistryState, keys: impl IntoIterator<Item = RegistryKey>) {
137    state.semantic_epoch = state.semantic_epoch.saturating_add(1);
138    let epoch = state.semantic_epoch;
139    state
140        .semantic_changes
141        .push_back((epoch, keys.into_iter().collect()));
142    if state.semantic_changes.len() > 1_024 {
143        state.semantic_changes.pop_front();
144    }
145}
146
147pub(crate) fn semantic_changes_since(epoch: u64) -> SemanticChanges {
148    let state = REGISTRY
149        .read()
150        .unwrap_or_else(|poisoned| poisoned.into_inner());
151    semantic_changes_since_in_state(&state, epoch)
152}
153
154fn semantic_changes_since_in_state(state: &RegistryState, epoch: u64) -> SemanticChanges {
155    let complete = state
156        .semantic_changes
157        .front()
158        .is_none_or(|(oldest, _)| epoch.saturating_add(1) >= *oldest);
159    let keys = state
160        .semantic_changes
161        .iter()
162        .filter(|(changed_epoch, _)| *changed_epoch > epoch)
163        .flat_map(|(_, keys)| keys.iter().cloned())
164        .collect();
165    SemanticChanges {
166        epoch: state.semantic_epoch,
167        complete,
168        keys,
169    }
170}
171
172#[derive(Clone, Debug, PartialEq, Eq)]
173#[non_exhaustive]
174pub enum RegistrationError {
175    NameMetadataPanicked,
176    NamespaceMetadataPanicked,
177}
178
179pub fn try_register_function(function: Arc<dyn Function>) -> Result<(), RegistrationError> {
180    register(function, false)
181}
182
183pub fn register_function(function: Arc<dyn Function>) {
184    let _ = try_register_function(function);
185}
186pub(crate) fn register_builtin(function: Arc<dyn Function>) {
187    register(function, true).expect("builtin name and namespace metadata must not panic");
188}
189
190fn register(function: Arc<dyn Function>, trusted_builtin: bool) -> Result<(), RegistrationError> {
191    let namespace = catch_unwind(AssertUnwindSafe(|| function.namespace()))
192        .map_err(|_| RegistrationError::NamespaceMetadataPanicked)?;
193    let name = catch_unwind(AssertUnwindSafe(|| function.name()))
194        .map_err(|_| RegistrationError::NameMetadataPanicked)?;
195    let key = (norm(namespace), norm(name));
196    let generation = NEXT_GENERATION.fetch_add(1, Ordering::Relaxed);
197    let aliases = catch_unwind(AssertUnwindSafe(|| function.aliases().to_vec()));
198    let min_args = catch_unwind(AssertUnwindSafe(|| function.min_args()));
199    let initial_arity = min_args.as_ref().copied().unwrap_or(0);
200    let mut semantics = match min_args {
201        Ok(arity) => inspect_semantics(&function, trusted_builtin, generation, arity),
202        Err(_) => failed_resolution(
203            generation,
204            trusted_builtin,
205            SemanticConformanceIssue::ArityMetadataPanicked,
206        ),
207    };
208    let aliases = match aliases {
209        Ok(aliases) => aliases,
210        Err(_) => {
211            semantics
212                .issues
213                .push(SemanticConformanceIssue::AliasMetadataPanicked);
214            semantics.contract = None;
215            Vec::new()
216        }
217    };
218
219    let mut state = REGISTRY
220        .write()
221        .unwrap_or_else(|poisoned| poisoned.into_inner());
222    if trusted_builtin
223        && state
224            .registrations
225            .get(&key)
226            .is_some_and(|entry| entry.trusted_builtin)
227    {
228        return Ok(());
229    }
230    let previous = state
231        .registrations
232        .get(&key)
233        .map(|entry| (entry.generation, entry.trusted_builtin));
234    let mut changed_spellings = Vec::new();
235    if let Some((previous_generation, _)) = previous {
236        changed_spellings.extend(
237            state
238                .aliases
239                .iter()
240                .filter(|(_, alias)| {
241                    alias.owner.as_ref() == Some(&(key.clone(), previous_generation))
242                })
243                .map(|(alias_key, _)| alias_key.clone()),
244        );
245        state
246            .aliases
247            .retain(|_, alias| alias.owner.as_ref() != Some(&(key.clone(), previous_generation)));
248    }
249    state.registrations.insert(
250        key.clone(),
251        RegistryEntry {
252            function: Arc::clone(&function),
253            generation,
254            trusted_builtin,
255            semantics: semantics.clone(),
256            semantics_by_arity: Arc::new(RwLock::new(HashMap::from([(initial_arity, semantics)]))),
257        },
258    );
259    for alias in aliases {
260        if !alias.eq_ignore_ascii_case(&key.1) {
261            let alias_key = (key.0.clone(), norm(alias));
262            changed_spellings.push(alias_key.clone());
263            state.aliases.insert(
264                alias_key,
265                AliasEntry {
266                    target: key.clone(),
267                    owner: Some((key.clone(), generation)),
268                },
269            );
270        }
271    }
272    changed_spellings.push(key);
273    publish_semantic_change(&mut state, changed_spellings);
274    Ok(())
275}
276
277fn failed_resolution(
278    generation: u64,
279    trusted_builtin: bool,
280    issue: SemanticConformanceIssue,
281) -> SemanticContractResolution {
282    SemanticContractResolution {
283        contract: None,
284        generation,
285        trusted_builtin,
286        issues: vec![issue],
287    }
288}
289
290fn inspect_semantics(
291    function: &Arc<dyn Function>,
292    trusted_builtin: bool,
293    generation: u64,
294    arity: usize,
295) -> SemanticContractResolution {
296    inspect_semantics_with_identity_metadata(function, trusted_builtin, generation, arity).0
297}
298
299fn inspect_semantics_with_identity_metadata(
300    function: &Arc<dyn Function>,
301    trusted_builtin: bool,
302    generation: u64,
303    arity: usize,
304) -> (SemanticContractResolution, Option<(FnCaps, Vec<bool>)>) {
305    let mut issues = Vec::new();
306    let inspected_caps = inspected(
307        &mut issues,
308        SemanticConformanceIssue::CapabilityPanicked,
309        || function.caps(),
310    );
311    let caps = inspected_caps.unwrap_or_else(FnCaps::empty);
312    let precision = inspected(
313        &mut issues,
314        SemanticConformanceIssue::DependencyContractPanicked,
315        || function.dependency_contract(arity),
316    )
317    .flatten();
318    let explicit = inspected(
319        &mut issues,
320        SemanticConformanceIssue::SemanticContractPanicked,
321        || function.semantic_contract(arity),
322    )
323    .flatten();
324    let schema = inspected(
325        &mut issues,
326        SemanticConformanceIssue::ArgumentSchemaPanicked,
327        || function.arg_schema(),
328    );
329    let min_args = inspected(
330        &mut issues,
331        SemanticConformanceIssue::ArityMetadataPanicked,
332        || function.min_args(),
333    );
334    let variadic = inspected(
335        &mut issues,
336        SemanticConformanceIssue::VariadicMetadataPanicked,
337        || function.variadic(),
338    );
339    if let (Some(schema), Some(min_args), Some(variadic)) = (schema, min_args, variadic)
340        && !schema_allows_arity(schema, min_args, variadic, arity, !trusted_builtin)
341    {
342        issues.push(SemanticConformanceIssue::AritySchemaMismatch);
343    }
344    let contract =
345        explicit.or_else(|| trusted_builtin.then(|| trusted_contract_from_caps(caps, precision)));
346    if let Some(contract) = contract {
347        if contract.precision != precision {
348            issues.push(SemanticConformanceIssue::PrecisionContractMismatch);
349        }
350        if !precision_is_valid(contract, arity) {
351            issues.push(SemanticConformanceIssue::PrecisionContractInvalid);
352        }
353        check_capability(
354            &mut issues,
355            caps.contains(FnCaps::DYNAMIC_DEPENDENCY),
356            contract.dependency == FunctionDependencySemantics::Dynamic,
357            SemanticConformanceIssue::DynamicDependencyMismatch,
358        );
359        check_capability(
360            &mut issues,
361            caps.contains(FnCaps::SHORT_CIRCUIT),
362            contract.evaluation == FunctionEvaluationSemantics::ShortCircuit,
363            SemanticConformanceIssue::ShortCircuitMismatch,
364        );
365        check_capability(
366            &mut issues,
367            caps.contains(FnCaps::RETURNS_REFERENCE),
368            contract.result.may_return_reference(),
369            SemanticConformanceIssue::ReferenceResultMismatch,
370        );
371        check_capability(
372            &mut issues,
373            caps.contains(FnCaps::LOCAL_ENVIRONMENT),
374            contract.environment == FunctionEnvironmentSemantics::LocalBindings,
375            SemanticConformanceIssue::LocalEnvironmentMismatch,
376        );
377        check_capability(
378            &mut issues,
379            caps.contains(FnCaps::MAY_SPILL),
380            contract.result.may_spill(),
381            SemanticConformanceIssue::SpillResultMismatch,
382        );
383    }
384    let identity_metadata = inspected_caps.zip(schema).map(|(caps, schema)| {
385        let repeating = schema.iter().find(|argument| argument.repeating.is_some());
386        let argument_by_ref = (0..arity)
387            .map(|index| {
388                schema
389                    .get(index)
390                    .or(repeating)
391                    .is_some_and(|argument| argument.by_ref)
392            })
393            .collect();
394        (caps, argument_by_ref)
395    });
396    (
397        SemanticContractResolution {
398            contract: issues.is_empty().then_some(contract).flatten(),
399            generation,
400            trusted_builtin,
401            issues,
402        },
403        identity_metadata,
404    )
405}
406
407fn precision_is_valid(contract: FunctionSemanticContract, arity: usize) -> bool {
408    use crate::function_contract::{
409        CriteriaValueRange, FunctionArgumentDependencyContract as Arguments,
410        FunctionArgumentDependencyRole as Role,
411    };
412    let Some(precision) = contract.precision else {
413        return true;
414    };
415    if !precision.arity.allows(arity)
416        || contract.dependency != FunctionDependencySemantics::RecursiveSyntacticArgs
417    {
418        return false;
419    }
420    match precision.arguments {
421        Arguments::AllArgs(role) | Arguments::Variadic(role) => {
422            !matches!(role, Role::IgnoredLiteral | Role::Unsupported)
423        }
424        Arguments::CriteriaPairs(criteria) => {
425            let value_valid = match criteria.value_range {
426                CriteriaValueRange::None => true,
427                CriteriaValueRange::Fixed(index) => index < arity,
428                CriteriaValueRange::Optional {
429                    provided_index,
430                    fallback_criteria_range_index,
431                } => provided_index <= arity && fallback_criteria_range_index < arity,
432            };
433            let pair_end = match criteria.value_range {
434                CriteriaValueRange::Fixed(index) if index >= criteria.first_criteria_pair => index,
435                CriteriaValueRange::Optional { provided_index, .. }
436                    if provided_index >= criteria.first_criteria_pair =>
437                {
438                    provided_index
439                }
440                _ => arity,
441            };
442            value_valid
443                && criteria.first_criteria_pair < pair_end
444                && (pair_end - criteria.first_criteria_pair).is_multiple_of(2)
445        }
446        Arguments::LocalBindingPairs => {
447            contract.environment == FunctionEnvironmentSemantics::LocalBindings
448                && arity >= 3
449                && !arity.is_multiple_of(2)
450        }
451        Arguments::LambdaParameters => {
452            contract.environment == FunctionEnvironmentSemantics::LocalBindings && arity >= 1
453        }
454    }
455}
456
457fn schema_allows_arity(
458    schema: &[crate::args::ArgSchema],
459    min_args: usize,
460    variadic: bool,
461    arity: usize,
462    strict_required_count: bool,
463) -> bool {
464    if schema.is_empty() {
465        return min_args == 0 && arity == 0;
466    }
467
468    let mut optional_seen = false;
469    let mut required_count = 0usize;
470    let mut repeating = None;
471    for (index, argument) in schema.iter().enumerate() {
472        if argument.required {
473            if optional_seen {
474                return false;
475            }
476            required_count += 1;
477        } else {
478            optional_seen = true;
479        }
480        if let Some(width) = argument.repeating {
481            if width == 0 || repeating.is_some() || index + 1 != schema.len() {
482                return false;
483            }
484            repeating = Some(width);
485        }
486    }
487    let represented_minimum = min_args.min(schema.len());
488    if (strict_required_count && required_count != min_args)
489        || (!strict_required_count && required_count < represented_minimum)
490        || schema
491            .iter()
492            .take(represented_minimum)
493            .any(|argument| !argument.required)
494        || (!variadic && schema.len() > 1 && min_args > schema.len())
495        || arity < min_args
496    {
497        return false;
498    }
499    if let Some(width) = repeating {
500        if width > schema.len() {
501            return false;
502        }
503        let fixed_prefix = schema.len() - width;
504        return arity >= schema.len() && (arity - fixed_prefix).is_multiple_of(width);
505    }
506    if variadic {
507        return true;
508    }
509    arity <= schema.len().max(min_args)
510}
511
512fn inspected<T>(
513    issues: &mut Vec<SemanticConformanceIssue>,
514    issue: SemanticConformanceIssue,
515    inspect: impl FnOnce() -> T,
516) -> Option<T> {
517    match catch_unwind(AssertUnwindSafe(inspect)) {
518        Ok(value) => Some(value),
519        Err(_) => {
520            issues.push(issue);
521            None
522        }
523    }
524}
525
526fn check_capability(
527    issues: &mut Vec<SemanticConformanceIssue>,
528    capability: bool,
529    semantic: bool,
530    issue: SemanticConformanceIssue,
531) {
532    if capability != semantic {
533        issues.push(issue);
534    }
535}
536
537fn trusted_contract_from_caps(
538    caps: FnCaps,
539    precision: Option<crate::function_contract::FunctionDependencyContract>,
540) -> FunctionSemanticContract {
541    let mut contract = FunctionSemanticContract::trusted_builtin_default(precision);
542    if caps.contains(FnCaps::DYNAMIC_DEPENDENCY) {
543        contract.dependency = FunctionDependencySemantics::Dynamic;
544    }
545    if caps.contains(FnCaps::SHORT_CIRCUIT) {
546        contract.evaluation = FunctionEvaluationSemantics::ShortCircuit;
547    }
548    contract.result = FunctionResultSemantics::from_capabilities(
549        caps.contains(FnCaps::RETURNS_REFERENCE),
550        caps.contains(FnCaps::MAY_SPILL),
551    );
552    if caps.contains(FnCaps::LOCAL_ENVIRONMENT) {
553        contract.environment = FunctionEnvironmentSemantics::LocalBindings;
554    }
555    contract
556}
557
558const EXCEL_PREFIXES: &[&str] = &["_XLFN.", "_XLL.", "_XLWS."];
559
560fn resolve_registered(
561    state: &RegistryState,
562    key: &RegistryKey,
563) -> Option<(RegistryKey, RegistryEntry)> {
564    if let Some(entry) = state.registrations.get(key) {
565        return Some((key.clone(), entry.clone()));
566    }
567    let alias = state.aliases.get(key)?;
568    state
569        .registrations
570        .get(&alias.target)
571        .map(|entry| (alias.target.clone(), entry.clone()))
572}
573
574#[cfg(test)]
575thread_local! {
576    static RESOLUTION_WRITES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
577}
578
579fn resolve_entry(ns: &str, name: &str) -> Option<(RegistryKey, RegistryEntry)> {
580    #[cfg(test)]
581    RESOLUTION_WRITES.with(|c| c.set(c.get() + 1));
582    let ns = norm(ns);
583    let normalized_name = norm(name);
584    let key = (ns.clone(), normalized_name.clone());
585    let mut state = REGISTRY
586        .write()
587        .unwrap_or_else(|poisoned| poisoned.into_inner());
588    if let Some(entry) = resolve_registered(&state, &key) {
589        return Some(entry);
590    }
591    let mut candidate = normalized_name.as_str();
592    loop {
593        let mut stripped_any = false;
594        for prefix in EXCEL_PREFIXES {
595            if let Some(rest) = candidate.strip_prefix(prefix) {
596                candidate = rest;
597                stripped_any = true;
598                let stripped_key = (ns.clone(), candidate.to_string());
599                if let Some((canonical, entry)) = resolve_registered(&state, &stripped_key) {
600                    state.aliases.insert(
601                        key.clone(),
602                        AliasEntry {
603                            target: canonical.clone(),
604                            owner: Some((canonical.clone(), entry.generation)),
605                        },
606                    );
607                    return Some((canonical, entry));
608                }
609                break;
610            }
611        }
612        if !stripped_any {
613            break;
614        }
615    }
616    None
617}
618
619fn resolve_key_read_only(
620    state: &RegistryState,
621    key: &RegistryKey,
622) -> Option<(RegistryKey, RegistryEntry)> {
623    if let Some(entry) = resolve_registered(state, key) {
624        return Some(entry);
625    }
626    let mut candidate = key.1.as_str();
627    loop {
628        let rest = EXCEL_PREFIXES
629            .iter()
630            .find_map(|prefix| candidate.strip_prefix(prefix))?;
631        candidate = rest;
632        let stripped = (key.0.clone(), candidate.to_string());
633        if let Some(entry) = resolve_registered(state, &stripped) {
634            return Some(entry);
635        }
636    }
637}
638
639fn resolve_entry_read_only(ns: &str, name: &str) -> Option<(RegistryKey, RegistryEntry)> {
640    let state = REGISTRY
641        .read()
642        .unwrap_or_else(|poisoned| poisoned.into_inner());
643    resolve_key_read_only(&state, &(norm(ns), norm(name)))
644}
645
646pub fn get(ns: &str, name: &str) -> Option<Arc<dyn Function>> {
647    let key = (norm(ns), norm(name));
648    {
649        let state = REGISTRY
650            .read()
651            .unwrap_or_else(|poisoned| poisoned.into_inner());
652        let entry = state.registrations.get(&key).or_else(|| {
653            let alias = state.aliases.get(&key)?;
654            state.registrations.get(&alias.target)
655        });
656        if let Some(entry) = entry {
657            return Some(Arc::clone(&entry.function));
658        }
659        if !EXCEL_PREFIXES
660            .iter()
661            .any(|prefix| key.1.starts_with(prefix))
662        {
663            return None;
664        }
665    }
666    // Prefix misses may publish an alias. Recheck under the write lock, since a
667    // registration or alias owner could have changed after releasing the read lock.
668    resolve_entry(&key.0, &key.1).map(|(_, entry)| entry.function)
669}
670
671/// Read-only registry lookup for planning providers. Unlike [`get`], this does
672/// not populate the global Excel-prefix alias cache.
673#[doc(hidden)]
674pub fn get_for_planning(ns: &str, name: &str) -> Option<Arc<dyn Function>> {
675    resolve_entry_read_only(ns, name).map(|(_, entry)| entry.function)
676}
677
678pub(crate) struct GlobalRegistryFunctionProvider;
679
680impl crate::traits::FunctionProvider for GlobalRegistryFunctionProvider {
681    fn planning_semantic_revision(&self) -> Option<u64> {
682        Some(0)
683    }
684
685    fn get_function(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
686        get(ns, name)
687    }
688
689    fn get_function_for_planning(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
690        get_for_planning(ns, name)
691    }
692}
693
694#[derive(Clone)]
695struct PlanningRegistration {
696    canonical: RegistryKey,
697    function: Arc<dyn Function>,
698    generation: u64,
699    trusted_builtin: bool,
700}
701
702/// Immutable registry view used by one formula-planning operation.
703///
704/// Function metadata is inspected before publication of the snapshot. Resolution
705/// afterward is lock-free and never populates the global prefix-alias cache.
706#[derive(Clone)]
707pub(crate) struct RegistryPlanningSnapshot {
708    epoch: u64,
709    provider_revision: Option<u64>,
710    requests: Arc<Vec<(String, String, usize)>>,
711    functions: Arc<HashMap<RegistryKey, Arc<dyn Function>>>,
712    capabilities: Arc<HashMap<RegistryKey, FnCaps>>,
713    identities: Arc<HashMap<(String, String, usize), FunctionSemanticIdentity>>,
714}
715
716#[derive(Clone, Copy, Debug, PartialEq, Eq)]
717pub(crate) enum PlanningSnapshotError {
718    RegistryChangedDuringCapture,
719    ProviderRevisionUnavailable,
720}
721
722impl RegistryPlanningSnapshot {
723    const CAPTURE_ATTEMPTS: usize = 16;
724
725    pub(crate) fn capture_for_requests(
726        runtime_provider: &dyn crate::traits::FunctionProvider,
727        requests: impl IntoIterator<Item = (String, String, usize)>,
728    ) -> Result<Self, PlanningSnapshotError> {
729        crate::builtins::load_builtins();
730        let mut requests: Vec<_> = requests.into_iter().collect();
731        requests.sort();
732        requests.dedup();
733        Self::capture_with_hook(runtime_provider, &requests, Self::CAPTURE_ATTEMPTS, |_| {})
734    }
735
736    fn capture_with_hook(
737        runtime_provider: &dyn crate::traits::FunctionProvider,
738        requests: &[(String, String, usize)],
739        attempts: usize,
740        mut after_registry_copy: impl FnMut(usize),
741    ) -> Result<Self, PlanningSnapshotError> {
742        for attempt in 0..attempts {
743            let provider_revision = if requests.is_empty() {
744                None
745            } else {
746                Some(
747                    runtime_provider
748                        .planning_semantic_revision()
749                        .ok_or(PlanningSnapshotError::ProviderRevisionUnavailable)?,
750                )
751            };
752            let start_epoch = REGISTRY
753                .read()
754                .unwrap_or_else(|poisoned| poisoned.into_inner())
755                .semantic_epoch;
756            let runtime_functions: HashMap<_, _> = requests
757                .iter()
758                .filter_map(|(namespace, name, _)| {
759                    runtime_provider
760                        .get_function_for_planning(namespace, name)
761                        .map(|function| ((norm(namespace), norm(name)), function))
762                })
763                .collect();
764            let (epoch, registrations) = {
765                let state = REGISTRY
766                    .read()
767                    .unwrap_or_else(|poisoned| poisoned.into_inner());
768                let registrations = requests
769                    .iter()
770                    .filter_map(|(namespace, name, arity)| {
771                        let request_key = (norm(namespace), norm(name));
772                        resolve_key_read_only(&state, &request_key).map(|(canonical, entry)| {
773                            (
774                                (request_key.0, request_key.1, *arity),
775                                PlanningRegistration {
776                                    canonical,
777                                    function: entry.function,
778                                    generation: entry.generation,
779                                    trusted_builtin: entry.trusted_builtin,
780                                },
781                            )
782                        })
783                    })
784                    .collect::<HashMap<_, _>>();
785                (state.semantic_epoch, registrations)
786            };
787
788            after_registry_copy(attempt);
789
790            let mut capabilities = HashMap::new();
791            let mut identities = HashMap::new();
792            for (namespace, name, arity) in requests {
793                let request_key = (norm(namespace), norm(name));
794                let Some(runtime) = runtime_functions.get(&request_key) else {
795                    continue;
796                };
797                let Some(registration) =
798                    registrations.get(&(request_key.0.clone(), request_key.1.clone(), *arity))
799                else {
800                    continue;
801                };
802                if !Arc::ptr_eq(runtime, &registration.function) {
803                    continue;
804                }
805                let (semantics, identity_metadata) = inspect_semantics_with_identity_metadata(
806                    &registration.function,
807                    registration.trusted_builtin,
808                    registration.generation,
809                    *arity,
810                );
811                let Some(contract) = semantics.contract else {
812                    continue;
813                };
814                let Some((caps, argument_by_ref)) = identity_metadata else {
815                    continue;
816                };
817                capabilities.insert(request_key.clone(), caps);
818                identities.insert(
819                    (request_key.0, request_key.1, *arity),
820                    FunctionSemanticIdentity {
821                        namespace: registration.canonical.0.clone(),
822                        canonical_name: registration.canonical.1.clone(),
823                        generation: registration.generation,
824                        caps,
825                        contract,
826                        argument_by_ref,
827                    },
828                );
829            }
830
831            for (key, function) in &runtime_functions {
832                if !capabilities.contains_key(key)
833                    && let Ok(caps) = catch_unwind(AssertUnwindSafe(|| function.caps()))
834                {
835                    capabilities.insert(key.clone(), caps);
836                }
837            }
838
839            let provider_unchanged = provider_revision.is_none_or(|revision| {
840                runtime_provider.planning_semantic_revision() == Some(revision)
841            });
842            // Validate the capture against the functions this snapshot actually
843            // requested, not against the global registry epoch.
844            //
845            // Registering any function anywhere bumps `semantic_epoch`, so a
846            // global equality check treated a completely unrelated registration
847            // -- a different namespace, a different name -- as "the registry
848            // changed underneath us". Under concurrent registration that
849            // produced spurious `RegistryChangedDuringCapture` results and, in
850            // authoritative mode, spurious FormulaPlane family fallbacks whose
851            // reported reason blamed the provider.
852            //
853            // `semantic_changes_affect_requests_in_state` consults the change
854            // log across the whole capture window [start_epoch, now] and only
855            // reports a conflict when a *requested* key changed. It stays
856            // conservative when the bounded change log has been truncated past
857            // `start_epoch`, so precision never costs correctness.
858            let requests_unchanged = {
859                let state = REGISTRY
860                    .read()
861                    .unwrap_or_else(|poisoned| poisoned.into_inner());
862                !semantic_changes_affect_requests_in_state(
863                    &state,
864                    start_epoch,
865                    requests.iter().cloned(),
866                )
867            };
868            let unchanged = requests_unchanged && provider_unchanged;
869            if unchanged {
870                return Ok(Self {
871                    epoch,
872                    provider_revision,
873                    requests: Arc::new(requests.to_vec()),
874                    functions: Arc::new(runtime_functions),
875                    capabilities: Arc::new(capabilities),
876                    identities: Arc::new(identities),
877                });
878            }
879        }
880        Err(PlanningSnapshotError::RegistryChangedDuringCapture)
881    }
882
883    pub(crate) fn epoch(&self) -> u64 {
884        self.epoch
885    }
886
887    pub(crate) fn provider_revision(&self) -> Option<u64> {
888        self.provider_revision
889    }
890
891    pub(crate) fn semantic_changes_affect_requests_since(&self, epoch: u64) -> bool {
892        semantic_changes_affect_requests_since(epoch, self.requests.iter().cloned())
893    }
894
895    pub(crate) fn semantic_changes_affect_requests_since_guarded(
896        &self,
897        guard: &SemanticEpochReadGuard,
898        epoch: u64,
899    ) -> bool {
900        guard.semantic_changes_affect_requests_since(epoch, self.requests.iter().cloned())
901    }
902}
903
904pub(crate) fn semantic_changes_affect_requests_since(
905    epoch: u64,
906    requests: impl IntoIterator<Item = (String, String, usize)>,
907) -> bool {
908    let state = REGISTRY
909        .read()
910        .unwrap_or_else(|poisoned| poisoned.into_inner());
911    semantic_changes_affect_requests_in_state(&state, epoch, requests)
912}
913
914fn semantic_changes_affect_requests_in_state(
915    state: &RegistryState,
916    epoch: u64,
917    requests: impl IntoIterator<Item = (String, String, usize)>,
918) -> bool {
919    let changes = semantic_changes_since_in_state(state, epoch);
920    if changes.epoch == epoch {
921        return false;
922    }
923    if !changes.complete {
924        return true;
925    }
926    let requests = requests
927        .into_iter()
928        .flat_map(|(namespace, name, _)| {
929            let namespace = norm(namespace);
930            let normalized = norm(name);
931            let mut spellings = vec![(namespace.clone(), normalized.clone())];
932            let mut stripped = normalized.as_str();
933            while let Some(rest) = EXCEL_PREFIXES
934                .iter()
935                .find_map(|prefix| stripped.strip_prefix(prefix))
936            {
937                stripped = rest;
938                spellings.push((namespace.clone(), stripped.to_string()));
939            }
940            spellings
941        })
942        .collect::<std::collections::BTreeSet<_>>();
943    changes.keys.into_iter().any(|key| requests.contains(&key))
944}
945
946impl crate::traits::FunctionProvider for RegistryPlanningSnapshot {
947    fn planning_semantic_revision(&self) -> Option<u64> {
948        Some(self.provider_revision.unwrap_or(0))
949    }
950
951    fn get_function(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
952        self.functions.get(&(norm(ns), norm(name))).cloned()
953    }
954
955    fn get_function_for_planning(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
956        self.get_function(ns, name)
957    }
958
959    fn function_capabilities(&self, ns: &str, name: &str) -> Option<FnCaps> {
960        self.capabilities.get(&(norm(ns), norm(name))).copied()
961    }
962
963    fn function_semantic_identity(
964        &self,
965        ns: &str,
966        name: &str,
967        arity: usize,
968    ) -> Option<FunctionSemanticIdentity> {
969        self.identities.get(&(norm(ns), norm(name), arity)).cloned()
970    }
971}
972
973pub fn resolve(ns: &str, name: &str) -> Option<ResolvedFunction> {
974    resolve_entry(ns, name).map(to_resolved)
975}
976
977pub fn resolve_with_epoch(ns: &str, name: &str) -> Option<(u64, ResolvedFunction)> {
978    let key = (norm(ns), norm(name));
979    let state = REGISTRY
980        .read()
981        .unwrap_or_else(|poisoned| poisoned.into_inner());
982    resolve_registered(&state, &key).map(|entry| (state.semantic_epoch, to_resolved(entry)))
983}
984pub fn resolve_for_arity(ns: &str, name: &str, arity: usize) -> Option<ResolvedFunction> {
985    resolve_entry(ns, name).map(|((namespace, canonical_name), entry)| {
986        let semantics = {
987            let cached = entry
988                .semantics_by_arity
989                .read()
990                .unwrap_or_else(|poisoned| poisoned.into_inner())
991                .get(&arity)
992                .cloned();
993            cached.unwrap_or_else(|| {
994                let inspected = inspect_semantics(
995                    &entry.function,
996                    entry.trusted_builtin,
997                    entry.generation,
998                    arity,
999                );
1000                entry
1001                    .semantics_by_arity
1002                    .write()
1003                    .unwrap_or_else(|poisoned| poisoned.into_inner())
1004                    .entry(arity)
1005                    .or_insert_with(|| inspected.clone())
1006                    .clone()
1007            })
1008        };
1009        ResolvedFunction {
1010            semantics,
1011            namespace,
1012            canonical_name,
1013            function: entry.function,
1014        }
1015    })
1016}
1017
1018pub(crate) fn resolve_semantic_identity<P: crate::traits::FunctionProvider + ?Sized>(
1019    provider: &P,
1020    ns: &str,
1021    name: &str,
1022    arity: usize,
1023) -> Option<FunctionSemanticIdentity> {
1024    let runtime = provider.get_function(ns, name)?;
1025    let resolved = resolve_for_arity(ns, name, arity)?;
1026    if !Arc::ptr_eq(&runtime, &resolved.function) {
1027        return None;
1028    }
1029    let contract = resolved.semantics.contract?;
1030    let argument_by_ref = catch_unwind(AssertUnwindSafe(|| {
1031        let schema = runtime.arg_schema();
1032        let repeating = schema.iter().find(|argument| argument.repeating.is_some());
1033        (0..arity)
1034            .map(|index| {
1035                schema
1036                    .get(index)
1037                    .or(repeating)
1038                    .is_some_and(|argument| argument.by_ref)
1039            })
1040            .collect()
1041    }))
1042    .ok()?;
1043    Some(FunctionSemanticIdentity {
1044        namespace: resolved.namespace,
1045        canonical_name: resolved.canonical_name,
1046        generation: resolved.semantics.generation,
1047        caps: runtime.caps(),
1048        contract,
1049        argument_by_ref,
1050    })
1051}
1052
1053fn to_resolved(
1054    ((namespace, canonical_name), entry): (RegistryKey, RegistryEntry),
1055) -> ResolvedFunction {
1056    ResolvedFunction {
1057        namespace,
1058        canonical_name,
1059        function: entry.function,
1060        semantics: entry.semantics,
1061    }
1062}
1063
1064pub fn register_alias(ns: &str, alias: &str, target_ns: &str, target_name: &str) {
1065    let mut state = REGISTRY
1066        .write()
1067        .unwrap_or_else(|poisoned| poisoned.into_inner());
1068    let alias_key = (norm(ns), norm(alias));
1069    let target = (norm(target_ns), norm(target_name));
1070    let old_target = state
1071        .aliases
1072        .get(&alias_key)
1073        .map(|entry| entry.target.clone());
1074    if old_target.as_ref() == Some(&target) {
1075        return;
1076    }
1077    state.aliases.insert(
1078        alias_key.clone(),
1079        AliasEntry {
1080            target: target.clone(),
1081            owner: None,
1082        },
1083    );
1084    // Retargeting changes resolution through the alias spelling, not resolution
1085    // of either directly named target.
1086    publish_semantic_change(&mut state, [alias_key]);
1087}
1088
1089pub fn snapshot_registered() -> Vec<(String, String, Arc<dyn Function>)> {
1090    let state = REGISTRY
1091        .read()
1092        .unwrap_or_else(|poisoned| poisoned.into_inner());
1093    state
1094        .registrations
1095        .iter()
1096        .map(|((ns, name), entry)| (ns.clone(), name.clone(), Arc::clone(&entry.function)))
1097        .collect()
1098}
1099pub fn snapshot_semantics() -> Vec<ResolvedFunction> {
1100    let state = REGISTRY
1101        .read()
1102        .unwrap_or_else(|poisoned| poisoned.into_inner());
1103    state
1104        .registrations
1105        .iter()
1106        .map(|((namespace, canonical_name), entry)| ResolvedFunction {
1107            namespace: namespace.clone(),
1108            canonical_name: canonical_name.clone(),
1109            function: Arc::clone(&entry.function),
1110            semantics: entry.semantics.clone(),
1111        })
1112        .collect()
1113}
1114
1115#[cfg(test)]
1116mod tests {
1117    use super::*;
1118    use crate::traits::FunctionProvider;
1119
1120    struct TestFn {
1121        ns: &'static str,
1122        name: &'static str,
1123        aliases: &'static [&'static str],
1124    }
1125
1126    impl Function for TestFn {
1127        fn name(&self) -> &'static str {
1128            self.name
1129        }
1130        fn namespace(&self) -> &'static str {
1131            self.ns
1132        }
1133        fn aliases(&self) -> &'static [&'static str] {
1134            self.aliases
1135        }
1136        fn eval<'a, 'b, 'c>(
1137            &self,
1138            _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
1139            _ctx: &dyn crate::traits::FunctionContext<'b>,
1140        ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
1141            Ok(crate::traits::CalcValue::Scalar(
1142                formualizer_common::LiteralValue::Number(1.0),
1143            ))
1144        }
1145    }
1146
1147    struct PlanningFn {
1148        ns: &'static str,
1149        name: &'static str,
1150        aliases: &'static [&'static str],
1151        caps: FnCaps,
1152    }
1153
1154    impl Function for PlanningFn {
1155        fn name(&self) -> &'static str {
1156            self.name
1157        }
1158        fn namespace(&self) -> &'static str {
1159            self.ns
1160        }
1161        fn aliases(&self) -> &'static [&'static str] {
1162            self.aliases
1163        }
1164        fn caps(&self) -> FnCaps {
1165            self.caps
1166        }
1167        fn min_args(&self) -> usize {
1168            1
1169        }
1170        fn variadic(&self) -> bool {
1171            true
1172        }
1173        fn arg_schema(&self) -> &'static [crate::args::ArgSchema] {
1174            static SCHEMA: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
1175                std::sync::LazyLock::new(|| {
1176                    let mut argument = crate::args::ArgSchema::any();
1177                    argument.repeating = Some(1);
1178                    vec![argument]
1179                });
1180            &SCHEMA
1181        }
1182        fn eval<'a, 'b, 'c>(
1183            &self,
1184            _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
1185            _ctx: &dyn crate::traits::FunctionContext<'b>,
1186        ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
1187            unreachable!()
1188        }
1189    }
1190
1191    fn planning_fn(
1192        ns: &'static str,
1193        name: &'static str,
1194        aliases: &'static [&'static str],
1195        caps: FnCaps,
1196    ) -> Arc<dyn Function> {
1197        Arc::new(PlanningFn {
1198            ns,
1199            name,
1200            aliases,
1201            caps,
1202        })
1203    }
1204
1205    #[test]
1206    fn runtime_hits_only_clone_current_function_without_resolution_writes() {
1207        let ns = "__RUNTIME_READ_FAST_PATH__";
1208        let first = planning_fn(ns, "TARGET", &["ALIAS"], FnCaps::empty());
1209        register_function(first.clone());
1210        assert!(Arc::ptr_eq(&get(ns, "_xlfn._xlws.alias").unwrap(), &first));
1211        RESOLUTION_WRITES.with(|c| c.set(0));
1212        for name in ["target", "ALIAS", "_XLFN._XLWS.ALIAS"] {
1213            assert!(Arc::ptr_eq(&get(ns, name).unwrap(), &first));
1214        }
1215        assert!(get(ns, "MISSING").is_none());
1216        assert_eq!(RESOLUTION_WRITES.with(|c| c.get()), 0);
1217        let second = planning_fn(ns, "TARGET", &["ALIAS"], FnCaps::empty());
1218        register_function(second.clone());
1219        for name in ["target", "ALIAS", "_XLFN._XLWS.ALIAS"] {
1220            assert!(Arc::ptr_eq(&get(ns, name).unwrap(), &second));
1221        }
1222        std::thread::scope(|scope| {
1223            for _ in 0..8 {
1224                let second = &second;
1225                scope.spawn(move || {
1226                    RESOLUTION_WRITES.with(|c| c.set(0));
1227                    for _ in 0..1000 {
1228                        assert!(Arc::ptr_eq(&get(ns, "TARGET").unwrap(), second));
1229                        assert!(Arc::ptr_eq(&get(ns, "_XLFN._XLWS.ALIAS").unwrap(), second));
1230                    }
1231                    assert_eq!(RESOLUTION_WRITES.with(|c| c.get()), 0);
1232                });
1233            }
1234        });
1235    }
1236
1237    #[test]
1238    fn planning_snapshot_resolves_direct_alias_namespace_and_prefix_without_cache_mutation() {
1239        let ns = "__PLANNING_PARITY__";
1240        register_builtin(planning_fn(ns, "TARGET", &["ALIAS"], FnCaps::empty()));
1241        let requests = [
1242            (ns.to_string(), "TARGET".to_string(), 1),
1243            (ns.to_string(), "alias".to_string(), 1),
1244            (ns.to_string(), "_xlfn._xlws.alias".to_string(), 1),
1245        ];
1246        let prefixed_key = (ns.to_string(), "_XLFN._XLWS.ALIAS".to_string());
1247        assert!(!REGISTRY.read().unwrap().aliases.contains_key(&prefixed_key));
1248        let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1249            &GlobalRegistryFunctionProvider,
1250            requests,
1251        )
1252        .unwrap();
1253        assert!(!REGISTRY.read().unwrap().aliases.contains_key(&prefixed_key));
1254
1255        let direct = snapshot
1256            .function_semantic_identity(ns, "TARGET", 1)
1257            .unwrap();
1258        for spelling in ["alias", "_xlfn._xlws.alias"] {
1259            let resolved = snapshot
1260                .function_semantic_identity(ns, spelling, 1)
1261                .unwrap();
1262            assert_eq!(resolved.namespace, ns);
1263            assert_eq!(resolved.canonical_name, "TARGET");
1264            assert_eq!(resolved.generation, direct.generation);
1265            assert!(Arc::ptr_eq(
1266                &snapshot.get_function(ns, spelling).unwrap(),
1267                &snapshot.get_function(ns, "TARGET").unwrap(),
1268            ));
1269        }
1270    }
1271
1272    #[test]
1273    fn guarded_request_change_check_does_not_relock_behind_queued_writer() {
1274        let ns = "__GUARDED_REQUEST_CHANGE__";
1275        register_function(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1276        let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1277            &GlobalRegistryFunctionProvider,
1278            [(ns.to_string(), "TARGET".to_string(), 1)],
1279        )
1280        .unwrap();
1281        register_function(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1282
1283        let guard = semantic_epoch_read_guard();
1284        let (queued_tx, queued_rx) = std::sync::mpsc::sync_channel(0);
1285        let writer = std::thread::spawn(move || {
1286            assert!(REGISTRY.try_write().is_err());
1287            queued_tx.send(()).unwrap();
1288            let mut state = REGISTRY
1289                .write()
1290                .unwrap_or_else(|poisoned| poisoned.into_inner());
1291            publish_semantic_change(&mut state, [(ns.to_string(), "QUEUED_WRITER".to_string())]);
1292        });
1293        queued_rx.recv().unwrap();
1294        std::thread::yield_now();
1295
1296        assert!(snapshot.semantic_changes_affect_requests_since_guarded(&guard, snapshot.epoch(),));
1297        drop(guard);
1298        writer.join().unwrap();
1299    }
1300
1301    #[test]
1302    fn workbook_planning_fallback_does_not_populate_prefix_alias_cache() {
1303        let ns = "__PLANNING_WORKBOOK_PREFIX__";
1304        register_builtin(planning_fn(ns, "TARGET", &["ALIAS"], FnCaps::empty()));
1305        let prefixed_key = (ns.to_string(), "_XLFN.ALIAS".to_string());
1306        assert!(!REGISTRY.read().unwrap().aliases.contains_key(&prefixed_key));
1307
1308        let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1309            &crate::test_workbook::TestWorkbook::default(),
1310            [(ns.to_string(), "_xlfn.alias".to_string(), 1)],
1311        )
1312        .unwrap();
1313
1314        assert!(
1315            snapshot
1316                .function_semantic_identity(ns, "_xlfn.alias", 1)
1317                .is_some()
1318        );
1319        assert!(!REGISTRY.read().unwrap().aliases.contains_key(&prefixed_key));
1320    }
1321
1322    #[test]
1323    fn planning_snapshot_is_immutable_across_replacement() {
1324        let ns = "__PLANNING_IMMUTABLE__";
1325        register_builtin(planning_fn(ns, "TARGET", &["OLD_ALIAS"], FnCaps::empty()));
1326        let requests = [
1327            (ns.to_string(), "TARGET".to_string(), 1),
1328            (ns.to_string(), "OLD_ALIAS".to_string(), 1),
1329        ];
1330        let snapshot = RegistryPlanningSnapshot::capture_with_hook(
1331            &GlobalRegistryFunctionProvider,
1332            &requests,
1333            10_000,
1334            |_| {},
1335        )
1336        .unwrap();
1337        let old_function = snapshot.get_function(ns, "TARGET").unwrap();
1338        let old_identity = snapshot
1339            .function_semantic_identity(ns, "TARGET", 1)
1340            .unwrap();
1341
1342        register_function(planning_fn(ns, "TARGET", &[], FnCaps::MAY_SPILL));
1343        let current = resolve_for_arity(ns, "TARGET", 1).unwrap();
1344        assert!(current.semantics.generation > old_identity.generation);
1345        assert!(!Arc::ptr_eq(&old_function, &current.function));
1346        assert_eq!(
1347            snapshot
1348                .function_semantic_identity(ns, "TARGET", 1)
1349                .unwrap(),
1350            old_identity
1351        );
1352        assert!(Arc::ptr_eq(
1353            &old_function,
1354            &snapshot.get_function(ns, "TARGET").unwrap(),
1355        ));
1356        assert!(get(ns, "OLD_ALIAS").is_none());
1357        assert_eq!(
1358            snapshot
1359                .function_semantic_identity(ns, "OLD_ALIAS", 1)
1360                .unwrap(),
1361            old_identity
1362        );
1363        assert!(Arc::ptr_eq(
1364            &old_function,
1365            &snapshot.get_function(ns, "OLD_ALIAS").unwrap(),
1366        ));
1367    }
1368
1369    #[test]
1370    fn planning_snapshot_requires_explicit_side_effect_free_provider_opt_in() {
1371        struct RuntimeOnlyProvider(Arc<dyn Function>);
1372        impl FunctionProvider for RuntimeOnlyProvider {
1373            fn get_function(&self, _ns: &str, _name: &str) -> Option<Arc<dyn Function>> {
1374                Some(Arc::clone(&self.0))
1375            }
1376        }
1377
1378        let ns = "__PLANNING_FAIL_CLOSED__";
1379        register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1380        let result = RegistryPlanningSnapshot::capture_for_requests(
1381            &RuntimeOnlyProvider(planning_fn(ns, "TARGET", &[], FnCaps::empty())),
1382            [(ns.to_string(), "TARGET".to_string(), 1)],
1383        );
1384        assert_eq!(
1385            result.err(),
1386            Some(PlanningSnapshotError::ProviderRevisionUnavailable)
1387        );
1388    }
1389
1390    #[test]
1391    fn planning_snapshot_preserves_runtime_override_without_global_semantics() {
1392        struct OverrideProvider(Arc<dyn Function>);
1393        impl FunctionProvider for OverrideProvider {
1394            fn planning_semantic_revision(&self) -> Option<u64> {
1395                Some(0)
1396            }
1397            fn get_function(&self, _ns: &str, _name: &str) -> Option<Arc<dyn Function>> {
1398                Some(Arc::clone(&self.0))
1399            }
1400            fn get_function_for_planning(
1401                &self,
1402                _ns: &str,
1403                _name: &str,
1404            ) -> Option<Arc<dyn Function>> {
1405                Some(Arc::clone(&self.0))
1406            }
1407        }
1408
1409        let ns = "__PLANNING_OVERRIDE__";
1410        register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1411        let global = get(ns, "TARGET").unwrap();
1412        let runtime = planning_fn(ns, "TARGET", &[], FnCaps::MAY_SPILL);
1413        let provider = OverrideProvider(Arc::clone(&runtime));
1414        let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1415            &provider,
1416            [(ns.to_string(), "TARGET".to_string(), 1)],
1417        )
1418        .unwrap();
1419
1420        let captured = snapshot.get_function(ns, "TARGET").unwrap();
1421        assert!(Arc::ptr_eq(&captured, &runtime));
1422        assert!(!Arc::ptr_eq(&captured, &global));
1423        assert!(
1424            snapshot
1425                .function_semantic_identity(ns, "TARGET", 1)
1426                .is_none()
1427        );
1428        assert_eq!(snapshot.functions.len(), 1);
1429        assert_eq!(
1430            snapshot.function_capabilities(ns, "TARGET"),
1431            Some(FnCaps::MAY_SPILL)
1432        );
1433        assert_eq!(snapshot.capabilities.len(), 1);
1434        assert!(snapshot.identities.is_empty());
1435        assert!(snapshot.get_function(ns, "UNREQUESTED").is_none());
1436    }
1437
1438    #[test]
1439    fn planning_snapshot_retries_provider_revision_flip_to_runtime_override() {
1440        struct FlippingProvider {
1441            function: Arc<RwLock<Arc<dyn Function>>>,
1442            revision: Arc<AtomicU64>,
1443        }
1444        impl FunctionProvider for FlippingProvider {
1445            fn planning_semantic_revision(&self) -> Option<u64> {
1446                Some(self.revision.load(Ordering::Acquire))
1447            }
1448            fn get_function(&self, _ns: &str, _name: &str) -> Option<Arc<dyn Function>> {
1449                Some(Arc::clone(&self.function.read().unwrap()))
1450            }
1451            fn get_function_for_planning(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
1452                self.get_function(ns, name)
1453            }
1454        }
1455
1456        let ns = "__PLANNING_PROVIDER_FLIP__";
1457        register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1458        let global = get(ns, "TARGET").unwrap();
1459        let override_function = planning_fn(ns, "TARGET", &[], FnCaps::MAY_SPILL);
1460        let function = Arc::new(RwLock::new(global));
1461        let revision = Arc::new(AtomicU64::new(0));
1462        let provider = FlippingProvider {
1463            function: Arc::clone(&function),
1464            revision: Arc::clone(&revision),
1465        };
1466        let requests = [(ns.to_string(), "TARGET".to_string(), 1)];
1467        let snapshot =
1468            RegistryPlanningSnapshot::capture_with_hook(&provider, &requests, 2, |attempt| {
1469                if attempt == 0 {
1470                    *function.write().unwrap() = Arc::clone(&override_function);
1471                    revision.fetch_add(1, Ordering::AcqRel);
1472                }
1473            })
1474            .unwrap();
1475
1476        assert_eq!(snapshot.provider_revision(), Some(1));
1477        assert!(Arc::ptr_eq(
1478            &snapshot.get_function(ns, "TARGET").unwrap(),
1479            &override_function
1480        ));
1481        assert!(
1482            snapshot
1483                .function_semantic_identity(ns, "TARGET", 1)
1484                .is_none()
1485        );
1486    }
1487
1488    #[test]
1489    fn planning_snapshot_capture_tolerates_unrelated_concurrent_registrations() {
1490        // Regression test for the flake in the provider-revision test family.
1491        //
1492        // A capture used to be validated against the global `semantic_epoch`,
1493        // so registering *any* function on another thread invalidated it. In
1494        // the test suite that surfaced as an intermittent failure that moved
1495        // between tests depending on which happened to be running in parallel;
1496        // in production it would surface as spurious FormulaPlane fallbacks in
1497        // any application that registers custom functions while evaluating.
1498        //
1499        // The churn below never touches the requested key, so a correct capture
1500        // must succeed even with the tightest useful attempt budget.
1501        // The capture hook fires between the registry copy and the validation
1502        // step -- exactly where a concurrent registration would land -- so this
1503        // reproduces the race deterministically rather than relying on thread
1504        // interleaving.
1505        let ns = "__PLANNING_UNRELATED_CHURN__";
1506        register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1507        let requests = [(ns.to_string(), "TARGET".to_string(), 1)];
1508
1509        let mut unrelated_registrations = 0usize;
1510        let snapshot = RegistryPlanningSnapshot::capture_with_hook(
1511            &GlobalRegistryFunctionProvider,
1512            &requests,
1513            2,
1514            |_| {
1515                // `register_function` is untrusted, so unlike a repeated builtin
1516                // registration it always publishes a semantic change. Firing on
1517                // every attempt means a global-epoch check can never converge.
1518                register_function(planning_fn(
1519                    "__PLANNING_UNRELATED_CHURN_OTHER__",
1520                    "OTHER",
1521                    &[],
1522                    FnCaps::empty(),
1523                ));
1524                unrelated_registrations += 1;
1525            },
1526        )
1527        .expect("unrelated registrations must not invalidate a planning snapshot capture");
1528
1529        assert!(unrelated_registrations > 0, "hook must have registered");
1530        assert!(Arc::ptr_eq(
1531            &snapshot.get_function(ns, "TARGET").unwrap(),
1532            &get(ns, "TARGET").unwrap(),
1533        ));
1534    }
1535
1536    #[test]
1537    fn planning_snapshot_capture_retries_and_fails_deterministically() {
1538        let ns = "__PLANNING_RACE__";
1539        register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1540        let requests = [(ns.to_string(), "TARGET".to_string(), 1)];
1541        let before = semantic_epoch();
1542        let retried = RegistryPlanningSnapshot::capture_with_hook(
1543            &GlobalRegistryFunctionProvider,
1544            &requests,
1545            100,
1546            |attempt| {
1547                if attempt == 0 {
1548                    register_function(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1549                }
1550            },
1551        )
1552        .unwrap();
1553        assert!(retried.epoch() > before);
1554
1555        let failed = RegistryPlanningSnapshot::capture_with_hook(
1556            &GlobalRegistryFunctionProvider,
1557            &requests,
1558            2,
1559            |_| register_function(planning_fn(ns, "TARGET", &[], FnCaps::empty())),
1560        );
1561        assert_eq!(
1562            failed.err(),
1563            Some(PlanningSnapshotError::RegistryChangedDuringCapture)
1564        );
1565    }
1566
1567    #[test]
1568    fn planning_snapshot_nested_function_authority_matches_global_registry() {
1569        let ns = "";
1570        register_builtin(planning_fn(ns, "__PLAN_OUTER__", &[], FnCaps::empty()));
1571        register_builtin(planning_fn(ns, "__PLAN_INNER__", &[], FnCaps::empty()));
1572        let requests = [
1573            (String::new(), "__PLAN_OUTER__".to_string(), 1),
1574            (String::new(), "_xlfn.__PLAN_INNER__".to_string(), 1),
1575        ];
1576        let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1577            &GlobalRegistryFunctionProvider,
1578            requests,
1579        )
1580        .unwrap();
1581        let ast =
1582            formualizer_parse::parser::parse("=__PLAN_OUTER__(_xlfn.__PLAN_INNER__(A1))").unwrap();
1583        let frozen = crate::formula_plane::template_canonical::canonicalize_template_with_provider(
1584            &ast,
1585            2,
1586            2,
1587            Some(&snapshot),
1588        );
1589        let global = crate::formula_plane::template_canonical::canonicalize_template_with_provider(
1590            &ast,
1591            2,
1592            2,
1593            Some(&GlobalRegistryFunctionProvider),
1594        );
1595        assert_eq!(frozen, global);
1596        assert!(frozen.labels.is_authority_supported());
1597    }
1598
1599    #[test]
1600    fn parallel_snapshot_capture_and_prefix_resolution_does_not_deadlock() {
1601        let ns = "__PLANNING_PARALLEL__";
1602        register_builtin(planning_fn(ns, "TARGET", &["ALIAS"], FnCaps::empty()));
1603        let (send, receive) = std::sync::mpsc::channel();
1604        std::thread::spawn(move || {
1605            let mut workers = Vec::new();
1606            for worker in 0..4 {
1607                workers.push(std::thread::spawn(move || {
1608                    for iteration in 0..100 {
1609                        if worker == 0 && iteration % 10 == 0 {
1610                            register_function(planning_fn(
1611                                ns,
1612                                "TARGET",
1613                                &["ALIAS"],
1614                                FnCaps::empty(),
1615                            ));
1616                        }
1617                        let _ = RegistryPlanningSnapshot::capture_for_requests(
1618                            &GlobalRegistryFunctionProvider,
1619                            [(ns.to_string(), "_xlfn.alias".to_string(), 1)],
1620                        );
1621                        let _ = get(ns, "_xlfn.alias");
1622                    }
1623                }));
1624            }
1625            for worker in workers {
1626                worker.join().unwrap();
1627            }
1628            send.send(()).unwrap();
1629        });
1630        receive
1631            .recv_timeout(std::time::Duration::from_secs(10))
1632            .expect("parallel registry planning timed out (possible lock inversion)");
1633    }
1634
1635    #[test]
1636    fn resolves_prefixes_aliases_and_direct_registration() {
1637        let ns = "__REG_PREFIX__";
1638        register_function(Arc::new(TestFn {
1639            ns,
1640            name: "FILTER",
1641            aliases: &["LEGACY"],
1642        }));
1643        assert_eq!(get(ns, "_xlfn._xlws.legacy").unwrap().name(), "FILTER");
1644        register_function(Arc::new(TestFn {
1645            ns,
1646            name: "_XLFN.FILTER",
1647            aliases: &[],
1648        }));
1649        assert_eq!(get(ns, "_xlfn.filter").unwrap().name(), "_XLFN.FILTER");
1650    }
1651
1652    #[test]
1653    fn trusted_replacement_records_removed_owned_alias_spelling() {
1654        let namespace = "__REG_STALE_ALIAS__";
1655        register_builtin(Arc::new(TestFn {
1656            ns: namespace,
1657            name: "TARGET",
1658            aliases: &["STALE_OWNED_ALIAS"],
1659        }));
1660        let before = semantic_epoch();
1661        register_function(Arc::new(TestFn {
1662            ns: namespace,
1663            name: "TARGET",
1664            aliases: &["NEW_OWNED_ALIAS"],
1665        }));
1666        let changes = semantic_changes_since(before);
1667        assert!(
1668            changes
1669                .keys
1670                .contains(&(namespace.to_string(), "STALE_OWNED_ALIAS".to_string()))
1671        );
1672        assert!(
1673            changes
1674                .keys
1675                .contains(&(namespace.to_string(), "NEW_OWNED_ALIAS".to_string()))
1676        );
1677    }
1678
1679    #[test]
1680    fn alias_requests_keep_the_spelling_used_by_the_formula() {
1681        let ns = "__ALIAS_REQUEST_SPELLING__";
1682        register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1683        register_alias(ns, "FORMULA_ALIAS", ns, "TARGET");
1684        let request = (ns.to_string(), "FORMULA_ALIAS".to_string(), 1);
1685        let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1686            &GlobalRegistryFunctionProvider,
1687            [request.clone()],
1688        )
1689        .unwrap();
1690
1691        assert_eq!(snapshot.requests.as_ref(), &[request]);
1692        assert_eq!(
1693            snapshot
1694                .function_semantic_identity(ns, "FORMULA_ALIAS", 1)
1695                .unwrap()
1696                .canonical_name,
1697            "TARGET"
1698        );
1699    }
1700
1701    #[test]
1702    fn alias_mutations_affect_alias_requests_but_not_direct_target_requests() {
1703        let ns = "__ALIAS_CHANGE_SCOPE__";
1704        register_builtin(planning_fn(ns, "OLD_TARGET", &[], FnCaps::empty()));
1705        register_builtin(planning_fn(ns, "NEW_TARGET", &[], FnCaps::empty()));
1706
1707        let add_epoch = semantic_epoch();
1708        register_alias(ns, "ADDED_ALIAS", ns, "OLD_TARGET");
1709        assert!(semantic_changes_affect_requests_since(
1710            add_epoch,
1711            [(ns.to_string(), "ADDED_ALIAS".to_string(), 1)]
1712        ));
1713        assert!(!semantic_changes_affect_requests_since(
1714            add_epoch,
1715            [(ns.to_string(), "OLD_TARGET".to_string(), 1)]
1716        ));
1717
1718        register_alias(ns, "RETARGETED_ALIAS", ns, "OLD_TARGET");
1719        let retarget_epoch = semantic_epoch();
1720        register_alias(ns, "RETARGETED_ALIAS", ns, "NEW_TARGET");
1721        assert!(semantic_changes_affect_requests_since(
1722            retarget_epoch,
1723            [(ns.to_string(), "RETARGETED_ALIAS".to_string(), 1)]
1724        ));
1725        assert!(semantic_changes_affect_requests_since(
1726            retarget_epoch,
1727            [(ns.to_string(), "_xlfn.RETARGETED_ALIAS".to_string(), 1)]
1728        ));
1729        for target in ["OLD_TARGET", "NEW_TARGET"] {
1730            assert!(!semantic_changes_affect_requests_since(
1731                retarget_epoch,
1732                [(ns.to_string(), target.to_string(), 1)]
1733            ));
1734        }
1735
1736        register_alias(ns, "REMOVED_ALIAS", ns, "OLD_TARGET");
1737        let remove_epoch = semantic_epoch();
1738        {
1739            let mut state = REGISTRY
1740                .write()
1741                .unwrap_or_else(|poisoned| poisoned.into_inner());
1742            let alias_key = (ns.to_string(), "REMOVED_ALIAS".to_string());
1743            assert!(state.aliases.remove(&alias_key).is_some());
1744            publish_semantic_change(&mut state, [alias_key]);
1745        }
1746        assert!(semantic_changes_affect_requests_since(
1747            remove_epoch,
1748            [(ns.to_string(), "REMOVED_ALIAS".to_string(), 1)]
1749        ));
1750        assert!(!semantic_changes_affect_requests_since(
1751            remove_epoch,
1752            [(ns.to_string(), "OLD_TARGET".to_string(), 1)]
1753        ));
1754    }
1755
1756    #[test]
1757    fn request_change_check_stays_conservative_after_log_truncation() {
1758        let mut state = RegistryState::default();
1759        let before = state.semantic_epoch;
1760        for index in 0..=1_024 {
1761            publish_semantic_change(&mut state, [(String::new(), format!("UNRELATED_{index}"))]);
1762        }
1763
1764        assert!(semantic_changes_affect_requests_in_state(
1765            &state,
1766            before,
1767            [(String::new(), "TARGET".to_string(), 1)]
1768        ));
1769    }
1770
1771    #[test]
1772    fn replacement_advances_semantic_generation() {
1773        let ns = "__REG_GENERATION__";
1774        register_function(Arc::new(TestFn {
1775            ns,
1776            name: "F",
1777            aliases: &[],
1778        }));
1779        let first = resolve(ns, "F").unwrap().semantics.generation;
1780        let epoch = semantic_epoch();
1781        register_function(Arc::new(TestFn {
1782            ns,
1783            name: "F",
1784            aliases: &[],
1785        }));
1786        let second = resolve(ns, "F").unwrap().semantics.generation;
1787        assert!(second > first);
1788        let changes = semantic_changes_since(epoch);
1789        assert!(changes.epoch > epoch);
1790        assert!(changes.keys.contains(&(ns.to_string(), "F".to_string())));
1791    }
1792
1793    struct PanickingSchemaFn;
1794
1795    impl Function for PanickingSchemaFn {
1796        fn name(&self) -> &'static str {
1797            "PANICKING_SCHEMA"
1798        }
1799        fn semantic_contract(&self, _arity: usize) -> Option<FunctionSemanticContract> {
1800            Some(FunctionSemanticContract::trusted_builtin_default(None))
1801        }
1802        fn arg_schema(&self) -> &'static [crate::args::ArgSchema] {
1803            panic!("bad schema")
1804        }
1805        fn eval<'a, 'b, 'c>(
1806            &self,
1807            _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
1808            _ctx: &dyn crate::traits::FunctionContext<'b>,
1809        ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
1810            unreachable!()
1811        }
1812    }
1813
1814    #[test]
1815    fn schema_panic_as_sole_defect_is_non_panicking_and_fails_closed() {
1816        register_function(Arc::new(PanickingSchemaFn));
1817        let semantics = resolve("", "PANICKING_SCHEMA").unwrap().semantics;
1818        assert!(semantics.contract.is_none());
1819        assert!(
1820            semantics
1821                .issues
1822                .contains(&SemanticConformanceIssue::ArgumentSchemaPanicked)
1823        );
1824        assert_eq!(
1825            semantics.issues,
1826            vec![SemanticConformanceIssue::ArgumentSchemaPanicked]
1827        );
1828        assert!(!semantics.conforms());
1829    }
1830
1831    #[test]
1832    fn every_registered_builtin_has_a_conforming_semantic_contract() {
1833        crate::builtins::load_builtins();
1834        let builtins: Vec<_> = snapshot_semantics()
1835            .into_iter()
1836            .filter(|entry| entry.semantics.trusted_builtin)
1837            .collect();
1838        assert!(builtins.len() > 100);
1839        let rejected: Vec<_> = builtins
1840            .iter()
1841            .filter(|entry| !entry.semantics.conforms())
1842            .map(|entry| {
1843                (
1844                    &entry.namespace,
1845                    &entry.canonical_name,
1846                    &entry.semantics.issues,
1847                )
1848            })
1849            .collect();
1850        assert!(rejected.is_empty(), "non-conforming builtins: {rejected:?}");
1851    }
1852
1853    #[test]
1854    fn semantic_contract_is_context_and_arity_aware() {
1855        crate::builtins::lookup::register_builtins();
1856        let row_without_arg = resolve_for_arity("", "ROW", 0).unwrap();
1857        let row_with_arg = resolve_for_arity("", "ROW", 1).unwrap();
1858        assert_eq!(
1859            row_without_arg.semantics.contract.unwrap().context,
1860            crate::function_contract::FunctionContextDependence::PlacementDependent
1861        );
1862        assert_eq!(
1863            row_with_arg.semantics.contract.unwrap().context,
1864            crate::function_contract::FunctionContextDependence::None
1865        );
1866    }
1867
1868    #[test]
1869    fn semantic_identity_encodes_effective_by_reference_roles_for_call_arity() {
1870        crate::builtins::load_builtins();
1871        let provider = GlobalRegistryFunctionProvider;
1872        let sum = resolve_semantic_identity(&provider, "", "SUM", 3).unwrap();
1873        assert_eq!(sum.argument_by_ref, vec![false, false, false]);
1874
1875        let row = resolve_semantic_identity(&provider, "", "ROW", 1).unwrap();
1876        assert_eq!(row.argument_by_ref, vec![true]);
1877    }
1878
1879    struct ExplicitSafeCustomFn;
1880
1881    impl Function for ExplicitSafeCustomFn {
1882        fn name(&self) -> &'static str {
1883            "EXPLICIT_SAFE_CUSTOM"
1884        }
1885        fn semantic_contract(&self, _arity: usize) -> Option<FunctionSemanticContract> {
1886            Some(FunctionSemanticContract::trusted_builtin_default(None))
1887        }
1888        fn eval<'a, 'b, 'c>(
1889            &self,
1890            _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
1891            _ctx: &dyn crate::traits::FunctionContext<'b>,
1892        ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
1893            unreachable!()
1894        }
1895    }
1896
1897    struct MismatchedPrecisionCustomFn;
1898
1899    impl Function for MismatchedPrecisionCustomFn {
1900        fn name(&self) -> &'static str {
1901            "MISMATCHED_PRECISION_CUSTOM"
1902        }
1903        fn dependency_contract(
1904            &self,
1905            arity: usize,
1906        ) -> Option<crate::function_contract::FunctionDependencyContract> {
1907            crate::function_contract::FunctionDependencyContract::static_scalar_all_args(arity)
1908        }
1909        fn semantic_contract(&self, _arity: usize) -> Option<FunctionSemanticContract> {
1910            Some(FunctionSemanticContract::trusted_builtin_default(None))
1911        }
1912        fn min_args(&self) -> usize {
1913            1
1914        }
1915        fn arg_schema(&self) -> &'static [crate::args::ArgSchema] {
1916            static SCHEMA: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
1917                std::sync::LazyLock::new(|| vec![crate::args::ArgSchema::any()]);
1918            &SCHEMA
1919        }
1920        fn eval<'a, 'b, 'c>(
1921            &self,
1922            _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
1923            _ctx: &dyn crate::traits::FunctionContext<'b>,
1924        ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
1925            unreachable!()
1926        }
1927    }
1928
1929    #[test]
1930    fn explicit_precision_must_equal_dependency_contract() {
1931        register_function(Arc::new(MismatchedPrecisionCustomFn));
1932        let semantics = resolve_for_arity("", "MISMATCHED_PRECISION_CUSTOM", 1)
1933            .unwrap()
1934            .semantics;
1935        assert!(semantics.contract.is_none());
1936        assert!(
1937            semantics
1938                .issues
1939                .contains(&SemanticConformanceIssue::PrecisionContractMismatch)
1940        );
1941    }
1942
1943    #[test]
1944    fn explicit_custom_semantics_can_conform_without_becoming_trusted() {
1945        register_function(Arc::new(ExplicitSafeCustomFn));
1946        let semantics = resolve_for_arity("", "EXPLICIT_SAFE_CUSTOM", 0)
1947            .unwrap()
1948            .semantics;
1949        assert!(!semantics.trusted_builtin);
1950        assert!(semantics.conforms());
1951    }
1952
1953    #[test]
1954    fn concurrent_replacements_leave_only_final_owned_alias() {
1955        let ns = "__REG_CONCURRENT__";
1956        register_function(Arc::new(TestFn {
1957            ns,
1958            name: "TARGET",
1959            aliases: &["INITIAL"],
1960        }));
1961        let mut workers = Vec::new();
1962        for alias in ["A", "B", "C", "D"] {
1963            workers.push(std::thread::spawn(move || {
1964                for _ in 0..100 {
1965                    let aliases: &'static [&'static str] = Box::leak(Box::new([alias]));
1966                    register_function(Arc::new(TestFn {
1967                        ns,
1968                        name: "TARGET",
1969                        aliases,
1970                    }));
1971                    assert_eq!(get(ns, "TARGET").unwrap().name(), "TARGET");
1972                }
1973            }));
1974        }
1975        for worker in workers {
1976            worker.join().unwrap();
1977        }
1978        register_function(Arc::new(TestFn {
1979            ns,
1980            name: "TARGET",
1981            aliases: &["FINAL"],
1982        }));
1983        for stale in ["INITIAL", "A", "B", "C", "D"] {
1984            assert!(get(ns, stale).is_none());
1985        }
1986        assert!(get(ns, "FINAL").is_some());
1987    }
1988
1989    #[test]
1990    fn independent_exception_inventory_matches_builtin_caps_and_context() {
1991        crate::builtins::load_builtins();
1992        for name in [
1993            "RAND",
1994            "RANDBETWEEN",
1995            "RANDARRAY",
1996            "TODAY",
1997            "NOW",
1998            "OFFSET",
1999            "INDIRECT",
2000        ] {
2001            assert!(
2002                get("", name).unwrap().caps().contains(FnCaps::VOLATILE),
2003                "{name}"
2004            );
2005        }
2006        for name in ["OFFSET", "INDIRECT"] {
2007            assert!(
2008                get("", name)
2009                    .unwrap()
2010                    .caps()
2011                    .contains(FnCaps::DYNAMIC_DEPENDENCY),
2012                "{name}"
2013            );
2014        }
2015        for name in ["INDEX", "OFFSET", "INDIRECT", "CHOOSE"] {
2016            assert!(
2017                get("", name)
2018                    .unwrap()
2019                    .caps()
2020                    .contains(FnCaps::RETURNS_REFERENCE),
2021                "{name}"
2022            );
2023        }
2024        for name in ["LET", "LAMBDA"] {
2025            assert!(
2026                get("", name)
2027                    .unwrap()
2028                    .caps()
2029                    .contains(FnCaps::LOCAL_ENVIRONMENT),
2030                "{name}"
2031            );
2032        }
2033        for name in [
2034            "IF",
2035            "IFERROR",
2036            "IFNA",
2037            "IFS",
2038            "SWITCH",
2039            "CHOOSE",
2040            "FILTER",
2041            "UNIQUE",
2042            "SEQUENCE",
2043            "TRANSPOSE",
2044            "TAKE",
2045            "DROP",
2046            "SORT",
2047            "SORTBY",
2048            "RANDARRAY",
2049            "HSTACK",
2050            "VSTACK",
2051            "TOCOL",
2052            "TOROW",
2053            "CHOOSECOLS",
2054            "CHOOSEROWS",
2055            "FREQUENCY",
2056            "LINEST",
2057            "TREND",
2058            "GROWTH",
2059            "LOGEST",
2060            "MODE.MULT",
2061            "TEXTSPLIT",
2062        ] {
2063            assert!(
2064                get("", name).unwrap().caps().contains(FnCaps::MAY_SPILL),
2065                "{name}"
2066            );
2067        }
2068        const SHORT_CIRCUIT: &[&str] = &[
2069            "IF", "IFERROR", "IFNA", "IFS", "SWITCH", "CHOOSE", "LET", "LAMBDA", "AND", "OR",
2070        ];
2071        let observed_short_circuit: std::collections::BTreeSet<_> = snapshot_registered()
2072            .into_iter()
2073            .filter(|(namespace, _, function)| {
2074                namespace.is_empty() && function.caps().contains(FnCaps::SHORT_CIRCUIT)
2075            })
2076            .map(|(_, name, _)| name)
2077            .collect();
2078        let expected_short_circuit: std::collections::BTreeSet<_> = SHORT_CIRCUIT
2079            .iter()
2080            .map(|name| (*name).to_string())
2081            .collect();
2082        assert_eq!(observed_short_circuit, expected_short_circuit);
2083        for name in SHORT_CIRCUIT {
2084            let contract = resolve_for_arity("", name, get("", name).unwrap().min_args())
2085                .unwrap()
2086                .semantics
2087                .contract
2088                .unwrap();
2089            assert_eq!(
2090                contract.evaluation,
2091                FunctionEvaluationSemantics::ShortCircuit,
2092                "{name}"
2093            );
2094        }
2095        assert_eq!(
2096            resolve_for_arity("", "CHOOSE", 2)
2097                .unwrap()
2098                .semantics
2099                .contract
2100                .unwrap()
2101                .result,
2102            FunctionResultSemantics::MayReturnReferenceAndSpill
2103        );
2104        for name in ["ROW", "COLUMN"] {
2105            let contract = resolve_for_arity("", name, 0)
2106                .unwrap()
2107                .semantics
2108                .contract
2109                .unwrap();
2110            assert_eq!(
2111                contract.context,
2112                crate::function_contract::FunctionContextDependence::PlacementDependent,
2113                "{name}"
2114            );
2115            assert_eq!(
2116                resolve_for_arity("", name, 1)
2117                    .unwrap()
2118                    .semantics
2119                    .contract
2120                    .unwrap()
2121                    .context,
2122                crate::function_contract::FunctionContextDependence::None,
2123                "{name} with argument"
2124            );
2125        }
2126        for name in ["CELL", "ISFORMULA", "FORMULATEXT", "SHEET", "SHEETS"] {
2127            let contract = resolve_for_arity("", name, get("", name).unwrap().min_args())
2128                .unwrap()
2129                .semantics
2130                .contract
2131                .unwrap();
2132            assert_eq!(
2133                contract.context,
2134                crate::function_contract::FunctionContextDependence::WorkbookMetadata,
2135                "{name}"
2136            );
2137        }
2138    }
2139
2140    struct NamePanicFn;
2141    impl Function for NamePanicFn {
2142        fn name(&self) -> &'static str {
2143            panic!("name")
2144        }
2145        fn eval<'a, 'b, 'c>(
2146            &self,
2147            _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
2148            _ctx: &dyn crate::traits::FunctionContext<'b>,
2149        ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
2150            unreachable!()
2151        }
2152    }
2153    struct NamespacePanicFn;
2154    impl Function for NamespacePanicFn {
2155        fn name(&self) -> &'static str {
2156            "NS_PANIC"
2157        }
2158        fn namespace(&self) -> &'static str {
2159            panic!("namespace")
2160        }
2161        fn eval<'a, 'b, 'c>(
2162            &self,
2163            _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
2164            _ctx: &dyn crate::traits::FunctionContext<'b>,
2165        ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
2166            unreachable!()
2167        }
2168    }
2169
2170    #[test]
2171    fn canonical_metadata_panics_decline_registration_without_unwinding() {
2172        assert_eq!(
2173            try_register_function(Arc::new(NamePanicFn)),
2174            Err(RegistrationError::NameMetadataPanicked)
2175        );
2176        assert_eq!(
2177            try_register_function(Arc::new(NamespacePanicFn)),
2178            Err(RegistrationError::NamespaceMetadataPanicked)
2179        );
2180        register_function(Arc::new(NamePanicFn));
2181        register_function(Arc::new(NamespacePanicFn));
2182        assert!(get("", "NS_PANIC").is_none());
2183    }
2184
2185    #[derive(Clone, Copy)]
2186    enum BadSchemaKind {
2187        TooLarge,
2188        Repeating,
2189        MinDisagreement,
2190        RequiredCount,
2191        TooManyRequired,
2192        RepeatWidth,
2193    }
2194    struct BadSchemaFn {
2195        name: &'static str,
2196        kind: BadSchemaKind,
2197    }
2198    impl Function for BadSchemaFn {
2199        fn name(&self) -> &'static str {
2200            self.name
2201        }
2202        fn min_args(&self) -> usize {
2203            if matches!(
2204                self.kind,
2205                BadSchemaKind::MinDisagreement
2206                    | BadSchemaKind::RequiredCount
2207                    | BadSchemaKind::RepeatWidth
2208            ) {
2209                2
2210            } else {
2211                1
2212            }
2213        }
2214        fn variadic(&self) -> bool {
2215            matches!(self.kind, BadSchemaKind::RepeatWidth)
2216        }
2217        fn semantic_contract(&self, _arity: usize) -> Option<FunctionSemanticContract> {
2218            Some(FunctionSemanticContract::trusted_builtin_default(None))
2219        }
2220        fn arg_schema(&self) -> &'static [crate::args::ArgSchema] {
2221            static ONE: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
2222                std::sync::LazyLock::new(|| vec![crate::args::ArgSchema::any()]);
2223            static BAD_REPEAT: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
2224                std::sync::LazyLock::new(|| {
2225                    let mut arg = crate::args::ArgSchema::any();
2226                    arg.repeating = Some(0);
2227                    vec![arg]
2228                });
2229            static REQUIRED_COUNT: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
2230                std::sync::LazyLock::new(|| {
2231                    let mut optional = crate::args::ArgSchema::any();
2232                    optional.required = false;
2233                    vec![crate::args::ArgSchema::any(), optional]
2234                });
2235            static REPEAT_WIDTH: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
2236                std::sync::LazyLock::new(|| {
2237                    let first = crate::args::ArgSchema::any();
2238                    let mut second = crate::args::ArgSchema::any();
2239                    second.repeating = Some(2);
2240                    vec![first, second]
2241                });
2242            match self.kind {
2243                BadSchemaKind::TooLarge => &ONE,
2244                BadSchemaKind::Repeating => &BAD_REPEAT,
2245                BadSchemaKind::MinDisagreement => &[],
2246                BadSchemaKind::RequiredCount => &REQUIRED_COUNT,
2247                BadSchemaKind::TooManyRequired => &REPEAT_WIDTH,
2248                BadSchemaKind::RepeatWidth => &REPEAT_WIDTH,
2249            }
2250        }
2251        fn eval<'a, 'b, 'c>(
2252            &self,
2253            _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
2254            _ctx: &dyn crate::traits::FunctionContext<'b>,
2255        ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
2256            unreachable!()
2257        }
2258    }
2259
2260    #[test]
2261    fn malformed_arity_and_schema_contracts_fail_closed() {
2262        for (name, kind, arity) in [
2263            ("TOO_LARGE", BadSchemaKind::TooLarge, 2),
2264            ("BAD_REPEAT", BadSchemaKind::Repeating, 1),
2265            ("MIN_DISAGREEMENT", BadSchemaKind::MinDisagreement, 2),
2266            ("REQUIRED_COUNT", BadSchemaKind::RequiredCount, 2),
2267            ("TOO_MANY_REQUIRED", BadSchemaKind::TooManyRequired, 1),
2268            ("REPEAT_WIDTH", BadSchemaKind::RepeatWidth, 3),
2269        ] {
2270            register_function(Arc::new(BadSchemaFn { name, kind }));
2271            let semantics = resolve_for_arity("", name, arity).unwrap().semantics;
2272            assert!(semantics.contract.is_none(), "{name}");
2273            assert!(
2274                semantics
2275                    .issues
2276                    .contains(&SemanticConformanceIssue::AritySchemaMismatch),
2277                "{name}: {:?}",
2278                semantics.issues
2279            );
2280        }
2281    }
2282
2283    #[test]
2284    fn valid_optional_and_width_n_repeating_schemas_conform() {
2285        let required = crate::args::ArgSchema::any();
2286        let mut optional = crate::args::ArgSchema::any();
2287        optional.required = false;
2288        assert!(schema_allows_arity(
2289            &[required.clone(), optional],
2290            1,
2291            false,
2292            2,
2293            true
2294        ));
2295
2296        let mut repeat_end = crate::args::ArgSchema::any();
2297        repeat_end.repeating = Some(2);
2298        let repeating = [required, repeat_end];
2299        assert!(schema_allows_arity(&repeating, 2, true, 4, true));
2300        assert!(!schema_allows_arity(&repeating, 2, true, 3, true));
2301    }
2302
2303    #[test]
2304    fn replacement_readers_observe_generation_and_epoch_atomically() {
2305        let ns = "__REG_SNAPSHOT_RACE__";
2306        register_builtin(Arc::new(TestFn {
2307            ns,
2308            name: "TARGET",
2309            aliases: &[],
2310        }));
2311        let (initial_epoch, initial) = resolve_with_epoch(ns, "TARGET").unwrap();
2312        let initial_generation = initial.semantics.generation;
2313        let barrier = Arc::new(std::sync::Barrier::new(5));
2314        let mut readers = Vec::new();
2315        for _ in 0..4 {
2316            let barrier = Arc::clone(&barrier);
2317            readers.push(std::thread::spawn(move || {
2318                barrier.wait();
2319                for _ in 0..1_000 {
2320                    let (epoch, resolved) = resolve_with_epoch(ns, "TARGET").unwrap();
2321                    if resolved.semantics.generation != initial_generation {
2322                        assert!(epoch > initial_epoch);
2323                    }
2324                }
2325            }));
2326        }
2327        barrier.wait();
2328        register_function(Arc::new(TestFn {
2329            ns,
2330            name: "TARGET",
2331            aliases: &[],
2332        }));
2333        for reader in readers {
2334            reader.join().unwrap();
2335        }
2336        let (epoch, resolved) = resolve_with_epoch(ns, "TARGET").unwrap();
2337        assert!(epoch > initial_epoch);
2338        assert!(resolved.semantics.generation > initial_generation);
2339    }
2340}