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