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