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