Skip to main content

shape_runtime/metadata/
unified.rs

1//! Unified Metadata API
2//!
3//! Combines metadata from all sources (Rust builtins + Shape stdlib)
4
5use super::types::{FunctionInfo, PropertyInfo};
6use crate::builtin_metadata::builtin_functions_from_macros;
7use crate::stdlib_metadata::{StdlibMetadata, default_stdlib_path};
8
9/// Unified metadata combining all sources (Rust builtins + Shape stdlib)
10///
11/// This provides a single API for the LSP to get all function/pattern metadata
12/// regardless of whether they're implemented in Rust or Shape stdlib.
13#[derive(Debug)]
14pub struct UnifiedMetadata {
15    /// Functions from #[shape_builtin] proc-macro
16    rust_builtins: Vec<FunctionInfo>,
17    /// Functions from Shape stdlib
18    stdlib_functions: Vec<FunctionInfo>,
19    /// Patterns from Shape stdlib
20    stdlib_patterns: Vec<crate::stdlib_metadata::PatternInfo>,
21    /// Type metadata from #[derive(ShapeType)]
22    type_metadata: Vec<TypeMetadataInfo>,
23}
24
25/// Runtime type metadata info (converted from compile-time TypeMetadata)
26#[derive(Debug, Clone)]
27pub struct TypeMetadataInfo {
28    pub name: String,
29    pub description: String,
30    pub properties: Vec<PropertyInfo>,
31}
32
33impl UnifiedMetadata {
34    /// Load unified metadata from all sources
35    pub fn load() -> Self {
36        use crate::builtin_metadata::collect_type_metadata;
37
38        // 1. Collect from proc-macro generated constants
39        let mut rust_builtins = builtin_functions_from_macros();
40
41        // 2. Parse stdlib files
42        let stdlib_path = default_stdlib_path();
43        let stdlib = StdlibMetadata::load(&stdlib_path).unwrap_or_else(|e| {
44            eprintln!("Warning: Failed to load stdlib metadata: {}", e);
45            StdlibMetadata::empty()
46        });
47
48        // If std/core provides declaration-only intrinsic function docs/signatures,
49        // apply them as authoritative metadata for matching runtime builtins.
50        if !stdlib.intrinsic_functions.is_empty() {
51            let intrinsic_by_name: std::collections::HashMap<_, _> = stdlib
52                .intrinsic_functions
53                .iter()
54                .map(|f| (f.name.clone(), f))
55                .collect();
56            for builtin in &mut rust_builtins {
57                if let Some(intrinsic) = intrinsic_by_name.get(&builtin.name) {
58                    builtin.signature = intrinsic.signature.clone();
59                    builtin.description = intrinsic.description.clone();
60                    builtin.parameters = intrinsic.parameters.clone();
61                    builtin.return_type = intrinsic.return_type.clone();
62                    builtin.comptime_only = intrinsic.comptime_only;
63                }
64            }
65            for intrinsic in &stdlib.intrinsic_functions {
66                if !rust_builtins.iter().any(|f| f.name == intrinsic.name) {
67                    rust_builtins.push(intrinsic.clone());
68                }
69            }
70        }
71
72        // 3. Collect type metadata from derive macro
73        let type_metadata = collect_type_metadata()
74            .into_iter()
75            .map(|tm| TypeMetadataInfo {
76                name: tm.name.to_string(),
77                description: tm.description.to_string(),
78                properties: tm.to_property_infos(),
79            })
80            .collect();
81
82        Self {
83            rust_builtins,
84            stdlib_functions: stdlib.functions,
85            stdlib_patterns: stdlib.patterns,
86            type_metadata,
87        }
88    }
89
90    /// Get all functions from all sources (deduplicated)
91    pub fn all_functions(&self) -> Vec<&FunctionInfo> {
92        let mut seen = std::collections::HashSet::new();
93        let mut result = Vec::new();
94
95        // Priority: rust_builtins > stdlib
96        for func in &self.rust_builtins {
97            if seen.insert(&func.name) {
98                result.push(func);
99            }
100        }
101        for func in &self.stdlib_functions {
102            if seen.insert(&func.name) {
103                result.push(func);
104            }
105        }
106
107        result
108    }
109
110    /// Get function by name
111    pub fn get_function(&self, name: &str) -> Option<&FunctionInfo> {
112        // Check rust builtins first
113        if let Some(func) = self.rust_builtins.iter().find(|f| f.name == name) {
114            return Some(func);
115        }
116        // Check stdlib
117        self.stdlib_functions.iter().find(|f| f.name == name)
118    }
119
120    /// Get all patterns from stdlib
121    pub fn all_patterns(&self) -> &[crate::stdlib_metadata::PatternInfo] {
122        &self.stdlib_patterns
123    }
124
125    /// Get stdlib functions only
126    pub fn stdlib_functions(&self) -> &[FunctionInfo] {
127        &self.stdlib_functions
128    }
129
130    /// Get rust builtin functions only
131    pub fn rust_builtins(&self) -> &[FunctionInfo] {
132        &self.rust_builtins
133    }
134
135    /// Get type properties by type name (case-insensitive)
136    pub fn get_type_properties(&self, type_name: &str) -> Option<&[PropertyInfo]> {
137        self.type_metadata
138            .iter()
139            .find(|t| t.name.eq_ignore_ascii_case(type_name))
140            .map(|t| t.properties.as_slice())
141    }
142
143    /// Get all type metadata
144    pub fn all_types(&self) -> &[TypeMetadataInfo] {
145        &self.type_metadata
146    }
147}
148
149#[cfg(test)]
150mod unified_metadata_tests {
151    use super::*;
152
153    #[test]
154    fn test_unified_metadata_load() {
155        let metadata = UnifiedMetadata::load();
156
157        // Should have some functions from Rust builtins
158        let all_funcs: Vec<_> = metadata.all_functions().into_iter().collect();
159        assert!(!all_funcs.is_empty(), "Should have functions");
160
161        // Check some core builtins exist
162        assert!(
163            metadata.get_function("abs").is_some(),
164            "abs should be present"
165        );
166        assert!(
167            metadata.get_function("sqrt").is_some(),
168            "sqrt should be present"
169        );
170
171        // snapshot() is defined in stdlib/core/snapshot.shape
172        assert!(
173            metadata.get_function("snapshot").is_some(),
174            "snapshot should be present from stdlib"
175        );
176    }
177
178    #[test]
179    fn test_metadata_coverage() {
180        let metadata = UnifiedMetadata::load();
181
182        println!("\n=== Metadata Coverage ===");
183        println!(
184            "Rust builtins (proc-macro): {}",
185            metadata.rust_builtins().len()
186        );
187        for f in metadata.rust_builtins() {
188            println!("  - {}", f.name);
189        }
190
191        println!("\nStdlib functions: {}", metadata.stdlib_functions().len());
192        assert!(
193            !metadata.stdlib_functions().is_empty(),
194            "stdlib function metadata should not be empty"
195        );
196
197        let all_funcs = metadata.all_functions();
198        println!("\nTotal functions available: {}", all_funcs.len());
199
200        // Core builtins that should be present from Rust
201        assert!(
202            metadata.get_function("abs").is_some(),
203            "abs should be present from builtins"
204        );
205        assert!(
206            metadata.get_function("sqrt").is_some(),
207            "sqrt should be present from builtins"
208        );
209        assert!(
210            metadata.get_function("snapshot").is_some(),
211            "snapshot should be present from stdlib"
212        );
213    }
214
215    #[test]
216    fn test_intrinsic_std_core_overrides_builtin_docs() {
217        let metadata = UnifiedMetadata::load();
218        let abs = metadata
219            .get_function("abs")
220            .expect("abs should be available in unified metadata");
221        assert_eq!(abs.signature, "abs(value: number) -> number");
222        assert!(
223            abs.description.contains("absolute value"),
224            "abs docs should be sourced from std::core intrinsic declarations"
225        );
226    }
227
228    #[test]
229    fn test_intrinsic_std_core_uses_table_signatures() {
230        let metadata = UnifiedMetadata::load();
231        let resample = metadata
232            .get_function("resample")
233            .expect("resample should be available in unified metadata");
234        assert!(
235            resample.signature.contains("Table<"),
236            "resample signature should use Table<T>, got: {}",
237            resample.signature
238        );
239    }
240}