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