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
574#[cfg(test)]
575thread_local! {
576 static RESOLUTION_WRITES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
577}
578
579fn resolve_entry(ns: &str, name: &str) -> Option<(RegistryKey, RegistryEntry)> {
580 #[cfg(test)]
581 RESOLUTION_WRITES.with(|c| c.set(c.get() + 1));
582 let ns = norm(ns);
583 let normalized_name = norm(name);
584 let key = (ns.clone(), normalized_name.clone());
585 let mut state = REGISTRY
586 .write()
587 .unwrap_or_else(|poisoned| poisoned.into_inner());
588 if let Some(entry) = resolve_registered(&state, &key) {
589 return Some(entry);
590 }
591 let mut candidate = normalized_name.as_str();
592 loop {
593 let mut stripped_any = false;
594 for prefix in EXCEL_PREFIXES {
595 if let Some(rest) = candidate.strip_prefix(prefix) {
596 candidate = rest;
597 stripped_any = true;
598 let stripped_key = (ns.clone(), candidate.to_string());
599 if let Some((canonical, entry)) = resolve_registered(&state, &stripped_key) {
600 state.aliases.insert(
601 key.clone(),
602 AliasEntry {
603 target: canonical.clone(),
604 owner: Some((canonical.clone(), entry.generation)),
605 },
606 );
607 return Some((canonical, entry));
608 }
609 break;
610 }
611 }
612 if !stripped_any {
613 break;
614 }
615 }
616 None
617}
618
619fn resolve_key_read_only(
620 state: &RegistryState,
621 key: &RegistryKey,
622) -> Option<(RegistryKey, RegistryEntry)> {
623 if let Some(entry) = resolve_registered(state, key) {
624 return Some(entry);
625 }
626 let mut candidate = key.1.as_str();
627 loop {
628 let rest = EXCEL_PREFIXES
629 .iter()
630 .find_map(|prefix| candidate.strip_prefix(prefix))?;
631 candidate = rest;
632 let stripped = (key.0.clone(), candidate.to_string());
633 if let Some(entry) = resolve_registered(state, &stripped) {
634 return Some(entry);
635 }
636 }
637}
638
639fn resolve_entry_read_only(ns: &str, name: &str) -> Option<(RegistryKey, RegistryEntry)> {
640 let state = REGISTRY
641 .read()
642 .unwrap_or_else(|poisoned| poisoned.into_inner());
643 resolve_key_read_only(&state, &(norm(ns), norm(name)))
644}
645
646pub fn get(ns: &str, name: &str) -> Option<Arc<dyn Function>> {
647 let key = (norm(ns), norm(name));
648 {
649 let state = REGISTRY
650 .read()
651 .unwrap_or_else(|poisoned| poisoned.into_inner());
652 let entry = state.registrations.get(&key).or_else(|| {
653 let alias = state.aliases.get(&key)?;
654 state.registrations.get(&alias.target)
655 });
656 if let Some(entry) = entry {
657 return Some(Arc::clone(&entry.function));
658 }
659 if !EXCEL_PREFIXES
660 .iter()
661 .any(|prefix| key.1.starts_with(prefix))
662 {
663 return None;
664 }
665 }
666 resolve_entry(&key.0, &key.1).map(|(_, entry)| entry.function)
669}
670
671#[doc(hidden)]
674pub fn get_for_planning(ns: &str, name: &str) -> Option<Arc<dyn Function>> {
675 resolve_entry_read_only(ns, name).map(|(_, entry)| entry.function)
676}
677
678pub(crate) struct GlobalRegistryFunctionProvider;
679
680impl crate::traits::FunctionProvider for GlobalRegistryFunctionProvider {
681 fn planning_semantic_revision(&self) -> Option<u64> {
682 Some(0)
683 }
684
685 fn get_function(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
686 get(ns, name)
687 }
688
689 fn get_function_for_planning(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
690 get_for_planning(ns, name)
691 }
692}
693
694#[derive(Clone)]
695struct PlanningRegistration {
696 canonical: RegistryKey,
697 function: Arc<dyn Function>,
698 generation: u64,
699 trusted_builtin: bool,
700}
701
702#[derive(Clone)]
707pub(crate) struct RegistryPlanningSnapshot {
708 epoch: u64,
709 provider_revision: Option<u64>,
710 requests: Arc<Vec<(String, String, usize)>>,
711 functions: Arc<HashMap<RegistryKey, Arc<dyn Function>>>,
712 capabilities: Arc<HashMap<RegistryKey, FnCaps>>,
713 identities: Arc<HashMap<(String, String, usize), FunctionSemanticIdentity>>,
714}
715
716#[derive(Clone, Copy, Debug, PartialEq, Eq)]
717pub(crate) enum PlanningSnapshotError {
718 RegistryChangedDuringCapture,
719 ProviderRevisionUnavailable,
720}
721
722impl RegistryPlanningSnapshot {
723 const CAPTURE_ATTEMPTS: usize = 16;
724
725 pub(crate) fn capture_for_requests(
726 runtime_provider: &dyn crate::traits::FunctionProvider,
727 requests: impl IntoIterator<Item = (String, String, usize)>,
728 ) -> Result<Self, PlanningSnapshotError> {
729 crate::builtins::load_builtins();
730 let mut requests: Vec<_> = requests.into_iter().collect();
731 requests.sort();
732 requests.dedup();
733 Self::capture_with_hook(runtime_provider, &requests, Self::CAPTURE_ATTEMPTS, |_| {})
734 }
735
736 fn capture_with_hook(
737 runtime_provider: &dyn crate::traits::FunctionProvider,
738 requests: &[(String, String, usize)],
739 attempts: usize,
740 mut after_registry_copy: impl FnMut(usize),
741 ) -> Result<Self, PlanningSnapshotError> {
742 for attempt in 0..attempts {
743 let provider_revision = if requests.is_empty() {
744 None
745 } else {
746 Some(
747 runtime_provider
748 .planning_semantic_revision()
749 .ok_or(PlanningSnapshotError::ProviderRevisionUnavailable)?,
750 )
751 };
752 let start_epoch = REGISTRY
753 .read()
754 .unwrap_or_else(|poisoned| poisoned.into_inner())
755 .semantic_epoch;
756 let runtime_functions: HashMap<_, _> = requests
757 .iter()
758 .filter_map(|(namespace, name, _)| {
759 runtime_provider
760 .get_function_for_planning(namespace, name)
761 .map(|function| ((norm(namespace), norm(name)), function))
762 })
763 .collect();
764 let (epoch, registrations) = {
765 let state = REGISTRY
766 .read()
767 .unwrap_or_else(|poisoned| poisoned.into_inner());
768 let registrations = requests
769 .iter()
770 .filter_map(|(namespace, name, arity)| {
771 let request_key = (norm(namespace), norm(name));
772 resolve_key_read_only(&state, &request_key).map(|(canonical, entry)| {
773 (
774 (request_key.0, request_key.1, *arity),
775 PlanningRegistration {
776 canonical,
777 function: entry.function,
778 generation: entry.generation,
779 trusted_builtin: entry.trusted_builtin,
780 },
781 )
782 })
783 })
784 .collect::<HashMap<_, _>>();
785 (state.semantic_epoch, registrations)
786 };
787
788 after_registry_copy(attempt);
789
790 let mut capabilities = HashMap::new();
791 let mut identities = HashMap::new();
792 for (namespace, name, arity) in requests {
793 let request_key = (norm(namespace), norm(name));
794 let Some(runtime) = runtime_functions.get(&request_key) else {
795 continue;
796 };
797 let Some(registration) =
798 registrations.get(&(request_key.0.clone(), request_key.1.clone(), *arity))
799 else {
800 continue;
801 };
802 if !Arc::ptr_eq(runtime, ®istration.function) {
803 continue;
804 }
805 let (semantics, identity_metadata) = inspect_semantics_with_identity_metadata(
806 ®istration.function,
807 registration.trusted_builtin,
808 registration.generation,
809 *arity,
810 );
811 let Some(contract) = semantics.contract else {
812 continue;
813 };
814 let Some((caps, argument_by_ref)) = identity_metadata else {
815 continue;
816 };
817 capabilities.insert(request_key.clone(), caps);
818 identities.insert(
819 (request_key.0, request_key.1, *arity),
820 FunctionSemanticIdentity {
821 namespace: registration.canonical.0.clone(),
822 canonical_name: registration.canonical.1.clone(),
823 generation: registration.generation,
824 caps,
825 contract,
826 argument_by_ref,
827 },
828 );
829 }
830
831 for (key, function) in &runtime_functions {
832 if !capabilities.contains_key(key)
833 && let Ok(caps) = catch_unwind(AssertUnwindSafe(|| function.caps()))
834 {
835 capabilities.insert(key.clone(), caps);
836 }
837 }
838
839 let provider_unchanged = provider_revision.is_none_or(|revision| {
840 runtime_provider.planning_semantic_revision() == Some(revision)
841 });
842 let requests_unchanged = {
859 let state = REGISTRY
860 .read()
861 .unwrap_or_else(|poisoned| poisoned.into_inner());
862 !semantic_changes_affect_requests_in_state(
863 &state,
864 start_epoch,
865 requests.iter().cloned(),
866 )
867 };
868 let unchanged = requests_unchanged && provider_unchanged;
869 if unchanged {
870 return Ok(Self {
871 epoch,
872 provider_revision,
873 requests: Arc::new(requests.to_vec()),
874 functions: Arc::new(runtime_functions),
875 capabilities: Arc::new(capabilities),
876 identities: Arc::new(identities),
877 });
878 }
879 }
880 Err(PlanningSnapshotError::RegistryChangedDuringCapture)
881 }
882
883 pub(crate) fn epoch(&self) -> u64 {
884 self.epoch
885 }
886
887 pub(crate) fn provider_revision(&self) -> Option<u64> {
888 self.provider_revision
889 }
890
891 pub(crate) fn semantic_changes_affect_requests_since(&self, epoch: u64) -> bool {
892 semantic_changes_affect_requests_since(epoch, self.requests.iter().cloned())
893 }
894
895 pub(crate) fn semantic_changes_affect_requests_since_guarded(
896 &self,
897 guard: &SemanticEpochReadGuard,
898 epoch: u64,
899 ) -> bool {
900 guard.semantic_changes_affect_requests_since(epoch, self.requests.iter().cloned())
901 }
902}
903
904pub(crate) fn semantic_changes_affect_requests_since(
905 epoch: u64,
906 requests: impl IntoIterator<Item = (String, String, usize)>,
907) -> bool {
908 let state = REGISTRY
909 .read()
910 .unwrap_or_else(|poisoned| poisoned.into_inner());
911 semantic_changes_affect_requests_in_state(&state, epoch, requests)
912}
913
914fn semantic_changes_affect_requests_in_state(
915 state: &RegistryState,
916 epoch: u64,
917 requests: impl IntoIterator<Item = (String, String, usize)>,
918) -> bool {
919 let changes = semantic_changes_since_in_state(state, epoch);
920 if changes.epoch == epoch {
921 return false;
922 }
923 if !changes.complete {
924 return true;
925 }
926 let requests = requests
927 .into_iter()
928 .flat_map(|(namespace, name, _)| {
929 let namespace = norm(namespace);
930 let normalized = norm(name);
931 let mut spellings = vec![(namespace.clone(), normalized.clone())];
932 let mut stripped = normalized.as_str();
933 while let Some(rest) = EXCEL_PREFIXES
934 .iter()
935 .find_map(|prefix| stripped.strip_prefix(prefix))
936 {
937 stripped = rest;
938 spellings.push((namespace.clone(), stripped.to_string()));
939 }
940 spellings
941 })
942 .collect::<std::collections::BTreeSet<_>>();
943 changes.keys.into_iter().any(|key| requests.contains(&key))
944}
945
946impl crate::traits::FunctionProvider for RegistryPlanningSnapshot {
947 fn planning_semantic_revision(&self) -> Option<u64> {
948 Some(self.provider_revision.unwrap_or(0))
949 }
950
951 fn get_function(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
952 self.functions.get(&(norm(ns), norm(name))).cloned()
953 }
954
955 fn get_function_for_planning(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
956 self.get_function(ns, name)
957 }
958
959 fn function_capabilities(&self, ns: &str, name: &str) -> Option<FnCaps> {
960 self.capabilities.get(&(norm(ns), norm(name))).copied()
961 }
962
963 fn function_semantic_identity(
964 &self,
965 ns: &str,
966 name: &str,
967 arity: usize,
968 ) -> Option<FunctionSemanticIdentity> {
969 self.identities.get(&(norm(ns), norm(name), arity)).cloned()
970 }
971}
972
973pub fn resolve(ns: &str, name: &str) -> Option<ResolvedFunction> {
974 resolve_entry(ns, name).map(to_resolved)
975}
976
977pub fn resolve_with_epoch(ns: &str, name: &str) -> Option<(u64, ResolvedFunction)> {
978 let key = (norm(ns), norm(name));
979 let state = REGISTRY
980 .read()
981 .unwrap_or_else(|poisoned| poisoned.into_inner());
982 resolve_registered(&state, &key).map(|entry| (state.semantic_epoch, to_resolved(entry)))
983}
984pub fn resolve_for_arity(ns: &str, name: &str, arity: usize) -> Option<ResolvedFunction> {
985 resolve_entry(ns, name).map(|((namespace, canonical_name), entry)| {
986 let semantics = {
987 let cached = entry
988 .semantics_by_arity
989 .read()
990 .unwrap_or_else(|poisoned| poisoned.into_inner())
991 .get(&arity)
992 .cloned();
993 cached.unwrap_or_else(|| {
994 let inspected = inspect_semantics(
995 &entry.function,
996 entry.trusted_builtin,
997 entry.generation,
998 arity,
999 );
1000 entry
1001 .semantics_by_arity
1002 .write()
1003 .unwrap_or_else(|poisoned| poisoned.into_inner())
1004 .entry(arity)
1005 .or_insert_with(|| inspected.clone())
1006 .clone()
1007 })
1008 };
1009 ResolvedFunction {
1010 semantics,
1011 namespace,
1012 canonical_name,
1013 function: entry.function,
1014 }
1015 })
1016}
1017
1018pub(crate) fn resolve_semantic_identity<P: crate::traits::FunctionProvider + ?Sized>(
1019 provider: &P,
1020 ns: &str,
1021 name: &str,
1022 arity: usize,
1023) -> Option<FunctionSemanticIdentity> {
1024 let runtime = provider.get_function(ns, name)?;
1025 let resolved = resolve_for_arity(ns, name, arity)?;
1026 if !Arc::ptr_eq(&runtime, &resolved.function) {
1027 return None;
1028 }
1029 let contract = resolved.semantics.contract?;
1030 let argument_by_ref = catch_unwind(AssertUnwindSafe(|| {
1031 let schema = runtime.arg_schema();
1032 let repeating = schema.iter().find(|argument| argument.repeating.is_some());
1033 (0..arity)
1034 .map(|index| {
1035 schema
1036 .get(index)
1037 .or(repeating)
1038 .is_some_and(|argument| argument.by_ref)
1039 })
1040 .collect()
1041 }))
1042 .ok()?;
1043 Some(FunctionSemanticIdentity {
1044 namespace: resolved.namespace,
1045 canonical_name: resolved.canonical_name,
1046 generation: resolved.semantics.generation,
1047 caps: runtime.caps(),
1048 contract,
1049 argument_by_ref,
1050 })
1051}
1052
1053fn to_resolved(
1054 ((namespace, canonical_name), entry): (RegistryKey, RegistryEntry),
1055) -> ResolvedFunction {
1056 ResolvedFunction {
1057 namespace,
1058 canonical_name,
1059 function: entry.function,
1060 semantics: entry.semantics,
1061 }
1062}
1063
1064pub fn register_alias(ns: &str, alias: &str, target_ns: &str, target_name: &str) {
1065 let mut state = REGISTRY
1066 .write()
1067 .unwrap_or_else(|poisoned| poisoned.into_inner());
1068 let alias_key = (norm(ns), norm(alias));
1069 let target = (norm(target_ns), norm(target_name));
1070 let old_target = state
1071 .aliases
1072 .get(&alias_key)
1073 .map(|entry| entry.target.clone());
1074 if old_target.as_ref() == Some(&target) {
1075 return;
1076 }
1077 state.aliases.insert(
1078 alias_key.clone(),
1079 AliasEntry {
1080 target: target.clone(),
1081 owner: None,
1082 },
1083 );
1084 publish_semantic_change(&mut state, [alias_key]);
1087}
1088
1089pub fn snapshot_registered() -> Vec<(String, String, Arc<dyn Function>)> {
1090 let state = REGISTRY
1091 .read()
1092 .unwrap_or_else(|poisoned| poisoned.into_inner());
1093 state
1094 .registrations
1095 .iter()
1096 .map(|((ns, name), entry)| (ns.clone(), name.clone(), Arc::clone(&entry.function)))
1097 .collect()
1098}
1099pub fn snapshot_semantics() -> Vec<ResolvedFunction> {
1100 let state = REGISTRY
1101 .read()
1102 .unwrap_or_else(|poisoned| poisoned.into_inner());
1103 state
1104 .registrations
1105 .iter()
1106 .map(|((namespace, canonical_name), entry)| ResolvedFunction {
1107 namespace: namespace.clone(),
1108 canonical_name: canonical_name.clone(),
1109 function: Arc::clone(&entry.function),
1110 semantics: entry.semantics.clone(),
1111 })
1112 .collect()
1113}
1114
1115#[cfg(test)]
1116mod tests {
1117 use super::*;
1118 use crate::traits::FunctionProvider;
1119
1120 struct TestFn {
1121 ns: &'static str,
1122 name: &'static str,
1123 aliases: &'static [&'static str],
1124 }
1125
1126 impl Function for TestFn {
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 eval<'a, 'b, 'c>(
1137 &self,
1138 _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
1139 _ctx: &dyn crate::traits::FunctionContext<'b>,
1140 ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
1141 Ok(crate::traits::CalcValue::Scalar(
1142 formualizer_common::LiteralValue::Number(1.0),
1143 ))
1144 }
1145 }
1146
1147 struct PlanningFn {
1148 ns: &'static str,
1149 name: &'static str,
1150 aliases: &'static [&'static str],
1151 caps: FnCaps,
1152 }
1153
1154 impl Function for PlanningFn {
1155 fn name(&self) -> &'static str {
1156 self.name
1157 }
1158 fn namespace(&self) -> &'static str {
1159 self.ns
1160 }
1161 fn aliases(&self) -> &'static [&'static str] {
1162 self.aliases
1163 }
1164 fn caps(&self) -> FnCaps {
1165 self.caps
1166 }
1167 fn min_args(&self) -> usize {
1168 1
1169 }
1170 fn variadic(&self) -> bool {
1171 true
1172 }
1173 fn arg_schema(&self) -> &'static [crate::args::ArgSchema] {
1174 static SCHEMA: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
1175 std::sync::LazyLock::new(|| {
1176 let mut argument = crate::args::ArgSchema::any();
1177 argument.repeating = Some(1);
1178 vec![argument]
1179 });
1180 &SCHEMA
1181 }
1182 fn eval<'a, 'b, 'c>(
1183 &self,
1184 _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
1185 _ctx: &dyn crate::traits::FunctionContext<'b>,
1186 ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
1187 unreachable!()
1188 }
1189 }
1190
1191 fn planning_fn(
1192 ns: &'static str,
1193 name: &'static str,
1194 aliases: &'static [&'static str],
1195 caps: FnCaps,
1196 ) -> Arc<dyn Function> {
1197 Arc::new(PlanningFn {
1198 ns,
1199 name,
1200 aliases,
1201 caps,
1202 })
1203 }
1204
1205 #[test]
1206 fn runtime_hits_only_clone_current_function_without_resolution_writes() {
1207 let ns = "__RUNTIME_READ_FAST_PATH__";
1208 let first = planning_fn(ns, "TARGET", &["ALIAS"], FnCaps::empty());
1209 register_function(first.clone());
1210 assert!(Arc::ptr_eq(&get(ns, "_xlfn._xlws.alias").unwrap(), &first));
1211 RESOLUTION_WRITES.with(|c| c.set(0));
1212 for name in ["target", "ALIAS", "_XLFN._XLWS.ALIAS"] {
1213 assert!(Arc::ptr_eq(&get(ns, name).unwrap(), &first));
1214 }
1215 assert!(get(ns, "MISSING").is_none());
1216 assert_eq!(RESOLUTION_WRITES.with(|c| c.get()), 0);
1217 let second = planning_fn(ns, "TARGET", &["ALIAS"], FnCaps::empty());
1218 register_function(second.clone());
1219 for name in ["target", "ALIAS", "_XLFN._XLWS.ALIAS"] {
1220 assert!(Arc::ptr_eq(&get(ns, name).unwrap(), &second));
1221 }
1222 std::thread::scope(|scope| {
1223 for _ in 0..8 {
1224 let second = &second;
1225 scope.spawn(move || {
1226 RESOLUTION_WRITES.with(|c| c.set(0));
1227 for _ in 0..1000 {
1228 assert!(Arc::ptr_eq(&get(ns, "TARGET").unwrap(), second));
1229 assert!(Arc::ptr_eq(&get(ns, "_XLFN._XLWS.ALIAS").unwrap(), second));
1230 }
1231 assert_eq!(RESOLUTION_WRITES.with(|c| c.get()), 0);
1232 });
1233 }
1234 });
1235 }
1236
1237 #[test]
1238 fn planning_snapshot_resolves_direct_alias_namespace_and_prefix_without_cache_mutation() {
1239 let ns = "__PLANNING_PARITY__";
1240 register_builtin(planning_fn(ns, "TARGET", &["ALIAS"], FnCaps::empty()));
1241 let requests = [
1242 (ns.to_string(), "TARGET".to_string(), 1),
1243 (ns.to_string(), "alias".to_string(), 1),
1244 (ns.to_string(), "_xlfn._xlws.alias".to_string(), 1),
1245 ];
1246 let prefixed_key = (ns.to_string(), "_XLFN._XLWS.ALIAS".to_string());
1247 assert!(!REGISTRY.read().unwrap().aliases.contains_key(&prefixed_key));
1248 let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1249 &GlobalRegistryFunctionProvider,
1250 requests,
1251 )
1252 .unwrap();
1253 assert!(!REGISTRY.read().unwrap().aliases.contains_key(&prefixed_key));
1254
1255 let direct = snapshot
1256 .function_semantic_identity(ns, "TARGET", 1)
1257 .unwrap();
1258 for spelling in ["alias", "_xlfn._xlws.alias"] {
1259 let resolved = snapshot
1260 .function_semantic_identity(ns, spelling, 1)
1261 .unwrap();
1262 assert_eq!(resolved.namespace, ns);
1263 assert_eq!(resolved.canonical_name, "TARGET");
1264 assert_eq!(resolved.generation, direct.generation);
1265 assert!(Arc::ptr_eq(
1266 &snapshot.get_function(ns, spelling).unwrap(),
1267 &snapshot.get_function(ns, "TARGET").unwrap(),
1268 ));
1269 }
1270 }
1271
1272 #[test]
1273 fn guarded_request_change_check_does_not_relock_behind_queued_writer() {
1274 let ns = "__GUARDED_REQUEST_CHANGE__";
1275 register_function(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1276 let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1277 &GlobalRegistryFunctionProvider,
1278 [(ns.to_string(), "TARGET".to_string(), 1)],
1279 )
1280 .unwrap();
1281 register_function(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1282
1283 let guard = semantic_epoch_read_guard();
1284 let (queued_tx, queued_rx) = std::sync::mpsc::sync_channel(0);
1285 let writer = std::thread::spawn(move || {
1286 assert!(REGISTRY.try_write().is_err());
1287 queued_tx.send(()).unwrap();
1288 let mut state = REGISTRY
1289 .write()
1290 .unwrap_or_else(|poisoned| poisoned.into_inner());
1291 publish_semantic_change(&mut state, [(ns.to_string(), "QUEUED_WRITER".to_string())]);
1292 });
1293 queued_rx.recv().unwrap();
1294 std::thread::yield_now();
1295
1296 assert!(snapshot.semantic_changes_affect_requests_since_guarded(&guard, snapshot.epoch(),));
1297 drop(guard);
1298 writer.join().unwrap();
1299 }
1300
1301 #[test]
1302 fn workbook_planning_fallback_does_not_populate_prefix_alias_cache() {
1303 let ns = "__PLANNING_WORKBOOK_PREFIX__";
1304 register_builtin(planning_fn(ns, "TARGET", &["ALIAS"], FnCaps::empty()));
1305 let prefixed_key = (ns.to_string(), "_XLFN.ALIAS".to_string());
1306 assert!(!REGISTRY.read().unwrap().aliases.contains_key(&prefixed_key));
1307
1308 let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1309 &crate::test_workbook::TestWorkbook::default(),
1310 [(ns.to_string(), "_xlfn.alias".to_string(), 1)],
1311 )
1312 .unwrap();
1313
1314 assert!(
1315 snapshot
1316 .function_semantic_identity(ns, "_xlfn.alias", 1)
1317 .is_some()
1318 );
1319 assert!(!REGISTRY.read().unwrap().aliases.contains_key(&prefixed_key));
1320 }
1321
1322 #[test]
1323 fn planning_snapshot_is_immutable_across_replacement() {
1324 let ns = "__PLANNING_IMMUTABLE__";
1325 register_builtin(planning_fn(ns, "TARGET", &["OLD_ALIAS"], FnCaps::empty()));
1326 let requests = [
1327 (ns.to_string(), "TARGET".to_string(), 1),
1328 (ns.to_string(), "OLD_ALIAS".to_string(), 1),
1329 ];
1330 let snapshot = RegistryPlanningSnapshot::capture_with_hook(
1331 &GlobalRegistryFunctionProvider,
1332 &requests,
1333 10_000,
1334 |_| {},
1335 )
1336 .unwrap();
1337 let old_function = snapshot.get_function(ns, "TARGET").unwrap();
1338 let old_identity = snapshot
1339 .function_semantic_identity(ns, "TARGET", 1)
1340 .unwrap();
1341
1342 register_function(planning_fn(ns, "TARGET", &[], FnCaps::MAY_SPILL));
1343 let current = resolve_for_arity(ns, "TARGET", 1).unwrap();
1344 assert!(current.semantics.generation > old_identity.generation);
1345 assert!(!Arc::ptr_eq(&old_function, ¤t.function));
1346 assert_eq!(
1347 snapshot
1348 .function_semantic_identity(ns, "TARGET", 1)
1349 .unwrap(),
1350 old_identity
1351 );
1352 assert!(Arc::ptr_eq(
1353 &old_function,
1354 &snapshot.get_function(ns, "TARGET").unwrap(),
1355 ));
1356 assert!(get(ns, "OLD_ALIAS").is_none());
1357 assert_eq!(
1358 snapshot
1359 .function_semantic_identity(ns, "OLD_ALIAS", 1)
1360 .unwrap(),
1361 old_identity
1362 );
1363 assert!(Arc::ptr_eq(
1364 &old_function,
1365 &snapshot.get_function(ns, "OLD_ALIAS").unwrap(),
1366 ));
1367 }
1368
1369 #[test]
1370 fn planning_snapshot_requires_explicit_side_effect_free_provider_opt_in() {
1371 struct RuntimeOnlyProvider(Arc<dyn Function>);
1372 impl FunctionProvider for RuntimeOnlyProvider {
1373 fn get_function(&self, _ns: &str, _name: &str) -> Option<Arc<dyn Function>> {
1374 Some(Arc::clone(&self.0))
1375 }
1376 }
1377
1378 let ns = "__PLANNING_FAIL_CLOSED__";
1379 register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1380 let result = RegistryPlanningSnapshot::capture_for_requests(
1381 &RuntimeOnlyProvider(planning_fn(ns, "TARGET", &[], FnCaps::empty())),
1382 [(ns.to_string(), "TARGET".to_string(), 1)],
1383 );
1384 assert_eq!(
1385 result.err(),
1386 Some(PlanningSnapshotError::ProviderRevisionUnavailable)
1387 );
1388 }
1389
1390 #[test]
1391 fn planning_snapshot_preserves_runtime_override_without_global_semantics() {
1392 struct OverrideProvider(Arc<dyn Function>);
1393 impl FunctionProvider for OverrideProvider {
1394 fn planning_semantic_revision(&self) -> Option<u64> {
1395 Some(0)
1396 }
1397 fn get_function(&self, _ns: &str, _name: &str) -> Option<Arc<dyn Function>> {
1398 Some(Arc::clone(&self.0))
1399 }
1400 fn get_function_for_planning(
1401 &self,
1402 _ns: &str,
1403 _name: &str,
1404 ) -> Option<Arc<dyn Function>> {
1405 Some(Arc::clone(&self.0))
1406 }
1407 }
1408
1409 let ns = "__PLANNING_OVERRIDE__";
1410 register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1411 let global = get(ns, "TARGET").unwrap();
1412 let runtime = planning_fn(ns, "TARGET", &[], FnCaps::MAY_SPILL);
1413 let provider = OverrideProvider(Arc::clone(&runtime));
1414 let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1415 &provider,
1416 [(ns.to_string(), "TARGET".to_string(), 1)],
1417 )
1418 .unwrap();
1419
1420 let captured = snapshot.get_function(ns, "TARGET").unwrap();
1421 assert!(Arc::ptr_eq(&captured, &runtime));
1422 assert!(!Arc::ptr_eq(&captured, &global));
1423 assert!(
1424 snapshot
1425 .function_semantic_identity(ns, "TARGET", 1)
1426 .is_none()
1427 );
1428 assert_eq!(snapshot.functions.len(), 1);
1429 assert_eq!(
1430 snapshot.function_capabilities(ns, "TARGET"),
1431 Some(FnCaps::MAY_SPILL)
1432 );
1433 assert_eq!(snapshot.capabilities.len(), 1);
1434 assert!(snapshot.identities.is_empty());
1435 assert!(snapshot.get_function(ns, "UNREQUESTED").is_none());
1436 }
1437
1438 #[test]
1439 fn planning_snapshot_retries_provider_revision_flip_to_runtime_override() {
1440 struct FlippingProvider {
1441 function: Arc<RwLock<Arc<dyn Function>>>,
1442 revision: Arc<AtomicU64>,
1443 }
1444 impl FunctionProvider for FlippingProvider {
1445 fn planning_semantic_revision(&self) -> Option<u64> {
1446 Some(self.revision.load(Ordering::Acquire))
1447 }
1448 fn get_function(&self, _ns: &str, _name: &str) -> Option<Arc<dyn Function>> {
1449 Some(Arc::clone(&self.function.read().unwrap()))
1450 }
1451 fn get_function_for_planning(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
1452 self.get_function(ns, name)
1453 }
1454 }
1455
1456 let ns = "__PLANNING_PROVIDER_FLIP__";
1457 register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1458 let global = get(ns, "TARGET").unwrap();
1459 let override_function = planning_fn(ns, "TARGET", &[], FnCaps::MAY_SPILL);
1460 let function = Arc::new(RwLock::new(global));
1461 let revision = Arc::new(AtomicU64::new(0));
1462 let provider = FlippingProvider {
1463 function: Arc::clone(&function),
1464 revision: Arc::clone(&revision),
1465 };
1466 let requests = [(ns.to_string(), "TARGET".to_string(), 1)];
1467 let snapshot =
1468 RegistryPlanningSnapshot::capture_with_hook(&provider, &requests, 2, |attempt| {
1469 if attempt == 0 {
1470 *function.write().unwrap() = Arc::clone(&override_function);
1471 revision.fetch_add(1, Ordering::AcqRel);
1472 }
1473 })
1474 .unwrap();
1475
1476 assert_eq!(snapshot.provider_revision(), Some(1));
1477 assert!(Arc::ptr_eq(
1478 &snapshot.get_function(ns, "TARGET").unwrap(),
1479 &override_function
1480 ));
1481 assert!(
1482 snapshot
1483 .function_semantic_identity(ns, "TARGET", 1)
1484 .is_none()
1485 );
1486 }
1487
1488 #[test]
1489 fn planning_snapshot_capture_tolerates_unrelated_concurrent_registrations() {
1490 let ns = "__PLANNING_UNRELATED_CHURN__";
1506 register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1507 let requests = [(ns.to_string(), "TARGET".to_string(), 1)];
1508
1509 let mut unrelated_registrations = 0usize;
1510 let snapshot = RegistryPlanningSnapshot::capture_with_hook(
1511 &GlobalRegistryFunctionProvider,
1512 &requests,
1513 2,
1514 |_| {
1515 register_function(planning_fn(
1519 "__PLANNING_UNRELATED_CHURN_OTHER__",
1520 "OTHER",
1521 &[],
1522 FnCaps::empty(),
1523 ));
1524 unrelated_registrations += 1;
1525 },
1526 )
1527 .expect("unrelated registrations must not invalidate a planning snapshot capture");
1528
1529 assert!(unrelated_registrations > 0, "hook must have registered");
1530 assert!(Arc::ptr_eq(
1531 &snapshot.get_function(ns, "TARGET").unwrap(),
1532 &get(ns, "TARGET").unwrap(),
1533 ));
1534 }
1535
1536 #[test]
1537 fn planning_snapshot_capture_retries_and_fails_deterministically() {
1538 let ns = "__PLANNING_RACE__";
1539 register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1540 let requests = [(ns.to_string(), "TARGET".to_string(), 1)];
1541 let before = semantic_epoch();
1542 let retried = RegistryPlanningSnapshot::capture_with_hook(
1543 &GlobalRegistryFunctionProvider,
1544 &requests,
1545 100,
1546 |attempt| {
1547 if attempt == 0 {
1548 register_function(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1549 }
1550 },
1551 )
1552 .unwrap();
1553 assert!(retried.epoch() > before);
1554
1555 let failed = RegistryPlanningSnapshot::capture_with_hook(
1556 &GlobalRegistryFunctionProvider,
1557 &requests,
1558 2,
1559 |_| register_function(planning_fn(ns, "TARGET", &[], FnCaps::empty())),
1560 );
1561 assert_eq!(
1562 failed.err(),
1563 Some(PlanningSnapshotError::RegistryChangedDuringCapture)
1564 );
1565 }
1566
1567 #[test]
1568 fn planning_snapshot_nested_function_authority_matches_global_registry() {
1569 let ns = "";
1570 register_builtin(planning_fn(ns, "__PLAN_OUTER__", &[], FnCaps::empty()));
1571 register_builtin(planning_fn(ns, "__PLAN_INNER__", &[], FnCaps::empty()));
1572 let requests = [
1573 (String::new(), "__PLAN_OUTER__".to_string(), 1),
1574 (String::new(), "_xlfn.__PLAN_INNER__".to_string(), 1),
1575 ];
1576 let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1577 &GlobalRegistryFunctionProvider,
1578 requests,
1579 )
1580 .unwrap();
1581 let ast =
1582 formualizer_parse::parser::parse("=__PLAN_OUTER__(_xlfn.__PLAN_INNER__(A1))").unwrap();
1583 let frozen = crate::formula_plane::template_canonical::canonicalize_template_with_provider(
1584 &ast,
1585 2,
1586 2,
1587 Some(&snapshot),
1588 );
1589 let global = crate::formula_plane::template_canonical::canonicalize_template_with_provider(
1590 &ast,
1591 2,
1592 2,
1593 Some(&GlobalRegistryFunctionProvider),
1594 );
1595 assert_eq!(frozen, global);
1596 assert!(frozen.labels.is_authority_supported());
1597 }
1598
1599 #[test]
1600 fn parallel_snapshot_capture_and_prefix_resolution_does_not_deadlock() {
1601 let ns = "__PLANNING_PARALLEL__";
1602 register_builtin(planning_fn(ns, "TARGET", &["ALIAS"], FnCaps::empty()));
1603 let (send, receive) = std::sync::mpsc::channel();
1604 std::thread::spawn(move || {
1605 let mut workers = Vec::new();
1606 for worker in 0..4 {
1607 workers.push(std::thread::spawn(move || {
1608 for iteration in 0..100 {
1609 if worker == 0 && iteration % 10 == 0 {
1610 register_function(planning_fn(
1611 ns,
1612 "TARGET",
1613 &["ALIAS"],
1614 FnCaps::empty(),
1615 ));
1616 }
1617 let _ = RegistryPlanningSnapshot::capture_for_requests(
1618 &GlobalRegistryFunctionProvider,
1619 [(ns.to_string(), "_xlfn.alias".to_string(), 1)],
1620 );
1621 let _ = get(ns, "_xlfn.alias");
1622 }
1623 }));
1624 }
1625 for worker in workers {
1626 worker.join().unwrap();
1627 }
1628 send.send(()).unwrap();
1629 });
1630 receive
1631 .recv_timeout(std::time::Duration::from_secs(10))
1632 .expect("parallel registry planning timed out (possible lock inversion)");
1633 }
1634
1635 #[test]
1636 fn resolves_prefixes_aliases_and_direct_registration() {
1637 let ns = "__REG_PREFIX__";
1638 register_function(Arc::new(TestFn {
1639 ns,
1640 name: "FILTER",
1641 aliases: &["LEGACY"],
1642 }));
1643 assert_eq!(get(ns, "_xlfn._xlws.legacy").unwrap().name(), "FILTER");
1644 register_function(Arc::new(TestFn {
1645 ns,
1646 name: "_XLFN.FILTER",
1647 aliases: &[],
1648 }));
1649 assert_eq!(get(ns, "_xlfn.filter").unwrap().name(), "_XLFN.FILTER");
1650 }
1651
1652 #[test]
1653 fn trusted_replacement_records_removed_owned_alias_spelling() {
1654 let namespace = "__REG_STALE_ALIAS__";
1655 register_builtin(Arc::new(TestFn {
1656 ns: namespace,
1657 name: "TARGET",
1658 aliases: &["STALE_OWNED_ALIAS"],
1659 }));
1660 let before = semantic_epoch();
1661 register_function(Arc::new(TestFn {
1662 ns: namespace,
1663 name: "TARGET",
1664 aliases: &["NEW_OWNED_ALIAS"],
1665 }));
1666 let changes = semantic_changes_since(before);
1667 assert!(
1668 changes
1669 .keys
1670 .contains(&(namespace.to_string(), "STALE_OWNED_ALIAS".to_string()))
1671 );
1672 assert!(
1673 changes
1674 .keys
1675 .contains(&(namespace.to_string(), "NEW_OWNED_ALIAS".to_string()))
1676 );
1677 }
1678
1679 #[test]
1680 fn alias_requests_keep_the_spelling_used_by_the_formula() {
1681 let ns = "__ALIAS_REQUEST_SPELLING__";
1682 register_builtin(planning_fn(ns, "TARGET", &[], FnCaps::empty()));
1683 register_alias(ns, "FORMULA_ALIAS", ns, "TARGET");
1684 let request = (ns.to_string(), "FORMULA_ALIAS".to_string(), 1);
1685 let snapshot = RegistryPlanningSnapshot::capture_for_requests(
1686 &GlobalRegistryFunctionProvider,
1687 [request.clone()],
1688 )
1689 .unwrap();
1690
1691 assert_eq!(snapshot.requests.as_ref(), &[request]);
1692 assert_eq!(
1693 snapshot
1694 .function_semantic_identity(ns, "FORMULA_ALIAS", 1)
1695 .unwrap()
1696 .canonical_name,
1697 "TARGET"
1698 );
1699 }
1700
1701 #[test]
1702 fn alias_mutations_affect_alias_requests_but_not_direct_target_requests() {
1703 let ns = "__ALIAS_CHANGE_SCOPE__";
1704 register_builtin(planning_fn(ns, "OLD_TARGET", &[], FnCaps::empty()));
1705 register_builtin(planning_fn(ns, "NEW_TARGET", &[], FnCaps::empty()));
1706
1707 let add_epoch = semantic_epoch();
1708 register_alias(ns, "ADDED_ALIAS", ns, "OLD_TARGET");
1709 assert!(semantic_changes_affect_requests_since(
1710 add_epoch,
1711 [(ns.to_string(), "ADDED_ALIAS".to_string(), 1)]
1712 ));
1713 assert!(!semantic_changes_affect_requests_since(
1714 add_epoch,
1715 [(ns.to_string(), "OLD_TARGET".to_string(), 1)]
1716 ));
1717
1718 register_alias(ns, "RETARGETED_ALIAS", ns, "OLD_TARGET");
1719 let retarget_epoch = semantic_epoch();
1720 register_alias(ns, "RETARGETED_ALIAS", ns, "NEW_TARGET");
1721 assert!(semantic_changes_affect_requests_since(
1722 retarget_epoch,
1723 [(ns.to_string(), "RETARGETED_ALIAS".to_string(), 1)]
1724 ));
1725 assert!(semantic_changes_affect_requests_since(
1726 retarget_epoch,
1727 [(ns.to_string(), "_xlfn.RETARGETED_ALIAS".to_string(), 1)]
1728 ));
1729 for target in ["OLD_TARGET", "NEW_TARGET"] {
1730 assert!(!semantic_changes_affect_requests_since(
1731 retarget_epoch,
1732 [(ns.to_string(), target.to_string(), 1)]
1733 ));
1734 }
1735
1736 register_alias(ns, "REMOVED_ALIAS", ns, "OLD_TARGET");
1737 let remove_epoch = semantic_epoch();
1738 {
1739 let mut state = REGISTRY
1740 .write()
1741 .unwrap_or_else(|poisoned| poisoned.into_inner());
1742 let alias_key = (ns.to_string(), "REMOVED_ALIAS".to_string());
1743 assert!(state.aliases.remove(&alias_key).is_some());
1744 publish_semantic_change(&mut state, [alias_key]);
1745 }
1746 assert!(semantic_changes_affect_requests_since(
1747 remove_epoch,
1748 [(ns.to_string(), "REMOVED_ALIAS".to_string(), 1)]
1749 ));
1750 assert!(!semantic_changes_affect_requests_since(
1751 remove_epoch,
1752 [(ns.to_string(), "OLD_TARGET".to_string(), 1)]
1753 ));
1754 }
1755
1756 #[test]
1757 fn request_change_check_stays_conservative_after_log_truncation() {
1758 let mut state = RegistryState::default();
1759 let before = state.semantic_epoch;
1760 for index in 0..=1_024 {
1761 publish_semantic_change(&mut state, [(String::new(), format!("UNRELATED_{index}"))]);
1762 }
1763
1764 assert!(semantic_changes_affect_requests_in_state(
1765 &state,
1766 before,
1767 [(String::new(), "TARGET".to_string(), 1)]
1768 ));
1769 }
1770
1771 #[test]
1772 fn replacement_advances_semantic_generation() {
1773 let ns = "__REG_GENERATION__";
1774 register_function(Arc::new(TestFn {
1775 ns,
1776 name: "F",
1777 aliases: &[],
1778 }));
1779 let first = resolve(ns, "F").unwrap().semantics.generation;
1780 let epoch = semantic_epoch();
1781 register_function(Arc::new(TestFn {
1782 ns,
1783 name: "F",
1784 aliases: &[],
1785 }));
1786 let second = resolve(ns, "F").unwrap().semantics.generation;
1787 assert!(second > first);
1788 let changes = semantic_changes_since(epoch);
1789 assert!(changes.epoch > epoch);
1790 assert!(changes.keys.contains(&(ns.to_string(), "F".to_string())));
1791 }
1792
1793 struct PanickingSchemaFn;
1794
1795 impl Function for PanickingSchemaFn {
1796 fn name(&self) -> &'static str {
1797 "PANICKING_SCHEMA"
1798 }
1799 fn semantic_contract(&self, _arity: usize) -> Option<FunctionSemanticContract> {
1800 Some(FunctionSemanticContract::trusted_builtin_default(None))
1801 }
1802 fn arg_schema(&self) -> &'static [crate::args::ArgSchema] {
1803 panic!("bad schema")
1804 }
1805 fn eval<'a, 'b, 'c>(
1806 &self,
1807 _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
1808 _ctx: &dyn crate::traits::FunctionContext<'b>,
1809 ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
1810 unreachable!()
1811 }
1812 }
1813
1814 #[test]
1815 fn schema_panic_as_sole_defect_is_non_panicking_and_fails_closed() {
1816 register_function(Arc::new(PanickingSchemaFn));
1817 let semantics = resolve("", "PANICKING_SCHEMA").unwrap().semantics;
1818 assert!(semantics.contract.is_none());
1819 assert!(
1820 semantics
1821 .issues
1822 .contains(&SemanticConformanceIssue::ArgumentSchemaPanicked)
1823 );
1824 assert_eq!(
1825 semantics.issues,
1826 vec![SemanticConformanceIssue::ArgumentSchemaPanicked]
1827 );
1828 assert!(!semantics.conforms());
1829 }
1830
1831 #[test]
1832 fn every_registered_builtin_has_a_conforming_semantic_contract() {
1833 crate::builtins::load_builtins();
1834 let builtins: Vec<_> = snapshot_semantics()
1835 .into_iter()
1836 .filter(|entry| entry.semantics.trusted_builtin)
1837 .collect();
1838 assert!(builtins.len() > 100);
1839 let rejected: Vec<_> = builtins
1840 .iter()
1841 .filter(|entry| !entry.semantics.conforms())
1842 .map(|entry| {
1843 (
1844 &entry.namespace,
1845 &entry.canonical_name,
1846 &entry.semantics.issues,
1847 )
1848 })
1849 .collect();
1850 assert!(rejected.is_empty(), "non-conforming builtins: {rejected:?}");
1851 }
1852
1853 #[test]
1854 fn semantic_contract_is_context_and_arity_aware() {
1855 crate::builtins::lookup::register_builtins();
1856 let row_without_arg = resolve_for_arity("", "ROW", 0).unwrap();
1857 let row_with_arg = resolve_for_arity("", "ROW", 1).unwrap();
1858 assert_eq!(
1859 row_without_arg.semantics.contract.unwrap().context,
1860 crate::function_contract::FunctionContextDependence::PlacementDependent
1861 );
1862 assert_eq!(
1863 row_with_arg.semantics.contract.unwrap().context,
1864 crate::function_contract::FunctionContextDependence::None
1865 );
1866 }
1867
1868 #[test]
1869 fn semantic_identity_encodes_effective_by_reference_roles_for_call_arity() {
1870 crate::builtins::load_builtins();
1871 let provider = GlobalRegistryFunctionProvider;
1872 let sum = resolve_semantic_identity(&provider, "", "SUM", 3).unwrap();
1873 assert_eq!(sum.argument_by_ref, vec![false, false, false]);
1874
1875 let row = resolve_semantic_identity(&provider, "", "ROW", 1).unwrap();
1876 assert_eq!(row.argument_by_ref, vec![true]);
1877 }
1878
1879 struct ExplicitSafeCustomFn;
1880
1881 impl Function for ExplicitSafeCustomFn {
1882 fn name(&self) -> &'static str {
1883 "EXPLICIT_SAFE_CUSTOM"
1884 }
1885 fn semantic_contract(&self, _arity: usize) -> Option<FunctionSemanticContract> {
1886 Some(FunctionSemanticContract::trusted_builtin_default(None))
1887 }
1888 fn eval<'a, 'b, 'c>(
1889 &self,
1890 _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
1891 _ctx: &dyn crate::traits::FunctionContext<'b>,
1892 ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
1893 unreachable!()
1894 }
1895 }
1896
1897 struct MismatchedPrecisionCustomFn;
1898
1899 impl Function for MismatchedPrecisionCustomFn {
1900 fn name(&self) -> &'static str {
1901 "MISMATCHED_PRECISION_CUSTOM"
1902 }
1903 fn dependency_contract(
1904 &self,
1905 arity: usize,
1906 ) -> Option<crate::function_contract::FunctionDependencyContract> {
1907 crate::function_contract::FunctionDependencyContract::static_scalar_all_args(arity)
1908 }
1909 fn semantic_contract(&self, _arity: usize) -> Option<FunctionSemanticContract> {
1910 Some(FunctionSemanticContract::trusted_builtin_default(None))
1911 }
1912 fn min_args(&self) -> usize {
1913 1
1914 }
1915 fn arg_schema(&self) -> &'static [crate::args::ArgSchema] {
1916 static SCHEMA: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
1917 std::sync::LazyLock::new(|| vec![crate::args::ArgSchema::any()]);
1918 &SCHEMA
1919 }
1920 fn eval<'a, 'b, 'c>(
1921 &self,
1922 _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
1923 _ctx: &dyn crate::traits::FunctionContext<'b>,
1924 ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
1925 unreachable!()
1926 }
1927 }
1928
1929 #[test]
1930 fn explicit_precision_must_equal_dependency_contract() {
1931 register_function(Arc::new(MismatchedPrecisionCustomFn));
1932 let semantics = resolve_for_arity("", "MISMATCHED_PRECISION_CUSTOM", 1)
1933 .unwrap()
1934 .semantics;
1935 assert!(semantics.contract.is_none());
1936 assert!(
1937 semantics
1938 .issues
1939 .contains(&SemanticConformanceIssue::PrecisionContractMismatch)
1940 );
1941 }
1942
1943 #[test]
1944 fn explicit_custom_semantics_can_conform_without_becoming_trusted() {
1945 register_function(Arc::new(ExplicitSafeCustomFn));
1946 let semantics = resolve_for_arity("", "EXPLICIT_SAFE_CUSTOM", 0)
1947 .unwrap()
1948 .semantics;
1949 assert!(!semantics.trusted_builtin);
1950 assert!(semantics.conforms());
1951 }
1952
1953 #[test]
1954 fn concurrent_replacements_leave_only_final_owned_alias() {
1955 let ns = "__REG_CONCURRENT__";
1956 register_function(Arc::new(TestFn {
1957 ns,
1958 name: "TARGET",
1959 aliases: &["INITIAL"],
1960 }));
1961 let mut workers = Vec::new();
1962 for alias in ["A", "B", "C", "D"] {
1963 workers.push(std::thread::spawn(move || {
1964 for _ in 0..100 {
1965 let aliases: &'static [&'static str] = Box::leak(Box::new([alias]));
1966 register_function(Arc::new(TestFn {
1967 ns,
1968 name: "TARGET",
1969 aliases,
1970 }));
1971 assert_eq!(get(ns, "TARGET").unwrap().name(), "TARGET");
1972 }
1973 }));
1974 }
1975 for worker in workers {
1976 worker.join().unwrap();
1977 }
1978 register_function(Arc::new(TestFn {
1979 ns,
1980 name: "TARGET",
1981 aliases: &["FINAL"],
1982 }));
1983 for stale in ["INITIAL", "A", "B", "C", "D"] {
1984 assert!(get(ns, stale).is_none());
1985 }
1986 assert!(get(ns, "FINAL").is_some());
1987 }
1988
1989 #[test]
1990 fn independent_exception_inventory_matches_builtin_caps_and_context() {
1991 crate::builtins::load_builtins();
1992 for name in [
1993 "RAND",
1994 "RANDBETWEEN",
1995 "RANDARRAY",
1996 "TODAY",
1997 "NOW",
1998 "OFFSET",
1999 "INDIRECT",
2000 ] {
2001 assert!(
2002 get("", name).unwrap().caps().contains(FnCaps::VOLATILE),
2003 "{name}"
2004 );
2005 }
2006 for name in ["OFFSET", "INDIRECT"] {
2007 assert!(
2008 get("", name)
2009 .unwrap()
2010 .caps()
2011 .contains(FnCaps::DYNAMIC_DEPENDENCY),
2012 "{name}"
2013 );
2014 }
2015 for name in ["INDEX", "OFFSET", "INDIRECT", "CHOOSE"] {
2016 assert!(
2017 get("", name)
2018 .unwrap()
2019 .caps()
2020 .contains(FnCaps::RETURNS_REFERENCE),
2021 "{name}"
2022 );
2023 }
2024 for name in ["LET", "LAMBDA"] {
2025 assert!(
2026 get("", name)
2027 .unwrap()
2028 .caps()
2029 .contains(FnCaps::LOCAL_ENVIRONMENT),
2030 "{name}"
2031 );
2032 }
2033 for name in [
2034 "IF",
2035 "IFERROR",
2036 "IFNA",
2037 "IFS",
2038 "SWITCH",
2039 "CHOOSE",
2040 "FILTER",
2041 "UNIQUE",
2042 "SEQUENCE",
2043 "TRANSPOSE",
2044 "TAKE",
2045 "DROP",
2046 "SORT",
2047 "SORTBY",
2048 "RANDARRAY",
2049 "HSTACK",
2050 "VSTACK",
2051 "TOCOL",
2052 "TOROW",
2053 "CHOOSECOLS",
2054 "CHOOSEROWS",
2055 "FREQUENCY",
2056 "LINEST",
2057 "TREND",
2058 "GROWTH",
2059 "LOGEST",
2060 "MODE.MULT",
2061 "TEXTSPLIT",
2062 ] {
2063 assert!(
2064 get("", name).unwrap().caps().contains(FnCaps::MAY_SPILL),
2065 "{name}"
2066 );
2067 }
2068 const SHORT_CIRCUIT: &[&str] = &[
2069 "IF", "IFERROR", "IFNA", "IFS", "SWITCH", "CHOOSE", "LET", "LAMBDA", "AND", "OR",
2070 ];
2071 let observed_short_circuit: std::collections::BTreeSet<_> = snapshot_registered()
2072 .into_iter()
2073 .filter(|(namespace, _, function)| {
2074 namespace.is_empty() && function.caps().contains(FnCaps::SHORT_CIRCUIT)
2075 })
2076 .map(|(_, name, _)| name)
2077 .collect();
2078 let expected_short_circuit: std::collections::BTreeSet<_> = SHORT_CIRCUIT
2079 .iter()
2080 .map(|name| (*name).to_string())
2081 .collect();
2082 assert_eq!(observed_short_circuit, expected_short_circuit);
2083 for name in SHORT_CIRCUIT {
2084 let contract = resolve_for_arity("", name, get("", name).unwrap().min_args())
2085 .unwrap()
2086 .semantics
2087 .contract
2088 .unwrap();
2089 assert_eq!(
2090 contract.evaluation,
2091 FunctionEvaluationSemantics::ShortCircuit,
2092 "{name}"
2093 );
2094 }
2095 assert_eq!(
2096 resolve_for_arity("", "CHOOSE", 2)
2097 .unwrap()
2098 .semantics
2099 .contract
2100 .unwrap()
2101 .result,
2102 FunctionResultSemantics::MayReturnReferenceAndSpill
2103 );
2104 for name in ["ROW", "COLUMN"] {
2105 let contract = resolve_for_arity("", name, 0)
2106 .unwrap()
2107 .semantics
2108 .contract
2109 .unwrap();
2110 assert_eq!(
2111 contract.context,
2112 crate::function_contract::FunctionContextDependence::PlacementDependent,
2113 "{name}"
2114 );
2115 assert_eq!(
2116 resolve_for_arity("", name, 1)
2117 .unwrap()
2118 .semantics
2119 .contract
2120 .unwrap()
2121 .context,
2122 crate::function_contract::FunctionContextDependence::None,
2123 "{name} with argument"
2124 );
2125 }
2126 for name in ["CELL", "ISFORMULA", "FORMULATEXT", "SHEET", "SHEETS"] {
2127 let contract = resolve_for_arity("", name, get("", name).unwrap().min_args())
2128 .unwrap()
2129 .semantics
2130 .contract
2131 .unwrap();
2132 assert_eq!(
2133 contract.context,
2134 crate::function_contract::FunctionContextDependence::WorkbookMetadata,
2135 "{name}"
2136 );
2137 }
2138 }
2139
2140 struct NamePanicFn;
2141 impl Function for NamePanicFn {
2142 fn name(&self) -> &'static str {
2143 panic!("name")
2144 }
2145 fn eval<'a, 'b, 'c>(
2146 &self,
2147 _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
2148 _ctx: &dyn crate::traits::FunctionContext<'b>,
2149 ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
2150 unreachable!()
2151 }
2152 }
2153 struct NamespacePanicFn;
2154 impl Function for NamespacePanicFn {
2155 fn name(&self) -> &'static str {
2156 "NS_PANIC"
2157 }
2158 fn namespace(&self) -> &'static str {
2159 panic!("namespace")
2160 }
2161 fn eval<'a, 'b, 'c>(
2162 &self,
2163 _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
2164 _ctx: &dyn crate::traits::FunctionContext<'b>,
2165 ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
2166 unreachable!()
2167 }
2168 }
2169
2170 #[test]
2171 fn canonical_metadata_panics_decline_registration_without_unwinding() {
2172 assert_eq!(
2173 try_register_function(Arc::new(NamePanicFn)),
2174 Err(RegistrationError::NameMetadataPanicked)
2175 );
2176 assert_eq!(
2177 try_register_function(Arc::new(NamespacePanicFn)),
2178 Err(RegistrationError::NamespaceMetadataPanicked)
2179 );
2180 register_function(Arc::new(NamePanicFn));
2181 register_function(Arc::new(NamespacePanicFn));
2182 assert!(get("", "NS_PANIC").is_none());
2183 }
2184
2185 #[derive(Clone, Copy)]
2186 enum BadSchemaKind {
2187 TooLarge,
2188 Repeating,
2189 MinDisagreement,
2190 RequiredCount,
2191 TooManyRequired,
2192 RepeatWidth,
2193 }
2194 struct BadSchemaFn {
2195 name: &'static str,
2196 kind: BadSchemaKind,
2197 }
2198 impl Function for BadSchemaFn {
2199 fn name(&self) -> &'static str {
2200 self.name
2201 }
2202 fn min_args(&self) -> usize {
2203 if matches!(
2204 self.kind,
2205 BadSchemaKind::MinDisagreement
2206 | BadSchemaKind::RequiredCount
2207 | BadSchemaKind::RepeatWidth
2208 ) {
2209 2
2210 } else {
2211 1
2212 }
2213 }
2214 fn variadic(&self) -> bool {
2215 matches!(self.kind, BadSchemaKind::RepeatWidth)
2216 }
2217 fn semantic_contract(&self, _arity: usize) -> Option<FunctionSemanticContract> {
2218 Some(FunctionSemanticContract::trusted_builtin_default(None))
2219 }
2220 fn arg_schema(&self) -> &'static [crate::args::ArgSchema] {
2221 static ONE: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
2222 std::sync::LazyLock::new(|| vec![crate::args::ArgSchema::any()]);
2223 static BAD_REPEAT: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
2224 std::sync::LazyLock::new(|| {
2225 let mut arg = crate::args::ArgSchema::any();
2226 arg.repeating = Some(0);
2227 vec![arg]
2228 });
2229 static REQUIRED_COUNT: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
2230 std::sync::LazyLock::new(|| {
2231 let mut optional = crate::args::ArgSchema::any();
2232 optional.required = false;
2233 vec![crate::args::ArgSchema::any(), optional]
2234 });
2235 static REPEAT_WIDTH: std::sync::LazyLock<Vec<crate::args::ArgSchema>> =
2236 std::sync::LazyLock::new(|| {
2237 let first = crate::args::ArgSchema::any();
2238 let mut second = crate::args::ArgSchema::any();
2239 second.repeating = Some(2);
2240 vec![first, second]
2241 });
2242 match self.kind {
2243 BadSchemaKind::TooLarge => &ONE,
2244 BadSchemaKind::Repeating => &BAD_REPEAT,
2245 BadSchemaKind::MinDisagreement => &[],
2246 BadSchemaKind::RequiredCount => &REQUIRED_COUNT,
2247 BadSchemaKind::TooManyRequired => &REPEAT_WIDTH,
2248 BadSchemaKind::RepeatWidth => &REPEAT_WIDTH,
2249 }
2250 }
2251 fn eval<'a, 'b, 'c>(
2252 &self,
2253 _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
2254 _ctx: &dyn crate::traits::FunctionContext<'b>,
2255 ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
2256 unreachable!()
2257 }
2258 }
2259
2260 #[test]
2261 fn malformed_arity_and_schema_contracts_fail_closed() {
2262 for (name, kind, arity) in [
2263 ("TOO_LARGE", BadSchemaKind::TooLarge, 2),
2264 ("BAD_REPEAT", BadSchemaKind::Repeating, 1),
2265 ("MIN_DISAGREEMENT", BadSchemaKind::MinDisagreement, 2),
2266 ("REQUIRED_COUNT", BadSchemaKind::RequiredCount, 2),
2267 ("TOO_MANY_REQUIRED", BadSchemaKind::TooManyRequired, 1),
2268 ("REPEAT_WIDTH", BadSchemaKind::RepeatWidth, 3),
2269 ] {
2270 register_function(Arc::new(BadSchemaFn { name, kind }));
2271 let semantics = resolve_for_arity("", name, arity).unwrap().semantics;
2272 assert!(semantics.contract.is_none(), "{name}");
2273 assert!(
2274 semantics
2275 .issues
2276 .contains(&SemanticConformanceIssue::AritySchemaMismatch),
2277 "{name}: {:?}",
2278 semantics.issues
2279 );
2280 }
2281 }
2282
2283 #[test]
2284 fn valid_optional_and_width_n_repeating_schemas_conform() {
2285 let required = crate::args::ArgSchema::any();
2286 let mut optional = crate::args::ArgSchema::any();
2287 optional.required = false;
2288 assert!(schema_allows_arity(
2289 &[required.clone(), optional],
2290 1,
2291 false,
2292 2,
2293 true
2294 ));
2295
2296 let mut repeat_end = crate::args::ArgSchema::any();
2297 repeat_end.repeating = Some(2);
2298 let repeating = [required, repeat_end];
2299 assert!(schema_allows_arity(&repeating, 2, true, 4, true));
2300 assert!(!schema_allows_arity(&repeating, 2, true, 3, true));
2301 }
2302
2303 #[test]
2304 fn replacement_readers_observe_generation_and_epoch_atomically() {
2305 let ns = "__REG_SNAPSHOT_RACE__";
2306 register_builtin(Arc::new(TestFn {
2307 ns,
2308 name: "TARGET",
2309 aliases: &[],
2310 }));
2311 let (initial_epoch, initial) = resolve_with_epoch(ns, "TARGET").unwrap();
2312 let initial_generation = initial.semantics.generation;
2313 let barrier = Arc::new(std::sync::Barrier::new(5));
2314 let mut readers = Vec::new();
2315 for _ in 0..4 {
2316 let barrier = Arc::clone(&barrier);
2317 readers.push(std::thread::spawn(move || {
2318 barrier.wait();
2319 for _ in 0..1_000 {
2320 let (epoch, resolved) = resolve_with_epoch(ns, "TARGET").unwrap();
2321 if resolved.semantics.generation != initial_generation {
2322 assert!(epoch > initial_epoch);
2323 }
2324 }
2325 }));
2326 }
2327 barrier.wait();
2328 register_function(Arc::new(TestFn {
2329 ns,
2330 name: "TARGET",
2331 aliases: &[],
2332 }));
2333 for reader in readers {
2334 reader.join().unwrap();
2335 }
2336 let (epoch, resolved) = resolve_with_epoch(ns, "TARGET").unwrap();
2337 assert!(epoch > initial_epoch);
2338 assert!(resolved.semantics.generation > initial_generation);
2339 }
2340}