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