1
use crate::ast::versioned_cache::VersionedCache;
2
use crate::ast::{DomainPtr, ReturnType, declaration::declaration_content_generation};
3
use polyquine::Quine;
4
use proc_macro2::TokenStream;
5
use quote::quote;
6
use serde::{Deserialize, Serialize};
7
use std::fmt::{Debug, Display};
8
use std::hash::Hash;
9
use std::sync::atomic::{AtomicU64, Ordering};
10
use uniplate::derive_unplateable;
11

            
12
derive_unplateable!(Metadata);
13

            
14
pub 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)]
19
pub 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

            
40
impl Metadata {
41
    /// Creates empty metadata with no type, source span, or cached rewrite state.
42
4528576897
    pub fn new() -> Metadata {
43
4528576897
        Metadata {
44
4528576897
            etype: None,
45
4528576897
            span_id: None,
46
4528576897
            cached_content_hash: AtomicU64::new(NO_HASH),
47
4528576897
            domain: VersionedCache::new(),
48
4528576897
        }
49
4528576897
    }
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
3206054961
    pub fn clear_cached_domain(&self) {
63
3206054961
        self.domain.clear();
64
3206054961
    }
65

            
66
    /// Returns the cached domain for this expression, computing and caching it via `compute` on
67
    /// first access.
68
487436465
    pub fn domain_or_init(
69
487436465
        &self,
70
487436465
        mut compute: impl FnMut() -> Option<DomainPtr>,
71
487436465
    ) -> Option<DomainPtr> {
72
487436465
        self.domain
73
487436465
            .get_or_init(declaration_content_generation, |_| compute())
74
487436465
    }
75
}
76

            
77
impl Default for Metadata {
78
1258042
    fn default() -> Self {
79
1258042
        Metadata::new()
80
1258042
    }
81
}
82

            
83
impl Display for Metadata {
84
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85
        write!(f, "Metadata")
86
    }
87
}
88

            
89
impl Hash for Metadata {
90
2807603
    fn hash<H: std::hash::Hasher>(&self, _state: &mut H) {
91
        // Dummy method - Metadata is ignored when hashing an Expression
92
2807603
    }
93
}
94

            
95
impl Clone for Metadata {
96
89447324934
    fn clone(&self) -> Self {
97
89447324934
        Metadata {
98
89447324934
            etype: self.etype.clone(),
99
89447324934
            span_id: self.span_id,
100
89447324934
            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
89447324934
            domain: VersionedCache::new(),
107
89447324934
        }
108
89447324934
    }
109
}
110

            
111
impl PartialEq for Metadata {
112
630306337
    fn eq(&self, other: &Self) -> bool {
113
630306337
        self.etype == other.etype
114
630306337
    }
115
}
116

            
117
impl Eq for Metadata {}
118

            
119
impl Quine for Metadata {
120
2389
    fn ctor_tokens(&self) -> TokenStream {
121
2389
        let etype = self.etype.ctor_tokens();
122
2389
        let span_id = self.span_id.ctor_tokens();
123
2389
        quote! {{
124
            let mut metadata = conjure_cp::ast::Metadata::new();
125
            metadata.etype = #etype;
126
            metadata.span_id = #span_id;
127
            metadata
128
        }}
129
2389
    }
130
}