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