Skip to main content

conjure_cp_core/ast/
metadata.rs

1use crate::ast::versioned_cache::VersionedCache;
2use crate::ast::{DomainPtr, ReturnType, declaration::declaration_content_generation};
3use polyquine::Quine;
4use proc_macro2::TokenStream;
5use quote::quote;
6use serde::{Deserialize, Serialize};
7use std::fmt::{Debug, Display};
8use std::hash::Hash;
9use std::sync::atomic::{AtomicU64, Ordering};
10use uniplate::derive_unplateable;
11
12derive_unplateable!(Metadata);
13
14pub const NO_HASH: u64 = 0;
15/// Per-expression metadata used for typing, source mapping, and runtime caches.
16///
17/// Metadata is ignored by expression equality and hashing.
18#[derive(Debug, Deserialize, Serialize)]
19pub struct Metadata {
20    /// Cached or inferred return type for this expression.
21    pub etype: Option<ReturnType>,
22    /// Optional source span identifier for diagnostics and reporting.
23    #[serde(default, skip_serializing_if = "Option::is_none")]
24    pub span_id: Option<u32>,
25    /// Cached expression content hash used by semantic hashing.
26    #[serde(default, skip_serializing)]
27    pub cached_content_hash: AtomicU64,
28    /// Cached result of `Expression::domain_of()` for this node, stamped internally with the
29    /// declaration generation used to compute it.
30    ///
31    /// The generation records the declaration state used to compute the domain. `domain_of()`
32    /// recomputes recursively from scratch on every cache miss, so repeatedly calling it on the
33    /// same subtree (as rules that check "is this operand scalar/abstract" tend to, once per rule
34    /// attempt) is quadratic in the worst case. Cleared when this expression or a descendant
35    /// changes, and ignored after a referenced declaration changes.
36    #[serde(skip)]
37    domain: VersionedCache<Option<DomainPtr>>,
38}
39
40impl Metadata {
41    /// Creates empty metadata with no type, source span, or cached rewrite state.
42    pub fn new() -> Metadata {
43        Metadata {
44            etype: None,
45            span_id: None,
46            cached_content_hash: AtomicU64::new(NO_HASH),
47            domain: VersionedCache::new(),
48        }
49    }
50
51    /// Creates empty metadata associated with a source span identifier.
52    pub fn with_span_id(span_id: u32) -> Metadata {
53        Metadata {
54            etype: None,
55            span_id: Some(span_id),
56            cached_content_hash: AtomicU64::new(NO_HASH),
57            domain: VersionedCache::new(),
58        }
59    }
60
61    /// Clears the cached domain after this expression or a descendant changes.
62    pub fn clear_cached_domain(&self) {
63        self.domain.clear();
64    }
65
66    /// Returns the cached domain for this expression, computing and caching it via `compute` on
67    /// first access.
68    pub fn domain_or_init(
69        &self,
70        mut compute: impl FnMut() -> Option<DomainPtr>,
71    ) -> Option<DomainPtr> {
72        self.domain
73            .get_or_init(declaration_content_generation, |_| compute())
74    }
75}
76
77impl Default for Metadata {
78    fn default() -> Self {
79        Metadata::new()
80    }
81}
82
83impl Display for Metadata {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        write!(f, "Metadata")
86    }
87}
88
89impl Hash for Metadata {
90    fn hash<H: std::hash::Hasher>(&self, _state: &mut H) {
91        // Dummy method - Metadata is ignored when hashing an Expression
92    }
93}
94
95impl Clone for Metadata {
96    fn clone(&self) -> Self {
97        Metadata {
98            etype: self.etype.clone(),
99            span_id: self.span_id,
100            cached_content_hash: AtomicU64::new(self.cached_content_hash.load(Ordering::Relaxed)),
101            // Deliberately *not* carried over, unlike the other runtime caches above: some
102            // callers clone an expression template and then mutate its children directly (e.g.
103            // comprehension-body substitution), bypassing the rewriter's zipper-based
104            // cached-domain invalidation entirely. A clone always starts uncached so
105            // that path can never observe a stale domain.
106            domain: VersionedCache::new(),
107        }
108    }
109}
110
111impl PartialEq for Metadata {
112    fn eq(&self, other: &Self) -> bool {
113        self.etype == other.etype
114    }
115}
116
117impl Eq for Metadata {}
118
119impl Quine for Metadata {
120    fn ctor_tokens(&self) -> TokenStream {
121        let etype = self.etype.ctor_tokens();
122        let span_id = self.span_id.ctor_tokens();
123        quote! {{
124            let mut metadata = conjure_cp::ast::Metadata::new();
125            metadata.etype = #etype;
126            metadata.span_id = #span_id;
127            metadata
128        }}
129    }
130}