1use crate::function::{FnCaps, Function};
2use crate::function_contract::{
3 FunctionDependencySemantics, FunctionEnvironmentSemantics, FunctionEvaluationSemantics,
4 FunctionResultSemantics, FunctionSemanticContract, FunctionSemanticIdentity,
5};
6use once_cell::sync::Lazy;
7use std::collections::{HashMap, VecDeque};
8use std::panic::{AssertUnwindSafe, catch_unwind};
9use std::sync::atomic::{AtomicU64, Ordering};
10use std::sync::{Arc, RwLock};
11
12type RegistryKey = (String, String);
13
14#[derive(Clone)]
15struct RegistryEntry {
16 function: Arc<dyn Function>,
17 generation: u64,
18 trusted_builtin: bool,
19 semantics: SemanticContractResolution,
20 semantics_by_arity: Arc<RwLock<HashMap<usize, SemanticContractResolution>>>,
21}
22
23#[derive(Clone)]
24struct AliasEntry {
25 target: RegistryKey,
26 owner: Option<(RegistryKey, u64)>,
27}
28
29struct RegistryState {
30 registrations: HashMap<RegistryKey, RegistryEntry>,
31 aliases: HashMap<RegistryKey, AliasEntry>,
32 semantic_epoch: u64,
33 semantic_changes: VecDeque<(u64, Vec<RegistryKey>)>,
34}
35
36impl Default for RegistryState {
37 fn default() -> Self {
38 Self {
39 registrations: HashMap::new(),
40 aliases: HashMap::new(),
41 semantic_epoch: 1,
42 semantic_changes: VecDeque::new(),
43 }
44 }
45}
46
47#[derive(Clone, Debug, PartialEq, Eq)]
48#[non_exhaustive]
49pub enum SemanticConformanceIssue {
50 CapabilityPanicked,
51 DependencyContractPanicked,
52 SemanticContractPanicked,
53 ArityMetadataPanicked,
54 VariadicMetadataPanicked,
55 AliasMetadataPanicked,
56 ArgumentSchemaPanicked,
57 AritySchemaMismatch,
58 DynamicDependencyMismatch,
59 ShortCircuitMismatch,
60 ReferenceResultMismatch,
61 LocalEnvironmentMismatch,
62 SpillResultMismatch,
63 PrecisionContractMismatch,
64 PrecisionContractInvalid,
65}
66
67#[derive(Clone, Debug)]
68#[non_exhaustive]
69pub struct SemanticContractResolution {
70 pub contract: Option<FunctionSemanticContract>,
71 pub generation: u64,
72 pub trusted_builtin: bool,
73 pub issues: Vec<SemanticConformanceIssue>,
74}
75
76impl SemanticContractResolution {
77 pub fn conforms(&self) -> bool {
78 self.contract.is_some() && self.issues.is_empty()
79 }
80}
81
82#[derive(Clone)]
83#[non_exhaustive]
84pub struct ResolvedFunction {
85 pub namespace: String,
86 pub canonical_name: String,
87 pub function: Arc<dyn Function>,
88 pub semantics: SemanticContractResolution,
89}
90
91static REGISTRY: Lazy<RwLock<RegistryState>> = Lazy::new(|| RwLock::new(RegistryState::default()));
92static NEXT_GENERATION: AtomicU64 = AtomicU64::new(1);
93
94#[inline]
95fn norm<S: AsRef<str>>(s: S) -> String {
96 s.as_ref().to_uppercase()
97}
98
99pub fn semantic_epoch() -> u64 {
100 REGISTRY
101 .read()
102 .unwrap_or_else(|poisoned| poisoned.into_inner())
103 .semantic_epoch
104}
105
106pub(crate) struct SemanticEpochReadGuard(std::sync::RwLockReadGuard<'static, RegistryState>);
107
108impl SemanticEpochReadGuard {
109 pub(crate) fn epoch(&self) -> u64 {
110 self.0.semantic_epoch
111 }
112
113 pub(crate) fn semantic_changes_affect_requests_since(
114 &self,
115 epoch: u64,
116 requests: impl IntoIterator<Item = (String, String, usize)>,
117 ) -> bool {
118 semantic_changes_affect_requests_in_state(&self.0, epoch, requests)
119 }
120}
121
122pub(crate) fn semantic_epoch_read_guard() -> SemanticEpochReadGuard {
123 SemanticEpochReadGuard(
124 REGISTRY
125 .read()
126 .unwrap_or_else(|poisoned| poisoned.into_inner()),
127 )
128}
129
130pub(crate) struct SemanticChanges {
131 pub(crate) epoch: u64,
132 pub(crate) complete: bool,
133 pub(crate) keys: Vec<(String, String)>,
134}
135
136fn publish_semantic_change(state: &mut RegistryState, keys: impl IntoIterator<Item = RegistryKey>) {
137 state.semantic_epoch = state.semantic_epoch.saturating_add(1);
138 let epoch = state.semantic_epoch;
139 state
140 .semantic_changes
141 .push_back((epoch, keys.into_iter().collect()));
142 if state.semantic_changes.len() > 1_024 {
143 state.semantic_changes.pop_front();
144 }
145}
146
147pub(crate) fn semantic_changes_since(epoch: u64) -> SemanticChanges {
148 let state = REGISTRY
149 .read()
150 .unwrap_or_else(|poisoned| poisoned.into_inner());
151 semantic_changes_since_in_state(&state, epoch)
152}
153
154fn semantic_changes_since_in_state(state: &RegistryState, epoch: u64) -> SemanticChanges {
155 let complete = state
156 .semantic_changes
157 .front()
158 .is_none_or(|(oldest, _)| epoch.saturating_add(1) >= *oldest);
159 let keys = state
160 .semantic_changes
161 .iter()
162 .filter(|(changed_epoch, _)| *changed_epoch > epoch)
163 .flat_map(|(_, keys)| keys.iter().cloned())
164 .collect();
165 SemanticChanges {
166 epoch: state.semantic_epoch,
167 complete,
168 keys,
169 }
170}
171
172#[derive(Clone, Debug, PartialEq, Eq)]
173#[non_exhaustive]
174pub enum RegistrationError {
175 NameMetadataPanicked,
176 NamespaceMetadataPanicked,
177}
178
179pub fn try_register_function(function: Arc<dyn Function>) -> Result<(), RegistrationError> {
180 register(function, false)
181}
182
183pub fn register_function(function: Arc<dyn Function>) {
184 let _ = try_register_function(function);
185}
186pub(crate) fn register_builtin(function: Arc<dyn Function>) {
187 register(function, true).expect("builtin name and namespace metadata must not panic");
188}
189
190fn register(function: Arc<dyn Function>, trusted_builtin: bool) -> Result<(), RegistrationError> {
191 let namespace = catch_unwind(AssertUnwindSafe(|| function.namespace()))
192 .map_err(|_| RegistrationError::NamespaceMetadataPanicked)?;
193 let name = catch_unwind(AssertUnwindSafe(|| function.name()))
194 .map_err(|_| RegistrationError::NameMetadataPanicked)?;
195 let key = (norm(namespace), norm(name));
196 let generation = NEXT_GENERATION.fetch_add(1, Ordering::Relaxed);
197 let aliases = catch_unwind(AssertUnwindSafe(|| function.aliases().to_vec()));
198 let min_args = catch_unwind(AssertUnwindSafe(|| function.min_args()));
199 let initial_arity = min_args.as_ref().copied().unwrap_or(0);
200 let mut semantics = match min_args {
201 Ok(arity) => inspect_semantics(&function, trusted_builtin, generation, arity),
202 Err(_) => failed_resolution(
203 generation,
204 trusted_builtin,
205 SemanticConformanceIssue::ArityMetadataPanicked,
206 ),
207 };
208 let aliases = match aliases {
209 Ok(aliases) => aliases,
210 Err(_) => {
211 semantics
212 .issues
213 .push(SemanticConformanceIssue::AliasMetadataPanicked);
214 semantics.contract = None;
215 Vec::new()
216 }
217 };
218
219 let mut state = REGISTRY
220 .write()
221 .unwrap_or_else(|poisoned| poisoned.into_inner());
222 if trusted_builtin
223 && state
224 .registrations
225 .get(&key)
226 .is_some_and(|entry| entry.trusted_builtin)
227 {
228 return Ok(());
229 }
230 let previous = state
231 .registrations
232 .get(&key)
233 .map(|entry| (entry.generation, entry.trusted_builtin));
234 let mut changed_spellings = Vec::new();
235 if let Some((previous_generation, _)) = previous {
236 changed_spellings.extend(
237 state
238 .aliases
239 .iter()
240 .filter(|(_, alias)| {
241 alias.owner.as_ref() == Some(&(key.clone(), previous_generation))
242 })
243 .map(|(alias_key, _)| alias_key.clone()),
244 );
245 state
246 .aliases
247 .retain(|_, alias| alias.owner.as_ref() != Some(&(key.clone(), previous_generation)));
248 }
249 state.registrations.insert(
250 key.clone(),
251 RegistryEntry {
252 function: Arc::clone(&function),
253 generation,
254 trusted_builtin,
255 semantics: semantics.clone(),
256 semantics_by_arity: Arc::new(RwLock::new(HashMap::from([(initial_arity, semantics)]))),
257 },
258 );
259 for alias in aliases {
260 if !alias.eq_ignore_ascii_case(&key.1) {
261 let alias_key = (key.0.clone(), norm(alias));
262 changed_spellings.push(alias_key.clone());
263 state.aliases.insert(
264 alias_key,
265 AliasEntry {
266 target: key.clone(),
267 owner: Some((key.clone(), generation)),
268 },
269 );
270 }
271 }
272 changed_spellings.push(key);
273 publish_semantic_change(&mut state, changed_spellings);
274 Ok(())
275}
276
277fn failed_resolution(
278 generation: u64,
279 trusted_builtin: bool,
280 issue: SemanticConformanceIssue,
281) -> SemanticContractResolution {
282 SemanticContractResolution {
283 contract: None,
284 generation,
285 trusted_builtin,
286 issues: vec![issue],
287 }
288}
289
290fn inspect_semantics(
291 function: &Arc<dyn Function>,
292 trusted_builtin: bool,
293 generation: u64,
294 arity: usize,
295) -> SemanticContractResolution {
296 inspect_semantics_with_identity_metadata(function, trusted_builtin, generation, arity).0
297}
298
299fn inspect_semantics_with_identity_metadata(
300 function: &Arc<dyn Function>,
301 trusted_builtin: bool,
302 generation: u64,
303 arity: usize,
304) -> (SemanticContractResolution, Option<(FnCaps, Vec<bool>)>) {
305 let mut issues = Vec::new();
306 let inspected_caps = inspected(
307 &mut issues,
308 SemanticConformanceIssue::CapabilityPanicked,
309 || function.caps(),
310 );
311 let caps = inspected_caps.unwrap_or_else(FnCaps::empty);
312 let precision = inspected(
313 &mut issues,
314 SemanticConformanceIssue::DependencyContractPanicked,
315 || function.dependency_contract(arity),
316 )
317 .flatten();
318 let explicit = inspected(
319 &mut issues,
320 SemanticConformanceIssue::SemanticContractPanicked,
321 || function.semantic_contract(arity),
322 )
323 .flatten();
324 let schema = inspected(
325 &mut issues,
326 SemanticConformanceIssue::ArgumentSchemaPanicked,
327 || function.arg_schema(),
328 );
329 let min_args = inspected(
330 &mut issues,
331 SemanticConformanceIssue::ArityMetadataPanicked,
332 || function.min_args(),
333 );
334 let variadic = inspected(
335 &mut issues,
336 SemanticConformanceIssue::VariadicMetadataPanicked,
337 || function.variadic(),
338 );
339 if let (Some(schema), Some(min_args), Some(variadic)) = (schema, min_args, variadic)
340 && !schema_allows_arity(schema, min_args, variadic, arity, !trusted_builtin)
341 {
342 issues.push(SemanticConformanceIssue::AritySchemaMismatch);
343 }
344 let contract =
345 explicit.or_else(|| trusted_builtin.then(|| trusted_contract_from_caps(caps, precision)));
346 if let Some(contract) = contract {
347 if contract.precision != precision {
348 issues.push(SemanticConformanceIssue::PrecisionContractMismatch);
349 }
350 if !precision_is_valid(contract, arity) {
351 issues.push(SemanticConformanceIssue::PrecisionContractInvalid);
352 }
353 check_capability(
354 &mut issues,
355 caps.contains(FnCaps::DYNAMIC_DEPENDENCY),
356 contract.dependency == FunctionDependencySemantics::Dynamic,
357 SemanticConformanceIssue::DynamicDependencyMismatch,
358 );
359 check_capability(
360 &mut issues,
361 caps.contains(FnCaps::SHORT_CIRCUIT),
362 contract.evaluation == FunctionEvaluationSemantics::ShortCircuit,
363 SemanticConformanceIssue::ShortCircuitMismatch,
364 );
365 check_capability(
366 &mut issues,
367 caps.contains(FnCaps::RETURNS_REFERENCE),
368 contract.result.may_return_reference(),
369 SemanticConformanceIssue::ReferenceResultMismatch,
370 );
371 check_capability(
372 &mut issues,
373 caps.contains(FnCaps::LOCAL_ENVIRONMENT),
374 contract.environment == FunctionEnvironmentSemantics::LocalBindings,
375 SemanticConformanceIssue::LocalEnvironmentMismatch,
376 );
377 check_capability(
378 &mut issues,
379 caps.contains(FnCaps::MAY_SPILL),
380 contract.result.may_spill(),
381 SemanticConformanceIssue::SpillResultMismatch,
382 );
383 }
384 let identity_metadata = inspected_caps.zip(schema).map(|(caps, schema)| {
385 let repeating = schema.iter().find(|argument| argument.repeating.is_some());
386 let argument_by_ref = (0..arity)
387 .map(|index| {
388 schema
389 .get(index)
390 .or(repeating)
391 .is_some_and(|argument| argument.by_ref)
392 })
393 .collect();
394 (caps, argument_by_ref)
395 });
396 (
397 SemanticContractResolution {
398 contract: issues.is_empty().then_some(contract).flatten(),
399 generation,
400 trusted_builtin,
401 issues,
402 },
403 identity_metadata,
404 )
405}
406
407fn precision_is_valid(contract: FunctionSemanticContract, arity: usize) -> bool {
408 use crate::function_contract::{
409 CriteriaValueRange, FunctionArgumentDependencyContract as Arguments,
410 FunctionArgumentDependencyRole as Role,
411 };
412 let Some(precision) = contract.precision else {
413 return true;
414 };
415 if !precision.arity.allows(arity)
416 || contract.dependency != FunctionDependencySemantics::RecursiveSyntacticArgs
417 {
418 return false;
419 }
420 match precision.arguments {
421 Arguments::AllArgs(role) | Arguments::Variadic(role) => {
422 !matches!(role, Role::IgnoredLiteral | Role::Unsupported)
423 }
424 Arguments::CriteriaPairs(criteria) => {
425 let value_valid = match criteria.value_range {
426 CriteriaValueRange::None => true,
427 CriteriaValueRange::Fixed(index) => index < arity,
428 CriteriaValueRange::Optional {
429 provided_index,
430 fallback_criteria_range_index,
431 } => provided_index <= arity && fallback_criteria_range_index < arity,
432 };
433 let pair_end = match criteria.value_range {
434 CriteriaValueRange::Fixed(index) if index >= criteria.first_criteria_pair => index,
435 CriteriaValueRange::Optional { provided_index, .. }
436 if provided_index >= criteria.first_criteria_pair =>
437 {
438 provided_index
439 }
440 _ => arity,
441 };
442 value_valid
443 && criteria.first_criteria_pair < pair_end
444 && (pair_end - criteria.first_criteria_pair).is_multiple_of(2)
445 }
446 Arguments::LocalBindingPairs => {
447 contract.environment == FunctionEnvironmentSemantics::LocalBindings
448 && arity >= 3
449 && !arity.is_multiple_of(2)
450 }
451 Arguments::LambdaParameters => {
452 contract.environment == FunctionEnvironmentSemantics::LocalBindings && arity >= 1
453 }
454 }
455}
456
457fn schema_allows_arity(
458 schema: &[crate::args::ArgSchema],
459 min_args: usize,
460 variadic: bool,
461 arity: usize,
462 strict_required_count: bool,
463) -> bool {
464 if schema.is_empty() {
465 return min_args == 0 && arity == 0;
466 }
467
468 let mut optional_seen = false;
469 let mut required_count = 0usize;
470 let mut repeating = None;
471 for (index, argument) in schema.iter().enumerate() {
472 if argument.required {
473 if optional_seen {
474 return false;
475 }
476 required_count += 1;
477 } else {
478 optional_seen = true;
479 }
480 if let Some(width) = argument.repeating {
481 if width == 0 || repeating.is_some() || index + 1 != schema.len() {
482 return false;
483 }
484 repeating = Some(width);
485 }
486 }
487 let represented_minimum = min_args.min(schema.len());
488 if (strict_required_count && required_count != min_args)
489 || (!strict_required_count && required_count < represented_minimum)
490 || schema
491 .iter()
492 .take(represented_minimum)
493 .any(|argument| !argument.required)
494 || (!variadic && schema.len() > 1 && min_args > schema.len())
495 || arity < min_args
496 {
497 return false;
498 }
499 if let Some(width) = repeating {
500 if width > schema.len() {
501 return false;
502 }
503 let fixed_prefix = schema.len() - width;
504 return arity >= schema.len() && (arity - fixed_prefix).is_multiple_of(width);
505 }
506 if variadic {
507 return true;
508 }
509 arity <= schema.len().max(min_args)
510}
511
512fn inspected<T>(
513 issues: &mut Vec<SemanticConformanceIssue>,
514 issue: SemanticConformanceIssue,
515 inspect: impl FnOnce() -> T,
516) -> Option<T> {
517 match catch_unwind(AssertUnwindSafe(inspect)) {
518 Ok(value) => Some(value),
519 Err(_) => {
520 issues.push(issue);
521 None
522 }
523 }
524}
525
526fn check_capability(
527 issues: &mut Vec<SemanticConformanceIssue>,
528 capability: bool,
529 semantic: bool,
530 issue: SemanticConformanceIssue,
531) {
532 if capability != semantic {
533 issues.push(issue);
534 }
535}
536
537fn trusted_contract_from_caps(
538 caps: FnCaps,
539 precision: Option<crate::function_contract::FunctionDependencyContract>,
540) -> FunctionSemanticContract {
541 let mut contract = FunctionSemanticContract::trusted_builtin_default(precision);
542 if caps.contains(FnCaps::DYNAMIC_DEPENDENCY) {
543 contract.dependency = FunctionDependencySemantics::Dynamic;
544 }
545 if caps.contains(FnCaps::SHORT_CIRCUIT) {
546 contract.evaluation = FunctionEvaluationSemantics::ShortCircuit;
547 }
548 contract.result = FunctionResultSemantics::from_capabilities(
549 caps.contains(FnCaps::RETURNS_REFERENCE),
550 caps.contains(FnCaps::MAY_SPILL),
551 );
552 if caps.contains(FnCaps::LOCAL_ENVIRONMENT) {
553 contract.environment = FunctionEnvironmentSemantics::LocalBindings;
554 }
555 contract
556}
557
558const EXCEL_PREFIXES: &[&str] = &["_XLFN.", "_XLL.", "_XLWS."];
559
560fn resolve_registered(
561 state: &RegistryState,
562 key: &RegistryKey,
563) -> Option<(RegistryKey, RegistryEntry)> {
564 if let Some(entry) = state.registrations.get(key) {
565 return Some((key.clone(), entry.clone()));
566 }
567 let alias = state.aliases.get(key)?;
568 state
569 .registrations
570 .get(&alias.target)
571 .map(|entry| (alias.target.clone(), entry.clone()))
572}
573
574fn resolve_entry(ns: &str, name: &str) -> Option<(RegistryKey, RegistryEntry)> {
575 let ns = norm(ns);
576 let normalized_name = norm(name);
577 let key = (ns.clone(), normalized_name.clone());
578 let mut state = REGISTRY
579 .write()
580 .unwrap_or_else(|poisoned| poisoned.into_inner());
581 if let Some(entry) = resolve_registered(&state, &key) {
582 return Some(entry);
583 }
584 let mut candidate = normalized_name.as_str();
585 loop {
586 let mut stripped_any = false;
587 for prefix in EXCEL_PREFIXES {
588 if let Some(rest) = candidate.strip_prefix(prefix) {
589 candidate = rest;
590 stripped_any = true;
591 let stripped_key = (ns.clone(), candidate.to_string());
592 if let Some((canonical, entry)) = resolve_registered(&state, &stripped_key) {
593 state.aliases.insert(
594 key.clone(),
595 AliasEntry {
596 target: canonical.clone(),
597 owner: Some((canonical.clone(), entry.generation)),
598 },
599 );
600 return Some((canonical, entry));
601 }
602 break;
603 }
604 }
605 if !stripped_any {
606 break;
607 }
608 }
609 None
610}
611
612fn resolve_key_read_only(
613 state: &RegistryState,
614 key: &RegistryKey,
615) -> Option<(RegistryKey, RegistryEntry)> {
616 if let Some(entry) = resolve_registered(state, key) {
617 return Some(entry);
618 }
619 let mut candidate = key.1.as_str();
620 loop {
621 let rest = EXCEL_PREFIXES
622 .iter()
623 .find_map(|prefix| candidate.strip_prefix(prefix))?;
624 candidate = rest;
625 let stripped = (key.0.clone(), candidate.to_string());
626 if let Some(entry) = resolve_registered(state, &stripped) {
627 return Some(entry);
628 }
629 }
630}
631
632fn resolve_entry_read_only(ns: &str, name: &str) -> Option<(RegistryKey, RegistryEntry)> {
633 let state = REGISTRY
634 .read()
635 .unwrap_or_else(|poisoned| poisoned.into_inner());
636 resolve_key_read_only(&state, &(norm(ns), norm(name)))
637}
638
639pub fn get(ns: &str, name: &str) -> Option<Arc<dyn Function>> {
640 resolve_entry(ns, name).map(|(_, entry)| entry.function)
641}
642
643#[doc(hidden)]
646pub fn get_for_planning(ns: &str, name: &str) -> Option<Arc<dyn Function>> {
647 resolve_entry_read_only(ns, name).map(|(_, entry)| entry.function)
648}
649
650pub(crate) struct GlobalRegistryFunctionProvider;
651
652impl crate::traits::FunctionProvider for GlobalRegistryFunctionProvider {
653 fn planning_semantic_revision(&self) -> Option<u64> {
654 Some(0)
655 }
656
657 fn get_function(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
658 get(ns, name)
659 }
660
661 fn get_function_for_planning(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
662 get_for_planning(ns, name)
663 }
664}
665
666#[derive(Clone)]
667struct PlanningRegistration {
668 canonical: RegistryKey,
669 function: Arc<dyn Function>,
670 generation: u64,
671 trusted_builtin: bool,
672}
673
674#[derive(Clone)]
679pub(crate) struct RegistryPlanningSnapshot {
680 epoch: u64,
681 provider_revision: Option<u64>,
682 requests: Arc<Vec<(String, String, usize)>>,
683 functions: Arc<HashMap<RegistryKey, Arc<dyn Function>>>,
684 capabilities: Arc<HashMap<RegistryKey, FnCaps>>,
685 identities: Arc<HashMap<(String, String, usize), FunctionSemanticIdentity>>,
686}
687
688#[derive(Clone, Copy, Debug, PartialEq, Eq)]
689pub(crate) enum PlanningSnapshotError {
690 RegistryChangedDuringCapture,
691 ProviderRevisionUnavailable,
692}
693
694impl RegistryPlanningSnapshot {
695 const CAPTURE_ATTEMPTS: usize = 16;
696
697 pub(crate) fn capture_for_requests(
698 runtime_provider: &dyn crate::traits::FunctionProvider,
699 requests: impl IntoIterator<Item = (String, String, usize)>,
700 ) -> Result<Self, PlanningSnapshotError> {
701 crate::builtins::load_builtins();
702 let mut requests: Vec<_> = requests.into_iter().collect();
703 requests.sort();
704 requests.dedup();
705 Self::capture_with_hook(runtime_provider, &requests, Self::CAPTURE_ATTEMPTS, |_| {})
706 }
707
708 fn capture_with_hook(
709 runtime_provider: &dyn crate::traits::FunctionProvider,
710 requests: &[(String, String, usize)],
711 attempts: usize,
712 mut after_registry_copy: impl FnMut(usize),
713 ) -> Result<Self, PlanningSnapshotError> {
714 for attempt in 0..attempts {
715 let provider_revision = if requests.is_empty() {
716 None
717 } else {
718 Some(
719 runtime_provider
720 .planning_semantic_revision()
721 .ok_or(PlanningSnapshotError::ProviderRevisionUnavailable)?,
722 )
723 };
724 let start_epoch = REGISTRY
725 .read()
726 .unwrap_or_else(|poisoned| poisoned.into_inner())
727 .semantic_epoch;
728 let runtime_functions: HashMap<_, _> = requests
729 .iter()
730 .filter_map(|(namespace, name, _)| {
731 runtime_provider
732 .get_function_for_planning(namespace, name)
733 .map(|function| ((norm(namespace), norm(name)), function))
734 })
735 .collect();
736 let (epoch, registrations) = {
737 let state = REGISTRY
738 .read()
739 .unwrap_or_else(|poisoned| poisoned.into_inner());
740 let registrations = requests
741 .iter()
742 .filter_map(|(namespace, name, arity)| {
743 let request_key = (norm(namespace), norm(name));
744 resolve_key_read_only(&state, &request_key).map(|(canonical, entry)| {
745 (
746 (request_key.0, request_key.1, *arity),
747 PlanningRegistration {
748 canonical,
749 function: entry.function,
750 generation: entry.generation,
751 trusted_builtin: entry.trusted_builtin,
752 },
753 )
754 })
755 })
756 .collect::<HashMap<_, _>>();
757 (state.semantic_epoch, registrations)
758 };
759
760 after_registry_copy(attempt);
761
762 let mut capabilities = HashMap::new();
763 let mut identities = HashMap::new();
764 for (namespace, name, arity) in requests {
765 let request_key = (norm(namespace), norm(name));
766 let Some(runtime) = runtime_functions.get(&request_key) else {
767 continue;
768 };
769 let Some(registration) =
770 registrations.get(&(request_key.0.clone(), request_key.1.clone(), *arity))
771 else {
772 continue;
773 };
774 if !Arc::ptr_eq(runtime, ®istration.function) {
775 continue;
776 }
777 let (semantics, identity_metadata) = inspect_semantics_with_identity_metadata(
778 ®istration.function,
779 registration.trusted_builtin,
780 registration.generation,
781 *arity,
782 );
783 let Some(contract) = semantics.contract else {
784 continue;
785 };
786 let Some((caps, argument_by_ref)) = identity_metadata else {
787 continue;
788 };
789 capabilities.insert(request_key.clone(), caps);
790 identities.insert(
791 (request_key.0, request_key.1, *arity),
792 FunctionSemanticIdentity {
793 namespace: registration.canonical.0.clone(),
794 canonical_name: registration.canonical.1.clone(),
795 generation: registration.generation,
796 caps,
797 contract,
798 argument_by_ref,
799 },
800 );
801 }
802
803 for (key, function) in &runtime_functions {
804 if !capabilities.contains_key(key)
805 && let Ok(caps) = catch_unwind(AssertUnwindSafe(|| function.caps()))
806 {
807 capabilities.insert(key.clone(), caps);
808 }
809 }
810
811 let provider_unchanged = provider_revision.is_none_or(|revision| {
812 runtime_provider.planning_semantic_revision() == Some(revision)
813 });
814 let requests_unchanged = {
831 let state = REGISTRY
832 .read()
833 .unwrap_or_else(|poisoned| poisoned.into_inner());
834 !semantic_changes_affect_requests_in_state(
835 &state,
836 start_epoch,
837 requests.iter().cloned(),
838 )
839 };
840 let unchanged = requests_unchanged && provider_unchanged;
841 if unchanged {
842 return Ok(Self {
843 epoch,
844 provider_revision,
845 requests: Arc::new(requests.to_vec()),
846 functions: Arc::new(runtime_functions),
847 capabilities: Arc::new(capabilities),
848 identities: Arc::new(identities),
849 });
850 }
851 }
852 Err(PlanningSnapshotError::RegistryChangedDuringCapture)
853 }
854
855 pub(crate) fn epoch(&self) -> u64 {
856 self.epoch
857 }
858
859 pub(crate) fn provider_revision(&self) -> Option<u64> {
860 self.provider_revision
861 }
862
863 pub(crate) fn semantic_changes_affect_requests_since(&self, epoch: u64) -> bool {
864 semantic_changes_affect_requests_since(epoch, self.requests.iter().cloned())
865 }
866
867 pub(crate) fn semantic_changes_affect_requests_since_guarded(
868 &self,
869 guard: &SemanticEpochReadGuard,
870 epoch: u64,
871 ) -> bool {
872 guard.semantic_changes_affect_requests_since(epoch, self.requests.iter().cloned())
873 }
874}
875
876pub(crate) fn semantic_changes_affect_requests_since(
877 epoch: u64,
878 requests: impl IntoIterator<Item = (String, String, usize)>,
879) -> bool {
880 let state = REGISTRY
881 .read()
882 .unwrap_or_else(|poisoned| poisoned.into_inner());
883 semantic_changes_affect_requests_in_state(&state, epoch, requests)
884}
885
886fn semantic_changes_affect_requests_in_state(
887 state: &RegistryState,
888 epoch: u64,
889 requests: impl IntoIterator<Item = (String, String, usize)>,
890) -> bool {
891 let changes = semantic_changes_since_in_state(state, epoch);
892 if changes.epoch == epoch {
893 return false;
894 }
895 if !changes.complete {
896 return true;
897 }
898 let requests = requests
899 .into_iter()
900 .flat_map(|(namespace, name, _)| {
901 let namespace = norm(namespace);
902 let normalized = norm(name);
903 let mut spellings = vec![(namespace.clone(), normalized.clone())];
904 let mut stripped = normalized.as_str();
905 while let Some(rest) = EXCEL_PREFIXES
906 .iter()
907 .find_map(|prefix| stripped.strip_prefix(prefix))
908 {
909 stripped = rest;
910 spellings.push((namespace.clone(), stripped.to_string()));
911 }
912 spellings
913 })
914 .collect::<std::collections::BTreeSet<_>>();
915 changes.keys.into_iter().any(|key| requests.contains(&key))
916}
917
918impl crate::traits::FunctionProvider for RegistryPlanningSnapshot {
919 fn planning_semantic_revision(&self) -> Option<u64> {
920 Some(self.provider_revision.unwrap_or(0))
921 }
922
923 fn get_function(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
924 self.functions.get(&(norm(ns), norm(name))).cloned()
925 }
926
927 fn get_function_for_planning(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
928 self.get_function(ns, name)
929 }
930
931 fn function_capabilities(&self, ns: &str, name: &str) -> Option<FnCaps> {
932 self.capabilities.get(&(norm(ns), norm(name))).copied()
933 }
934
935 fn function_semantic_identity(
936 &self,
937 ns: &str,
938 name: &str,
939 arity: usize,
940 ) -> Option<FunctionSemanticIdentity> {
941 self.identities.get(&(norm(ns), norm(name), arity)).cloned()
942 }
943}
944
945pub fn resolve(ns: &str, name: &str) -> Option<ResolvedFunction> {
946 resolve_entry(ns, name).map(to_resolved)
947}
948
949pub fn resolve_with_epoch(ns: &str, name: &str) -> Option<(u64, ResolvedFunction)> {
950 let key = (norm(ns), norm(name));
951 let state = REGISTRY
952 .read()
953 .unwrap_or_else(|poisoned| poisoned.into_inner());
954 resolve_registered(&state, &key).map(|entry| (state.semantic_epoch, to_resolved(entry)))
955}
956pub fn resolve_for_arity(ns: &str, name: &str, arity: usize) -> Option<ResolvedFunction> {
957 resolve_entry(ns, name).map(|((namespace, canonical_name), entry)| {
958 let semantics = {
959 let cached = entry
960 .semantics_by_arity
961 .read()
962 .unwrap_or_else(|poisoned| poisoned.into_inner())
963 .get(&arity)
964 .cloned();
965 cached.unwrap_or_else(|| {
966 let inspected = inspect_semantics(
967 &entry.function,
968 entry.trusted_builtin,
969 entry.generation,
970 arity,
971 );
972 entry
973 .semantics_by_arity
974 .write()
975 .unwrap_or_else(|poisoned| poisoned.into_inner())
976 .entry(arity)
977 .or_insert_with(|| inspected.clone())
978 .clone()
979 })
980 };
981 ResolvedFunction {
982 semantics,
983 namespace,
984 canonical_name,
985 function: entry.function,
986 }
987 })
988}
989
990pub(crate) fn resolve_semantic_identity<P: crate::traits::FunctionProvider + ?Sized>(
991 provider: &P,
992 ns: &str,
993 name: &str,
994 arity: usize,
995) -> Option<FunctionSemanticIdentity> {
996 let runtime = provider.get_function(ns, name)?;
997 let resolved = resolve_for_arity(ns, name, arity)?;
998 if !Arc::ptr_eq(&runtime, &resolved.function) {
999 return None;
1000 }
1001 let contract = resolved.semantics.contract?;
1002 let argument_by_ref = catch_unwind(AssertUnwindSafe(|| {
1003 let schema = runtime.arg_schema();
1004 let repeating = schema.iter().find(|argument| argument.repeating.is_some());
1005 (0..arity)
1006 .map(|index| {
1007 schema
1008 .get(index)
1009 .or(repeating)
1010 .is_some_and(|argument| argument.by_ref)
1011 })
1012 .collect()
1013 }))
1014 .ok()?;
1015 Some(FunctionSemanticIdentity {
1016 namespace: resolved.namespace,
1017 canonical_name: resolved.canonical_name,
1018 generation: resolved.semantics.generation,
1019 caps: runtime.caps(),
1020 contract,
1021 argument_by_ref,
1022 })
1023}
1024
1025fn to_resolved(
1026 ((namespace, canonical_name), entry): (RegistryKey, RegistryEntry),
1027) -> ResolvedFunction {
1028 ResolvedFunction {
1029 namespace,
1030 canonical_name,
1031 function: entry.function,
1032 semantics: entry.semantics,
1033 }
1034}
1035
1036pub fn register_alias(ns: &str, alias: &str, target_ns: &str, target_name: &str) {
1037 let mut state = REGISTRY
1038 .write()
1039 .unwrap_or_else(|poisoned| poisoned.into_inner());
1040 let alias_key = (norm(ns), norm(alias));
1041 let target = (norm(target_ns), norm(target_name));
1042 let old_target = state
1043 .aliases
1044 .get(&alias_key)
1045 .map(|entry| entry.target.clone());
1046 if old_target.as_ref() == Some(&target) {
1047 return;
1048 }
1049 state.aliases.insert(
1050 alias_key.clone(),
1051 AliasEntry {
1052 target: target.clone(),
1053 owner: None,
1054 },
1055 );
1056 publish_semantic_change(&mut state, [alias_key]);
1059}
1060
1061pub fn snapshot_registered() -> Vec<(String, String, Arc<dyn Function>)> {
1062 let state = REGISTRY
1063 .read()
1064 .unwrap_or_else(|poisoned| poisoned.into_inner());
1065 state
1066 .registrations
1067 .iter()
1068 .map(|((ns, name), entry)| (ns.clone(), name.clone(), Arc::clone(&entry.function)))
1069 .collect()
1070}
1071pub fn snapshot_semantics() -> Vec<ResolvedFunction> {
1072 let state = REGISTRY
1073 .read()
1074 .unwrap_or_else(|poisoned| poisoned.into_inner());
1075 state
1076 .registrations
1077 .iter()
1078 .map(|((namespace, canonical_name), entry)| ResolvedFunction {
1079 namespace: namespace.clone(),
1080 canonical_name: canonical_name.clone(),
1081 function: Arc::clone(&entry.function),
1082 semantics: entry.semantics.clone(),
1083 })
1084 .collect()
1085}
1086
1087#[cfg(test)]
1088mod tests {
1089 use super::*;
1090 use crate::traits::FunctionProvider;
1091
1092 struct TestFn {
1093 ns: &'static str,
1094 name: &'static str,
1095 aliases: &'static [&'static str],
1096 }
1097
1098 impl Function for TestFn {
1099 fn name(&self) -> &'static str {
1100 self.name
1101 }
1102 fn namespace(&self) -> &'static str {
1103 self.ns
1104 }
1105 fn aliases(&self) -> &'static [&'static str] {
1106 self.aliases
1107 }
1108 fn eval<'a, 'b, 'c>(
1109 &self,
1110 _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
1111 _ctx: &dyn crate::traits::FunctionContext<'b>,
1112 ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
1113 Ok(crate::traits::CalcValue::Scalar(
1114 formualizer_common::LiteralValue::Number(1.0),
1115 ))
1116 }
1117 }
1118
1119 struct PlanningFn {
1120 ns: &'static str,
1121 name: &'static str,
1122 aliases: &'static [&'static str],
1123 caps: FnCaps,
1124 }
1125
1126 impl Function for PlanningFn {
1127 fn name(&self) -> &'static str {
1128 self.name
1129 }
1130 fn namespace(&self) -> &'static str {
1131 self.ns
1132 }
1133 fn aliases(&self) -> &'static [&'static str] {
1134 self.aliases
1135 }
1136 fn caps(&self) -> FnCaps {
1137 self.caps
1138 }
1139 fn min_args(&self) -> usize {
1140 1
1141 }
1142 fn variadic(&self) -> bool {
1143 true
1144 }
1145 fn arg_schema(&self) -> &'static [crate::args::ArgSchema] {
1146 static SCHEMA: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
1147 std::sync::LazyLock::new(|| {
1148 let mut argument = crate::args::ArgSchema::any();
1149 argument.repeating = Some(1);
1150 vec![argument]
1151 });
1152 &SCHEMA
1153 }
1154 fn eval<'a, 'b, 'c>(
1155 &self,
1156 _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
1157 _ctx: &dyn crate::traits::FunctionContext<'b>,
1158 ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
1159 unreachable!()
1160 }
1161 }
1162
1163 fn planning_fn(
1164 ns: &'static str,
1165 name: &'static str,
1166 aliases: &'static [&'static str],
1167 caps: FnCaps,
1168 ) -> Arc<dyn Function> {
1169 Arc::new(PlanningFn {
1170 ns,
1171 name,
1172 aliases,
1173 caps,
1174 })
1175 }
1176
1177 #[test]
1178 fn planning_snapshot_resolves_direct_alias_namespace_and_prefix_without_cache_mutation() {
1179 let ns = "__PLANNING_PARITY__";
1180 register_builtin(planning_fn(ns, "TARGET", &["ALIAS"], FnCaps::empty()));
1181 let requests = [
1182 (ns.to_string(), "TARGET".to_string(), 1),
1183 (ns.to_string(), "alias".to_string(), 1),
1184 (ns.to_string(), "_xlfn._xlws.alias".to_string(), 1),
1185 ];
1186 let prefixed_key = (ns.to_string(), "_XLFN._XLWS.ALIAS".to_string());
1187 assert!(!REGISTRY.read().unwrap().aliases.contains_key(&prefixed_key));
1188 let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1189 &GlobalRegistryFunctionProvider,
1190 requests,
1191 )
1192 .unwrap();
1193 assert!(!REGISTRY.read().unwrap().aliases.contains_key(&prefixed_key));
1194
1195 let direct = snapshot
1196 .function_semantic_identity(ns, "TARGET", 1)
1197 .unwrap();
1198 for spelling in ["alias", "_xlfn._xlws.alias"] {
1199 let resolved = snapshot
1200 .function_semantic_identity(ns, spelling, 1)
1201 .unwrap();
1202 assert_eq!(resolved.namespace, ns);
1203 assert_eq!(resolved.canonical_name, "TARGET");
1204 assert_eq!(resolved.generation, direct.generation);
1205 assert!(Arc::ptr_eq(
1206 &snapshot.get_function(ns, spelling).unwrap(),
1207 &snapshot.get_function(ns, "TARGET").unwrap(),
1208 ));
1209 }
1210 }
1211
1212 #[test]
1213 fn guarded_request_change_check_does_not_relock_behind_queued_writer() {
1214 let ns = "__GUARDED_REQUEST_CHANGE__";
1215 register_function(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1216 let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1217 &GlobalRegistryFunctionProvider,
1218 [(ns.to_string(), "TARGET".to_string(), 1)],
1219 )
1220 .unwrap();
1221 register_function(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1222
1223 let guard = semantic_epoch_read_guard();
1224 let (queued_tx, queued_rx) = std::sync::mpsc::sync_channel(0);
1225 let writer = std::thread::spawn(move || {
1226 assert!(REGISTRY.try_write().is_err());
1227 queued_tx.send(()).unwrap();
1228 let mut state = REGISTRY
1229 .write()
1230 .unwrap_or_else(|poisoned| poisoned.into_inner());
1231 publish_semantic_change(&mut state, [(ns.to_string(), "QUEUED_WRITER".to_string())]);
1232 });
1233 queued_rx.recv().unwrap();
1234 std::thread::yield_now();
1235
1236 assert!(snapshot.semantic_changes_affect_requests_since_guarded(&guard, snapshot.epoch(),));
1237 drop(guard);
1238 writer.join().unwrap();
1239 }
1240
1241 #[test]
1242 fn workbook_planning_fallback_does_not_populate_prefix_alias_cache() {
1243 let ns = "__PLANNING_WORKBOOK_PREFIX__";
1244 register_builtin(planning_fn(ns, "TARGET", &["ALIAS"], FnCaps::empty()));
1245 let prefixed_key = (ns.to_string(), "_XLFN.ALIAS".to_string());
1246 assert!(!REGISTRY.read().unwrap().aliases.contains_key(&prefixed_key));
1247
1248 let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1249 &crate::test_workbook::TestWorkbook::default(),
1250 [(ns.to_string(), "_xlfn.alias".to_string(), 1)],
1251 )
1252 .unwrap();
1253
1254 assert!(
1255 snapshot
1256 .function_semantic_identity(ns, "_xlfn.alias", 1)
1257 .is_some()
1258 );
1259 assert!(!REGISTRY.read().unwrap().aliases.contains_key(&prefixed_key));
1260 }
1261
1262 #[test]
1263 fn planning_snapshot_is_immutable_across_replacement() {
1264 let ns = "__PLANNING_IMMUTABLE__";
1265 register_builtin(planning_fn(ns, "TARGET", &["OLD_ALIAS"], FnCaps::empty()));
1266 let requests = [
1267 (ns.to_string(), "TARGET".to_string(), 1),
1268 (ns.to_string(), "OLD_ALIAS".to_string(), 1),
1269 ];
1270 let snapshot = RegistryPlanningSnapshot::capture_with_hook(
1271 &GlobalRegistryFunctionProvider,
1272 &requests,
1273 10_000,
1274 |_| {},
1275 )
1276 .unwrap();
1277 let old_function = snapshot.get_function(ns, "TARGET").unwrap();
1278 let old_identity = snapshot
1279 .function_semantic_identity(ns, "TARGET", 1)
1280 .unwrap();
1281
1282 register_function(planning_fn(ns, "TARGET", &[], FnCaps::MAY_SPILL));
1283 let current = resolve_for_arity(ns, "TARGET", 1).unwrap();
1284 assert!(current.semantics.generation > old_identity.generation);
1285 assert!(!Arc::ptr_eq(&old_function, ¤t.function));
1286 assert_eq!(
1287 snapshot
1288 .function_semantic_identity(ns, "TARGET", 1)
1289 .unwrap(),
1290 old_identity
1291 );
1292 assert!(Arc::ptr_eq(
1293 &old_function,
1294 &snapshot.get_function(ns, "TARGET").unwrap(),
1295 ));
1296 assert!(get(ns, "OLD_ALIAS").is_none());
1297 assert_eq!(
1298 snapshot
1299 .function_semantic_identity(ns, "OLD_ALIAS", 1)
1300 .unwrap(),
1301 old_identity
1302 );
1303 assert!(Arc::ptr_eq(
1304 &old_function,
1305 &snapshot.get_function(ns, "OLD_ALIAS").unwrap(),
1306 ));
1307 }
1308
1309 #[test]
1310 fn planning_snapshot_requires_explicit_side_effect_free_provider_opt_in() {
1311 struct RuntimeOnlyProvider(Arc<dyn Function>);
1312 impl FunctionProvider for RuntimeOnlyProvider {
1313 fn get_function(&self, _ns: &str, _name: &str) -> Option<Arc<dyn Function>> {
1314 Some(Arc::clone(&self.0))
1315 }
1316 }
1317
1318 let ns = "__PLANNING_FAIL_CLOSED__";
1319 register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1320 let result = RegistryPlanningSnapshot::capture_for_requests(
1321 &RuntimeOnlyProvider(planning_fn(ns, "TARGET", &[], FnCaps::empty())),
1322 [(ns.to_string(), "TARGET".to_string(), 1)],
1323 );
1324 assert_eq!(
1325 result.err(),
1326 Some(PlanningSnapshotError::ProviderRevisionUnavailable)
1327 );
1328 }
1329
1330 #[test]
1331 fn planning_snapshot_preserves_runtime_override_without_global_semantics() {
1332 struct OverrideProvider(Arc<dyn Function>);
1333 impl FunctionProvider for OverrideProvider {
1334 fn planning_semantic_revision(&self) -> Option<u64> {
1335 Some(0)
1336 }
1337 fn get_function(&self, _ns: &str, _name: &str) -> Option<Arc<dyn Function>> {
1338 Some(Arc::clone(&self.0))
1339 }
1340 fn get_function_for_planning(
1341 &self,
1342 _ns: &str,
1343 _name: &str,
1344 ) -> Option<Arc<dyn Function>> {
1345 Some(Arc::clone(&self.0))
1346 }
1347 }
1348
1349 let ns = "__PLANNING_OVERRIDE__";
1350 register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1351 let global = get(ns, "TARGET").unwrap();
1352 let runtime = planning_fn(ns, "TARGET", &[], FnCaps::MAY_SPILL);
1353 let provider = OverrideProvider(Arc::clone(&runtime));
1354 let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1355 &provider,
1356 [(ns.to_string(), "TARGET".to_string(), 1)],
1357 )
1358 .unwrap();
1359
1360 let captured = snapshot.get_function(ns, "TARGET").unwrap();
1361 assert!(Arc::ptr_eq(&captured, &runtime));
1362 assert!(!Arc::ptr_eq(&captured, &global));
1363 assert!(
1364 snapshot
1365 .function_semantic_identity(ns, "TARGET", 1)
1366 .is_none()
1367 );
1368 assert_eq!(snapshot.functions.len(), 1);
1369 assert_eq!(
1370 snapshot.function_capabilities(ns, "TARGET"),
1371 Some(FnCaps::MAY_SPILL)
1372 );
1373 assert_eq!(snapshot.capabilities.len(), 1);
1374 assert!(snapshot.identities.is_empty());
1375 assert!(snapshot.get_function(ns, "UNREQUESTED").is_none());
1376 }
1377
1378 #[test]
1379 fn planning_snapshot_retries_provider_revision_flip_to_runtime_override() {
1380 struct FlippingProvider {
1381 function: Arc<RwLock<Arc<dyn Function>>>,
1382 revision: Arc<AtomicU64>,
1383 }
1384 impl FunctionProvider for FlippingProvider {
1385 fn planning_semantic_revision(&self) -> Option<u64> {
1386 Some(self.revision.load(Ordering::Acquire))
1387 }
1388 fn get_function(&self, _ns: &str, _name: &str) -> Option<Arc<dyn Function>> {
1389 Some(Arc::clone(&self.function.read().unwrap()))
1390 }
1391 fn get_function_for_planning(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
1392 self.get_function(ns, name)
1393 }
1394 }
1395
1396 let ns = "__PLANNING_PROVIDER_FLIP__";
1397 register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1398 let global = get(ns, "TARGET").unwrap();
1399 let override_function = planning_fn(ns, "TARGET", &[], FnCaps::MAY_SPILL);
1400 let function = Arc::new(RwLock::new(global));
1401 let revision = Arc::new(AtomicU64::new(0));
1402 let provider = FlippingProvider {
1403 function: Arc::clone(&function),
1404 revision: Arc::clone(&revision),
1405 };
1406 let requests = [(ns.to_string(), "TARGET".to_string(), 1)];
1407 let snapshot =
1408 RegistryPlanningSnapshot::capture_with_hook(&provider, &requests, 2, |attempt| {
1409 if attempt == 0 {
1410 *function.write().unwrap() = Arc::clone(&override_function);
1411 revision.fetch_add(1, Ordering::AcqRel);
1412 }
1413 })
1414 .unwrap();
1415
1416 assert_eq!(snapshot.provider_revision(), Some(1));
1417 assert!(Arc::ptr_eq(
1418 &snapshot.get_function(ns, "TARGET").unwrap(),
1419 &override_function
1420 ));
1421 assert!(
1422 snapshot
1423 .function_semantic_identity(ns, "TARGET", 1)
1424 .is_none()
1425 );
1426 }
1427
1428 #[test]
1429 fn planning_snapshot_capture_tolerates_unrelated_concurrent_registrations() {
1430 let ns = "__PLANNING_UNRELATED_CHURN__";
1446 register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1447 let requests = [(ns.to_string(), "TARGET".to_string(), 1)];
1448
1449 let mut unrelated_registrations = 0usize;
1450 let snapshot = RegistryPlanningSnapshot::capture_with_hook(
1451 &GlobalRegistryFunctionProvider,
1452 &requests,
1453 2,
1454 |_| {
1455 register_function(planning_fn(
1459 "__PLANNING_UNRELATED_CHURN_OTHER__",
1460 "OTHER",
1461 &[],
1462 FnCaps::empty(),
1463 ));
1464 unrelated_registrations += 1;
1465 },
1466 )
1467 .expect("unrelated registrations must not invalidate a planning snapshot capture");
1468
1469 assert!(unrelated_registrations > 0, "hook must have registered");
1470 assert!(Arc::ptr_eq(
1471 &snapshot.get_function(ns, "TARGET").unwrap(),
1472 &get(ns, "TARGET").unwrap(),
1473 ));
1474 }
1475
1476 #[test]
1477 fn planning_snapshot_capture_retries_and_fails_deterministically() {
1478 let ns = "__PLANNING_RACE__";
1479 register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1480 let requests = [(ns.to_string(), "TARGET".to_string(), 1)];
1481 let before = semantic_epoch();
1482 let retried = RegistryPlanningSnapshot::capture_with_hook(
1483 &GlobalRegistryFunctionProvider,
1484 &requests,
1485 100,
1486 |attempt| {
1487 if attempt == 0 {
1488 register_function(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1489 }
1490 },
1491 )
1492 .unwrap();
1493 assert!(retried.epoch() > before);
1494
1495 let failed = RegistryPlanningSnapshot::capture_with_hook(
1496 &GlobalRegistryFunctionProvider,
1497 &requests,
1498 2,
1499 |_| register_function(planning_fn(ns, "TARGET", &[], FnCaps::empty())),
1500 );
1501 assert_eq!(
1502 failed.err(),
1503 Some(PlanningSnapshotError::RegistryChangedDuringCapture)
1504 );
1505 }
1506
1507 #[test]
1508 fn planning_snapshot_nested_function_authority_matches_global_registry() {
1509 let ns = "";
1510 register_builtin(planning_fn(ns, "__PLAN_OUTER__", &[], FnCaps::empty()));
1511 register_builtin(planning_fn(ns, "__PLAN_INNER__", &[], FnCaps::empty()));
1512 let requests = [
1513 (String::new(), "__PLAN_OUTER__".to_string(), 1),
1514 (String::new(), "_xlfn.__PLAN_INNER__".to_string(), 1),
1515 ];
1516 let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1517 &GlobalRegistryFunctionProvider,
1518 requests,
1519 )
1520 .unwrap();
1521 let ast =
1522 formualizer_parse::parser::parse("=__PLAN_OUTER__(_xlfn.__PLAN_INNER__(A1))").unwrap();
1523 let frozen = crate::formula_plane::template_canonical::canonicalize_template_with_provider(
1524 &ast,
1525 2,
1526 2,
1527 Some(&snapshot),
1528 );
1529 let global = crate::formula_plane::template_canonical::canonicalize_template_with_provider(
1530 &ast,
1531 2,
1532 2,
1533 Some(&GlobalRegistryFunctionProvider),
1534 );
1535 assert_eq!(frozen, global);
1536 assert!(frozen.labels.is_authority_supported());
1537 }
1538
1539 #[test]
1540 fn parallel_snapshot_capture_and_prefix_resolution_does_not_deadlock() {
1541 let ns = "__PLANNING_PARALLEL__";
1542 register_builtin(planning_fn(ns, "TARGET", &["ALIAS"], FnCaps::empty()));
1543 let (send, receive) = std::sync::mpsc::channel();
1544 std::thread::spawn(move || {
1545 let mut workers = Vec::new();
1546 for worker in 0..4 {
1547 workers.push(std::thread::spawn(move || {
1548 for iteration in 0..100 {
1549 if worker == 0 && iteration % 10 == 0 {
1550 register_function(planning_fn(
1551 ns,
1552 "TARGET",
1553 &["ALIAS"],
1554 FnCaps::empty(),
1555 ));
1556 }
1557 let _ = RegistryPlanningSnapshot::capture_for_requests(
1558 &GlobalRegistryFunctionProvider,
1559 [(ns.to_string(), "_xlfn.alias".to_string(), 1)],
1560 );
1561 let _ = get(ns, "_xlfn.alias");
1562 }
1563 }));
1564 }
1565 for worker in workers {
1566 worker.join().unwrap();
1567 }
1568 send.send(()).unwrap();
1569 });
1570 receive
1571 .recv_timeout(std::time::Duration::from_secs(10))
1572 .expect("parallel registry planning timed out (possible lock inversion)");
1573 }
1574
1575 #[test]
1576 fn resolves_prefixes_aliases_and_direct_registration() {
1577 let ns = "__REG_PREFIX__";
1578 register_function(Arc::new(TestFn {
1579 ns,
1580 name: "FILTER",
1581 aliases: &["LEGACY"],
1582 }));
1583 assert_eq!(get(ns, "_xlfn._xlws.legacy").unwrap().name(), "FILTER");
1584 register_function(Arc::new(TestFn {
1585 ns,
1586 name: "_XLFN.FILTER",
1587 aliases: &[],
1588 }));
1589 assert_eq!(get(ns, "_xlfn.filter").unwrap().name(), "_XLFN.FILTER");
1590 }
1591
1592 #[test]
1593 fn trusted_replacement_records_removed_owned_alias_spelling() {
1594 let namespace = "__REG_STALE_ALIAS__";
1595 register_builtin(Arc::new(TestFn {
1596 ns: namespace,
1597 name: "TARGET",
1598 aliases: &["STALE_OWNED_ALIAS"],
1599 }));
1600 let before = semantic_epoch();
1601 register_function(Arc::new(TestFn {
1602 ns: namespace,
1603 name: "TARGET",
1604 aliases: &["NEW_OWNED_ALIAS"],
1605 }));
1606 let changes = semantic_changes_since(before);
1607 assert!(
1608 changes
1609 .keys
1610 .contains(&(namespace.to_string(), "STALE_OWNED_ALIAS".to_string()))
1611 );
1612 assert!(
1613 changes
1614 .keys
1615 .contains(&(namespace.to_string(), "NEW_OWNED_ALIAS".to_string()))
1616 );
1617 }
1618
1619 #[test]
1620 fn alias_requests_keep_the_spelling_used_by_the_formula() {
1621 let ns = "__ALIAS_REQUEST_SPELLING__";
1622 register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1623 register_alias(ns, "FORMULA_ALIAS", ns, "TARGET");
1624 let request = (ns.to_string(), "FORMULA_ALIAS".to_string(), 1);
1625 let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1626 &GlobalRegistryFunctionProvider,
1627 [request.clone()],
1628 )
1629 .unwrap();
1630
1631 assert_eq!(snapshot.requests.as_ref(), &[request]);
1632 assert_eq!(
1633 snapshot
1634 .function_semantic_identity(ns, "FORMULA_ALIAS", 1)
1635 .unwrap()
1636 .canonical_name,
1637 "TARGET"
1638 );
1639 }
1640
1641 #[test]
1642 fn alias_mutations_affect_alias_requests_but_not_direct_target_requests() {
1643 let ns = "__ALIAS_CHANGE_SCOPE__";
1644 register_builtin(planning_fn(ns, "OLD_TARGET", &[], FnCaps::empty()));
1645 register_builtin(planning_fn(ns, "NEW_TARGET", &[], FnCaps::empty()));
1646
1647 let add_epoch = semantic_epoch();
1648 register_alias(ns, "ADDED_ALIAS", ns, "OLD_TARGET");
1649 assert!(semantic_changes_affect_requests_since(
1650 add_epoch,
1651 [(ns.to_string(), "ADDED_ALIAS".to_string(), 1)]
1652 ));
1653 assert!(!semantic_changes_affect_requests_since(
1654 add_epoch,
1655 [(ns.to_string(), "OLD_TARGET".to_string(), 1)]
1656 ));
1657
1658 register_alias(ns, "RETARGETED_ALIAS", ns, "OLD_TARGET");
1659 let retarget_epoch = semantic_epoch();
1660 register_alias(ns, "RETARGETED_ALIAS", ns, "NEW_TARGET");
1661 assert!(semantic_changes_affect_requests_since(
1662 retarget_epoch,
1663 [(ns.to_string(), "RETARGETED_ALIAS".to_string(), 1)]
1664 ));
1665 assert!(semantic_changes_affect_requests_since(
1666 retarget_epoch,
1667 [(ns.to_string(), "_xlfn.RETARGETED_ALIAS".to_string(), 1)]
1668 ));
1669 for target in ["OLD_TARGET", "NEW_TARGET"] {
1670 assert!(!semantic_changes_affect_requests_since(
1671 retarget_epoch,
1672 [(ns.to_string(), target.to_string(), 1)]
1673 ));
1674 }
1675
1676 register_alias(ns, "REMOVED_ALIAS", ns, "OLD_TARGET");
1677 let remove_epoch = semantic_epoch();
1678 {
1679 let mut state = REGISTRY
1680 .write()
1681 .unwrap_or_else(|poisoned| poisoned.into_inner());
1682 let alias_key = (ns.to_string(), "REMOVED_ALIAS".to_string());
1683 assert!(state.aliases.remove(&alias_key).is_some());
1684 publish_semantic_change(&mut state, [alias_key]);
1685 }
1686 assert!(semantic_changes_affect_requests_since(
1687 remove_epoch,
1688 [(ns.to_string(), "REMOVED_ALIAS".to_string(), 1)]
1689 ));
1690 assert!(!semantic_changes_affect_requests_since(
1691 remove_epoch,
1692 [(ns.to_string(), "OLD_TARGET".to_string(), 1)]
1693 ));
1694 }
1695
1696 #[test]
1697 fn request_change_check_stays_conservative_after_log_truncation() {
1698 let mut state = RegistryState::default();
1699 let before = state.semantic_epoch;
1700 for index in 0..=1_024 {
1701 publish_semantic_change(&mut state, [(String::new(), format!("UNRELATED_{index}"))]);
1702 }
1703
1704 assert!(semantic_changes_affect_requests_in_state(
1705 &state,
1706 before,
1707 [(String::new(), "TARGET".to_string(), 1)]
1708 ));
1709 }
1710
1711 #[test]
1712 fn replacement_advances_semantic_generation() {
1713 let ns = "__REG_GENERATION__";
1714 register_function(Arc::new(TestFn {
1715 ns,
1716 name: "F",
1717 aliases: &[],
1718 }));
1719 let first = resolve(ns, "F").unwrap().semantics.generation;
1720 let epoch = semantic_epoch();
1721 register_function(Arc::new(TestFn {
1722 ns,
1723 name: "F",
1724 aliases: &[],
1725 }));
1726 let second = resolve(ns, "F").unwrap().semantics.generation;
1727 assert!(second > first);
1728 let changes = semantic_changes_since(epoch);
1729 assert!(changes.epoch > epoch);
1730 assert!(changes.keys.contains(&(ns.to_string(), "F".to_string())));
1731 }
1732
1733 struct PanickingSchemaFn;
1734
1735 impl Function for PanickingSchemaFn {
1736 fn name(&self) -> &'static str {
1737 "PANICKING_SCHEMA"
1738 }
1739 fn semantic_contract(&self, _arity: usize) -> Option<FunctionSemanticContract> {
1740 Some(FunctionSemanticContract::trusted_builtin_default(None))
1741 }
1742 fn arg_schema(&self) -> &'static [crate::args::ArgSchema] {
1743 panic!("bad schema")
1744 }
1745 fn eval<'a, 'b, 'c>(
1746 &self,
1747 _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
1748 _ctx: &dyn crate::traits::FunctionContext<'b>,
1749 ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
1750 unreachable!()
1751 }
1752 }
1753
1754 #[test]
1755 fn schema_panic_as_sole_defect_is_non_panicking_and_fails_closed() {
1756 register_function(Arc::new(PanickingSchemaFn));
1757 let semantics = resolve("", "PANICKING_SCHEMA").unwrap().semantics;
1758 assert!(semantics.contract.is_none());
1759 assert!(
1760 semantics
1761 .issues
1762 .contains(&SemanticConformanceIssue::ArgumentSchemaPanicked)
1763 );
1764 assert_eq!(
1765 semantics.issues,
1766 vec![SemanticConformanceIssue::ArgumentSchemaPanicked]
1767 );
1768 assert!(!semantics.conforms());
1769 }
1770
1771 #[test]
1772 fn every_registered_builtin_has_a_conforming_semantic_contract() {
1773 crate::builtins::load_builtins();
1774 let builtins: Vec<_> = snapshot_semantics()
1775 .into_iter()
1776 .filter(|entry| entry.semantics.trusted_builtin)
1777 .collect();
1778 assert!(builtins.len() > 100);
1779 let rejected: Vec<_> = builtins
1780 .iter()
1781 .filter(|entry| !entry.semantics.conforms())
1782 .map(|entry| {
1783 (
1784 &entry.namespace,
1785 &entry.canonical_name,
1786 &entry.semantics.issues,
1787 )
1788 })
1789 .collect();
1790 assert!(rejected.is_empty(), "non-conforming builtins: {rejected:?}");
1791 }
1792
1793 #[test]
1794 fn semantic_contract_is_context_and_arity_aware() {
1795 crate::builtins::lookup::register_builtins();
1796 let row_without_arg = resolve_for_arity("", "ROW", 0).unwrap();
1797 let row_with_arg = resolve_for_arity("", "ROW", 1).unwrap();
1798 assert_eq!(
1799 row_without_arg.semantics.contract.unwrap().context,
1800 crate::function_contract::FunctionContextDependence::PlacementDependent
1801 );
1802 assert_eq!(
1803 row_with_arg.semantics.contract.unwrap().context,
1804 crate::function_contract::FunctionContextDependence::None
1805 );
1806 }
1807
1808 #[test]
1809 fn semantic_identity_encodes_effective_by_reference_roles_for_call_arity() {
1810 crate::builtins::load_builtins();
1811 let provider = GlobalRegistryFunctionProvider;
1812 let sum = resolve_semantic_identity(&provider, "", "SUM", 3).unwrap();
1813 assert_eq!(sum.argument_by_ref, vec![false, false, false]);
1814
1815 let row = resolve_semantic_identity(&provider, "", "ROW", 1).unwrap();
1816 assert_eq!(row.argument_by_ref, vec![true]);
1817 }
1818
1819 struct ExplicitSafeCustomFn;
1820
1821 impl Function for ExplicitSafeCustomFn {
1822 fn name(&self) -> &'static str {
1823 "EXPLICIT_SAFE_CUSTOM"
1824 }
1825 fn semantic_contract(&self, _arity: usize) -> Option<FunctionSemanticContract> {
1826 Some(FunctionSemanticContract::trusted_builtin_default(None))
1827 }
1828 fn eval<'a, 'b, 'c>(
1829 &self,
1830 _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
1831 _ctx: &dyn crate::traits::FunctionContext<'b>,
1832 ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
1833 unreachable!()
1834 }
1835 }
1836
1837 struct MismatchedPrecisionCustomFn;
1838
1839 impl Function for MismatchedPrecisionCustomFn {
1840 fn name(&self) -> &'static str {
1841 "MISMATCHED_PRECISION_CUSTOM"
1842 }
1843 fn dependency_contract(
1844 &self,
1845 arity: usize,
1846 ) -> Option<crate::function_contract::FunctionDependencyContract> {
1847 crate::function_contract::FunctionDependencyContract::static_scalar_all_args(arity)
1848 }
1849 fn semantic_contract(&self, _arity: usize) -> Option<FunctionSemanticContract> {
1850 Some(FunctionSemanticContract::trusted_builtin_default(None))
1851 }
1852 fn min_args(&self) -> usize {
1853 1
1854 }
1855 fn arg_schema(&self) -> &'static [crate::args::ArgSchema] {
1856 static SCHEMA: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
1857 std::sync::LazyLock::new(|| vec![crate::args::ArgSchema::any()]);
1858 &SCHEMA
1859 }
1860 fn eval<'a, 'b, 'c>(
1861 &self,
1862 _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
1863 _ctx: &dyn crate::traits::FunctionContext<'b>,
1864 ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
1865 unreachable!()
1866 }
1867 }
1868
1869 #[test]
1870 fn explicit_precision_must_equal_dependency_contract() {
1871 register_function(Arc::new(MismatchedPrecisionCustomFn));
1872 let semantics = resolve_for_arity("", "MISMATCHED_PRECISION_CUSTOM", 1)
1873 .unwrap()
1874 .semantics;
1875 assert!(semantics.contract.is_none());
1876 assert!(
1877 semantics
1878 .issues
1879 .contains(&SemanticConformanceIssue::PrecisionContractMismatch)
1880 );
1881 }
1882
1883 #[test]
1884 fn explicit_custom_semantics_can_conform_without_becoming_trusted() {
1885 register_function(Arc::new(ExplicitSafeCustomFn));
1886 let semantics = resolve_for_arity("", "EXPLICIT_SAFE_CUSTOM", 0)
1887 .unwrap()
1888 .semantics;
1889 assert!(!semantics.trusted_builtin);
1890 assert!(semantics.conforms());
1891 }
1892
1893 #[test]
1894 fn concurrent_replacements_leave_only_final_owned_alias() {
1895 let ns = "__REG_CONCURRENT__";
1896 register_function(Arc::new(TestFn {
1897 ns,
1898 name: "TARGET",
1899 aliases: &["INITIAL"],
1900 }));
1901 let mut workers = Vec::new();
1902 for alias in ["A", "B", "C", "D"] {
1903 workers.push(std::thread::spawn(move || {
1904 for _ in 0..100 {
1905 let aliases: &'static [&'static str] = Box::leak(Box::new([alias]));
1906 register_function(Arc::new(TestFn {
1907 ns,
1908 name: "TARGET",
1909 aliases,
1910 }));
1911 assert_eq!(get(ns, "TARGET").unwrap().name(), "TARGET");
1912 }
1913 }));
1914 }
1915 for worker in workers {
1916 worker.join().unwrap();
1917 }
1918 register_function(Arc::new(TestFn {
1919 ns,
1920 name: "TARGET",
1921 aliases: &["FINAL"],
1922 }));
1923 for stale in ["INITIAL", "A", "B", "C", "D"] {
1924 assert!(get(ns, stale).is_none());
1925 }
1926 assert!(get(ns, "FINAL").is_some());
1927 }
1928
1929 #[test]
1930 fn independent_exception_inventory_matches_builtin_caps_and_context() {
1931 crate::builtins::load_builtins();
1932 for name in [
1933 "RAND",
1934 "RANDBETWEEN",
1935 "RANDARRAY",
1936 "TODAY",
1937 "NOW",
1938 "OFFSET",
1939 "INDIRECT",
1940 ] {
1941 assert!(
1942 get("", name).unwrap().caps().contains(FnCaps::VOLATILE),
1943 "{name}"
1944 );
1945 }
1946 for name in ["OFFSET", "INDIRECT"] {
1947 assert!(
1948 get("", name)
1949 .unwrap()
1950 .caps()
1951 .contains(FnCaps::DYNAMIC_DEPENDENCY),
1952 "{name}"
1953 );
1954 }
1955 for name in ["INDEX", "OFFSET", "INDIRECT", "CHOOSE"] {
1956 assert!(
1957 get("", name)
1958 .unwrap()
1959 .caps()
1960 .contains(FnCaps::RETURNS_REFERENCE),
1961 "{name}"
1962 );
1963 }
1964 for name in ["LET", "LAMBDA"] {
1965 assert!(
1966 get("", name)
1967 .unwrap()
1968 .caps()
1969 .contains(FnCaps::LOCAL_ENVIRONMENT),
1970 "{name}"
1971 );
1972 }
1973 for name in [
1974 "IF",
1975 "IFERROR",
1976 "IFNA",
1977 "IFS",
1978 "SWITCH",
1979 "CHOOSE",
1980 "FILTER",
1981 "UNIQUE",
1982 "SEQUENCE",
1983 "TRANSPOSE",
1984 "TAKE",
1985 "DROP",
1986 "SORT",
1987 "SORTBY",
1988 "RANDARRAY",
1989 "HSTACK",
1990 "VSTACK",
1991 "TOCOL",
1992 "TOROW",
1993 "CHOOSECOLS",
1994 "CHOOSEROWS",
1995 "FREQUENCY",
1996 "LINEST",
1997 "TREND",
1998 "GROWTH",
1999 "LOGEST",
2000 "MODE.MULT",
2001 "TEXTSPLIT",
2002 ] {
2003 assert!(
2004 get("", name).unwrap().caps().contains(FnCaps::MAY_SPILL),
2005 "{name}"
2006 );
2007 }
2008 const SHORT_CIRCUIT: &[&str] = &[
2009 "IF", "IFERROR", "IFNA", "IFS", "SWITCH", "CHOOSE", "LET", "LAMBDA", "AND", "OR",
2010 ];
2011 let observed_short_circuit: std::collections::BTreeSet<_> = snapshot_registered()
2012 .into_iter()
2013 .filter(|(namespace, _, function)| {
2014 namespace.is_empty() && function.caps().contains(FnCaps::SHORT_CIRCUIT)
2015 })
2016 .map(|(_, name, _)| name)
2017 .collect();
2018 let expected_short_circuit: std::collections::BTreeSet<_> = SHORT_CIRCUIT
2019 .iter()
2020 .map(|name| (*name).to_string())
2021 .collect();
2022 assert_eq!(observed_short_circuit, expected_short_circuit);
2023 for name in SHORT_CIRCUIT {
2024 let contract = resolve_for_arity("", name, get("", name).unwrap().min_args())
2025 .unwrap()
2026 .semantics
2027 .contract
2028 .unwrap();
2029 assert_eq!(
2030 contract.evaluation,
2031 FunctionEvaluationSemantics::ShortCircuit,
2032 "{name}"
2033 );
2034 }
2035 assert_eq!(
2036 resolve_for_arity("", "CHOOSE", 2)
2037 .unwrap()
2038 .semantics
2039 .contract
2040 .unwrap()
2041 .result,
2042 FunctionResultSemantics::MayReturnReferenceAndSpill
2043 );
2044 for name in ["ROW", "COLUMN"] {
2045 let contract = resolve_for_arity("", name, 0)
2046 .unwrap()
2047 .semantics
2048 .contract
2049 .unwrap();
2050 assert_eq!(
2051 contract.context,
2052 crate::function_contract::FunctionContextDependence::PlacementDependent,
2053 "{name}"
2054 );
2055 assert_eq!(
2056 resolve_for_arity("", name, 1)
2057 .unwrap()
2058 .semantics
2059 .contract
2060 .unwrap()
2061 .context,
2062 crate::function_contract::FunctionContextDependence::None,
2063 "{name} with argument"
2064 );
2065 }
2066 for name in ["ISFORMULA", "FORMULATEXT", "SHEET", "SHEETS"] {
2067 let contract = resolve_for_arity("", name, get("", name).unwrap().min_args())
2068 .unwrap()
2069 .semantics
2070 .contract
2071 .unwrap();
2072 assert_eq!(
2073 contract.context,
2074 crate::function_contract::FunctionContextDependence::WorkbookMetadata,
2075 "{name}"
2076 );
2077 }
2078 }
2079
2080 struct NamePanicFn;
2081 impl Function for NamePanicFn {
2082 fn name(&self) -> &'static str {
2083 panic!("name")
2084 }
2085 fn eval<'a, 'b, 'c>(
2086 &self,
2087 _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
2088 _ctx: &dyn crate::traits::FunctionContext<'b>,
2089 ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
2090 unreachable!()
2091 }
2092 }
2093 struct NamespacePanicFn;
2094 impl Function for NamespacePanicFn {
2095 fn name(&self) -> &'static str {
2096 "NS_PANIC"
2097 }
2098 fn namespace(&self) -> &'static str {
2099 panic!("namespace")
2100 }
2101 fn eval<'a, 'b, 'c>(
2102 &self,
2103 _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
2104 _ctx: &dyn crate::traits::FunctionContext<'b>,
2105 ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
2106 unreachable!()
2107 }
2108 }
2109
2110 #[test]
2111 fn canonical_metadata_panics_decline_registration_without_unwinding() {
2112 assert_eq!(
2113 try_register_function(Arc::new(NamePanicFn)),
2114 Err(RegistrationError::NameMetadataPanicked)
2115 );
2116 assert_eq!(
2117 try_register_function(Arc::new(NamespacePanicFn)),
2118 Err(RegistrationError::NamespaceMetadataPanicked)
2119 );
2120 register_function(Arc::new(NamePanicFn));
2121 register_function(Arc::new(NamespacePanicFn));
2122 assert!(get("", "NS_PANIC").is_none());
2123 }
2124
2125 #[derive(Clone, Copy)]
2126 enum BadSchemaKind {
2127 TooLarge,
2128 Repeating,
2129 MinDisagreement,
2130 RequiredCount,
2131 TooManyRequired,
2132 RepeatWidth,
2133 }
2134 struct BadSchemaFn {
2135 name: &'static str,
2136 kind: BadSchemaKind,
2137 }
2138 impl Function for BadSchemaFn {
2139 fn name(&self) -> &'static str {
2140 self.name
2141 }
2142 fn min_args(&self) -> usize {
2143 if matches!(
2144 self.kind,
2145 BadSchemaKind::MinDisagreement
2146 | BadSchemaKind::RequiredCount
2147 | BadSchemaKind::RepeatWidth
2148 ) {
2149 2
2150 } else {
2151 1
2152 }
2153 }
2154 fn variadic(&self) -> bool {
2155 matches!(self.kind, BadSchemaKind::RepeatWidth)
2156 }
2157 fn semantic_contract(&self, _arity: usize) -> Option<FunctionSemanticContract> {
2158 Some(FunctionSemanticContract::trusted_builtin_default(None))
2159 }
2160 fn arg_schema(&self) -> &'static [crate::args::ArgSchema] {
2161 static ONE: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
2162 std::sync::LazyLock::new(|| vec![crate::args::ArgSchema::any()]);
2163 static BAD_REPEAT: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
2164 std::sync::LazyLock::new(|| {
2165 let mut arg = crate::args::ArgSchema::any();
2166 arg.repeating = Some(0);
2167 vec![arg]
2168 });
2169 static REQUIRED_COUNT: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
2170 std::sync::LazyLock::new(|| {
2171 let mut optional = crate::args::ArgSchema::any();
2172 optional.required = false;
2173 vec![crate::args::ArgSchema::any(), optional]
2174 });
2175 static REPEAT_WIDTH: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
2176 std::sync::LazyLock::new(|| {
2177 let first = crate::args::ArgSchema::any();
2178 let mut second = crate::args::ArgSchema::any();
2179 second.repeating = Some(2);
2180 vec![first, second]
2181 });
2182 match self.kind {
2183 BadSchemaKind::TooLarge => &ONE,
2184 BadSchemaKind::Repeating => &BAD_REPEAT,
2185 BadSchemaKind::MinDisagreement => &[],
2186 BadSchemaKind::RequiredCount => &REQUIRED_COUNT,
2187 BadSchemaKind::TooManyRequired => &REPEAT_WIDTH,
2188 BadSchemaKind::RepeatWidth => &REPEAT_WIDTH,
2189 }
2190 }
2191 fn eval<'a, 'b, 'c>(
2192 &self,
2193 _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
2194 _ctx: &dyn crate::traits::FunctionContext<'b>,
2195 ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
2196 unreachable!()
2197 }
2198 }
2199
2200 #[test]
2201 fn malformed_arity_and_schema_contracts_fail_closed() {
2202 for (name, kind, arity) in [
2203 ("TOO_LARGE", BadSchemaKind::TooLarge, 2),
2204 ("BAD_REPEAT", BadSchemaKind::Repeating, 1),
2205 ("MIN_DISAGREEMENT", BadSchemaKind::MinDisagreement, 2),
2206 ("REQUIRED_COUNT", BadSchemaKind::RequiredCount, 2),
2207 ("TOO_MANY_REQUIRED", BadSchemaKind::TooManyRequired, 1),
2208 ("REPEAT_WIDTH", BadSchemaKind::RepeatWidth, 3),
2209 ] {
2210 register_function(Arc::new(BadSchemaFn { name, kind }));
2211 let semantics = resolve_for_arity("", name, arity).unwrap().semantics;
2212 assert!(semantics.contract.is_none(), "{name}");
2213 assert!(
2214 semantics
2215 .issues
2216 .contains(&SemanticConformanceIssue::AritySchemaMismatch),
2217 "{name}: {:?}",
2218 semantics.issues
2219 );
2220 }
2221 }
2222
2223 #[test]
2224 fn valid_optional_and_width_n_repeating_schemas_conform() {
2225 let required = crate::args::ArgSchema::any();
2226 let mut optional = crate::args::ArgSchema::any();
2227 optional.required = false;
2228 assert!(schema_allows_arity(
2229 &[required.clone(), optional],
2230 1,
2231 false,
2232 2,
2233 true
2234 ));
2235
2236 let mut repeat_end = crate::args::ArgSchema::any();
2237 repeat_end.repeating = Some(2);
2238 let repeating = [required, repeat_end];
2239 assert!(schema_allows_arity(&repeating, 2, true, 4, true));
2240 assert!(!schema_allows_arity(&repeating, 2, true, 3, true));
2241 }
2242
2243 #[test]
2244 fn replacement_readers_observe_generation_and_epoch_atomically() {
2245 let ns = "__REG_SNAPSHOT_RACE__";
2246 register_builtin(Arc::new(TestFn {
2247 ns,
2248 name: "TARGET",
2249 aliases: &[],
2250 }));
2251 let (initial_epoch, initial) = resolve_with_epoch(ns, "TARGET").unwrap();
2252 let initial_generation = initial.semantics.generation;
2253 let barrier = Arc::new(std::sync::Barrier::new(5));
2254 let mut readers = Vec::new();
2255 for _ in 0..4 {
2256 let barrier = Arc::clone(&barrier);
2257 readers.push(std::thread::spawn(move || {
2258 barrier.wait();
2259 for _ in 0..1_000 {
2260 let (epoch, resolved) = resolve_with_epoch(ns, "TARGET").unwrap();
2261 if resolved.semantics.generation != initial_generation {
2262 assert!(epoch > initial_epoch);
2263 }
2264 }
2265 }));
2266 }
2267 barrier.wait();
2268 register_function(Arc::new(TestFn {
2269 ns,
2270 name: "TARGET",
2271 aliases: &[],
2272 }));
2273 for reader in readers {
2274 reader.join().unwrap();
2275 }
2276 let (epoch, resolved) = resolve_with_epoch(ns, "TARGET").unwrap();
2277 assert!(epoch > initial_epoch);
2278 assert!(resolved.semantics.generation > initial_generation);
2279 }
2280}