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