shape_runtime/context/registries.rs
1//! Registry access methods for ExecutionContext
2//!
3//! Handles type methods, type schemas, pattern registries,
4//! and annotation lifecycle dispatch.
5
6use super::super::annotation_context::AnnotationContext;
7use super::super::type_methods::TypeMethodRegistry;
8use super::super::type_schema::TypeSchemaRegistry;
9use shape_ast::ast::{AnnotationDef, AnnotationHandlerType, FunctionDef};
10use shape_ast::error::{Result, ShapeError};
11use std::sync::Arc;
12
13impl super::ExecutionContext {
14 /// Register an annotation definition
15 ///
16 /// Annotation definitions are stored and used to dispatch lifecycle hooks
17 /// when functions with those annotations are registered.
18 pub fn register_annotation(&mut self, def: AnnotationDef) {
19 self.annotation_registry.register(def);
20 }
21
22 /// Get the annotation context (for lifecycle hooks)
23 pub fn annotation_context(&self) -> &AnnotationContext {
24 &self.annotation_context
25 }
26
27 /// Get mutable annotation context (for lifecycle hooks)
28 pub fn annotation_context_mut(&mut self) -> &mut AnnotationContext {
29 &mut self.annotation_context
30 }
31
32 /// Register a user-defined function
33 ///
34 /// This dispatches `on_define` lifecycle hooks for all annotations on the function.
35 pub fn register_function(&mut self, function: FunctionDef) {
36 self.dispatch_on_define_hooks(&function);
37 }
38
39 /// Dispatch on_define lifecycle hooks for all annotations on a function
40 ///
41 /// For each annotation on the function:
42 /// 1. Look up the annotation definition in the registry
43 /// 2. If it has an on_define handler, execute it
44 fn dispatch_on_define_hooks(&mut self, func: &FunctionDef) {
45 for annotation in &func.annotations {
46 // Look up the annotation definition
47 if let Some(ann_def) = self.annotation_registry.get(&annotation.name).cloned() {
48 // Find the on_define handler
49 for handler in &ann_def.handlers {
50 if handler.handler_type == AnnotationHandlerType::OnDefine {
51 // Execute the on_define handler
52 self.execute_on_define_handler(&ann_def, handler, func);
53 }
54 }
55 }
56 // If annotation definition not found, no hooks are executed
57 // Annotations must be defined to have behavior
58 }
59 }
60
61 /// Execute an on_define lifecycle handler
62 ///
63 /// The handler body is a Shape expression that will be evaluated
64 /// with `fn` and `ctx` bound to the function and annotation context.
65 /// Currently a stub until VM-based closure handling is implemented.
66 fn execute_on_define_handler(
67 &mut self,
68 _ann_def: &AnnotationDef,
69 _handler: &shape_ast::ast::AnnotationHandler,
70 _func: &FunctionDef,
71 ) {
72 self.sync_pattern_registry_from_annotation_context();
73 }
74
75 /// Sync pattern registry from annotation context
76 ///
77 /// When an annotation's on_define calls ctx.registry("patterns").set(...),
78 /// we need to copy those entries to the main pattern_registry for .find() lookup.
79 /// Currently a stub until VM-based closure handling is implemented.
80 fn sync_pattern_registry_from_annotation_context(&mut self) {}
81
82 /// Look up a pattern by name from the pattern registry
83 ///
84 /// Returns an error with a helpful message if the pattern is not found.
85 pub fn lookup_pattern(&self, name: &str) -> Result<&super::super::closure::Closure> {
86 self.pattern_registry
87 .get(name)
88 .ok_or_else(|| ShapeError::RuntimeError {
89 message: format!(
90 "Unknown pattern: '{}'. Did you register it in the pattern registry?",
91 name
92 ),
93 location: None,
94 })
95 }
96
97 /// Get the type method registry
98 pub fn type_method_registry(&self) -> &Arc<TypeMethodRegistry> {
99 &self.type_method_registry
100 }
101
102 /// Get the type schema registry for JIT type specialization
103 pub fn type_schema_registry(&self) -> &Arc<TypeSchemaRegistry> {
104 &self.type_schema_registry
105 }
106
107 /// Merge additional type schemas into the context's registry.
108 /// Used after compilation to make inline object schemas available for wire serialization.
109 pub fn merge_type_schemas(&mut self, other: TypeSchemaRegistry) {
110 Arc::make_mut(&mut self.type_schema_registry).merge(other);
111 }
112
113 // =========================================================================
114 // Enum Registry Methods (for sum types)
115 // =========================================================================
116
117 /// Register an enum definition
118 ///
119 /// This enables sum type support by tracking which enums exist and their variants.
120 /// Called during semantic analysis when processing `enum` declarations.
121 pub fn register_enum(&mut self, enum_def: shape_ast::ast::EnumDef) {
122 self.enum_registry.register(enum_def);
123 }
124
125 /// Look up an enum definition by name
126 pub fn lookup_enum(&self, name: &str) -> Option<&shape_ast::ast::EnumDef> {
127 self.enum_registry.get(name)
128 }
129
130 /// Check if an enum exists
131 pub fn has_enum(&self, name: &str) -> bool {
132 self.enum_registry.contains(name)
133 }
134
135 /// Get the enum registry (for advanced queries)
136 pub fn enum_registry(&self) -> &super::EnumRegistry {
137 &self.enum_registry
138 }
139
140 // =========================================================================
141 // Struct Type Registry Methods (for REPL persistence)
142 // =========================================================================
143
144 /// Register a struct type definition for REPL persistence
145 ///
146 /// This stores the full StructTypeDef so that type definitions survive across
147 /// REPL sessions. When a new REPL command is compiled, previously registered
148 /// struct types are injected into the program so the compiler can see them.
149 pub fn register_struct_type(&mut self, struct_def: shape_ast::ast::StructTypeDef) {
150 self.struct_type_registry
151 .insert(struct_def.name.clone(), struct_def);
152 }
153
154 /// Get all registered struct type definitions
155 ///
156 /// Returns an iterator over all struct type definitions registered in previous
157 /// REPL sessions. Used to inject them into the program before compilation.
158 pub fn struct_type_defs(
159 &self,
160 ) -> &std::collections::HashMap<String, shape_ast::ast::StructTypeDef> {
161 &self.struct_type_registry
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::super::ExecutionContext;
168 use shape_ast::ast::{
169 Annotation, AnnotationDef, AnnotationHandler, AnnotationHandlerType, Expr, FunctionDef,
170 Span,
171 };
172
173 #[test]
174 fn test_register_annotation_definition() {
175 let mut ctx = ExecutionContext::new_empty();
176
177 // Create a simple annotation definition (no handlers for this test)
178 let ann_def = AnnotationDef {
179 name: "test_annotation".to_string(),
180 name_span: Span::DUMMY,
181 doc_comment: None,
182 params: vec![],
183 allowed_targets: None,
184 handlers: vec![],
185 span: Span::DUMMY,
186 };
187
188 ctx.register_annotation(ann_def);
189
190 // Verify the annotation is registered
191 assert!(ctx.annotation_registry.contains("test_annotation"));
192 }
193
194 #[test]
195 fn test_function_without_annotations_registers_normally() {
196 let mut ctx = ExecutionContext::new_empty();
197
198 let func = FunctionDef {
199 name: "my_func".to_string(),
200 name_span: Span::DUMMY,
201 declaring_module_path: None,
202 doc_comment: None,
203 type_params: None,
204 params: vec![],
205 return_type: None,
206 body: vec![],
207 annotations: vec![],
208 where_clause: None,
209 is_async: false,
210 is_comptime: false,
211 };
212
213 ctx.register_function(func);
214
215 // TODO: Update test when BytecodeExecutor/VM integration is complete
216 // Function should be registered in evaluator
217 // assert!(ctx.evaluator().get_function("my_func").is_some());
218 }
219
220 #[test]
221 fn test_function_with_undefined_annotation_no_crash() {
222 let mut ctx = ExecutionContext::new_empty();
223
224 // Function has @undefined annotation but no annotation definition registered
225 let func = FunctionDef {
226 name: "annotated_func".to_string(),
227 name_span: Span::DUMMY,
228 declaring_module_path: None,
229 doc_comment: None,
230 type_params: None,
231 params: vec![],
232 return_type: None,
233 body: vec![],
234 annotations: vec![Annotation {
235 name: "undefined".to_string(),
236 args: vec![],
237 span: Span::DUMMY,
238 }],
239 where_clause: None,
240 is_async: false,
241 is_comptime: false,
242 };
243
244 // Should not crash - undefined annotations are silently ignored
245 ctx.register_function(func);
246
247 // TODO: Update test when BytecodeExecutor/VM integration is complete
248 // Function should still be registered
249 // assert!(ctx.evaluator().get_function("annotated_func").is_some());
250 }
251
252 #[test]
253 fn test_annotation_with_on_define_handler_is_called() {
254 let mut ctx = ExecutionContext::new_empty();
255
256 // Create an annotation definition with on_define handler
257 // The handler body sets a value in the annotation context state
258 // For simplicity, we use a literal expression that doesn't require complex evaluation
259 let ann_def = AnnotationDef {
260 name: "tracked".to_string(),
261 name_span: Span::DUMMY,
262 doc_comment: None,
263 params: vec![],
264 allowed_targets: None,
265 handlers: vec![AnnotationHandler {
266 handler_type: AnnotationHandlerType::OnDefine,
267 params: vec![shape_ast::ast::AnnotationHandlerParam {
268 name: "fn".to_string(),
269 is_variadic: false,
270 }],
271 return_type: None,
272 // Simple expression that just returns the fn parameter
273 // This tests that the handler is called and fn is bound
274 body: Expr::Identifier("fn".to_string(), Span::DUMMY),
275 span: Span::DUMMY,
276 }],
277 span: Span::DUMMY,
278 };
279
280 ctx.register_annotation(ann_def);
281
282 // Create a function with @tracked annotation
283 let func = FunctionDef {
284 name: "tracked_func".to_string(),
285 name_span: Span::DUMMY,
286 declaring_module_path: None,
287 doc_comment: None,
288 type_params: None,
289 params: vec![],
290 return_type: None,
291 body: vec![],
292 annotations: vec![Annotation {
293 name: "tracked".to_string(),
294 args: vec![],
295 span: Span::DUMMY,
296 }],
297 where_clause: None,
298 is_async: false,
299 is_comptime: false,
300 };
301
302 // Register function - this should trigger on_define handler
303 ctx.register_function(func);
304
305 // TODO: Update test when BytecodeExecutor/VM integration is complete
306 // Function should be registered
307 // assert!(ctx.evaluator().get_function("tracked_func").is_some());
308 }
309
310 #[test]
311 fn test_lookup_pattern_not_found() {
312 let ctx = ExecutionContext::new_empty();
313
314 let result = ctx.lookup_pattern("nonexistent");
315 assert!(result.is_err());
316
317 let err = result.unwrap_err();
318 let msg = format!("{}", err);
319 assert!(msg.contains("Unknown pattern"));
320 assert!(msg.contains("nonexistent"));
321 }
322
323 #[test]
324 fn test_annotation_context_registry_access() {
325 let mut ctx = ExecutionContext::new_empty();
326
327 // Access and modify the annotation context registry
328 {
329 let registry = ctx.annotation_context_mut().registry("test_registry");
330 registry.set(
331 "key1".to_string(),
332 shape_value::ValueWord::from_string(std::sync::Arc::new("value1".to_string())),
333 );
334 }
335
336 // Verify the value is stored
337 let registry = ctx.annotation_context_mut().registry("test_registry");
338 assert!(registry.get("key1").is_some());
339 }
340}