sigil_parser/
lower.rs

1//! AST to IR Lowering Pass
2//!
3//! Converts the parsed AST into the AI-facing IR format.
4//! This pass:
5//! - Generates unique IDs for all definitions
6//! - Infers types where needed (or uses placeholders)
7//! - Propagates evidentiality through expressions
8//! - Normalizes pipeline operations
9
10use crate::ast::{self, SourceFile};
11use crate::ir::*;
12use std::collections::HashMap;
13
14/// Counter for generating unique IDs
15struct IdGenerator {
16    counters: HashMap<String, usize>,
17}
18
19impl IdGenerator {
20    fn new() -> Self {
21        Self {
22            counters: HashMap::new(),
23        }
24    }
25
26    fn next(&mut self, prefix: &str) -> String {
27        let count = self.counters.entry(prefix.to_string()).or_insert(0);
28        *count += 1;
29        format!("{}_{:03}", prefix, count)
30    }
31}
32
33/// Lowering context
34pub struct LoweringContext {
35    ids: IdGenerator,
36    /// Current scope for variable resolution
37    scope: Vec<HashMap<String, String>>,
38    /// Inferred types for expressions (placeholder for now)
39    type_cache: HashMap<String, IrType>,
40}
41
42impl LoweringContext {
43    pub fn new() -> Self {
44        Self {
45            ids: IdGenerator::new(),
46            scope: vec![HashMap::new()],
47            type_cache: HashMap::new(),
48        }
49    }
50
51    fn push_scope(&mut self) {
52        self.scope.push(HashMap::new());
53    }
54
55    fn pop_scope(&mut self) {
56        self.scope.pop();
57    }
58
59    fn bind_var(&mut self, name: &str) -> String {
60        let id = self.ids.next("var");
61        if let Some(scope) = self.scope.last_mut() {
62            scope.insert(name.to_string(), id.clone());
63        }
64        id
65    }
66
67    fn lookup_var(&self, name: &str) -> Option<String> {
68        for scope in self.scope.iter().rev() {
69            if let Some(id) = scope.get(name) {
70                return Some(id.clone());
71            }
72        }
73        None
74    }
75}
76
77impl Default for LoweringContext {
78    fn default() -> Self {
79        Self::new()
80    }
81}
82
83/// Lower a complete source file to IR
84pub fn lower_source_file(source: &str, ast: &SourceFile) -> IrModule {
85    let mut ctx = LoweringContext::new();
86    let mut module = IrModule::new(source.to_string());
87
88    for item in &ast.items {
89        lower_item(&mut ctx, &mut module, &item.node);
90    }
91
92    module
93}
94
95
96/// Collect free variables from an AST expression
97/// Returns variable names that are used but not bound in the expression
98fn collect_free_variables(expr: &ast::Expr, bound: &mut std::collections::HashSet<String>) -> Vec<String> {
99    let mut free = Vec::new();
100    collect_free_vars_inner(expr, bound, &mut free);
101    free
102}
103
104fn collect_free_vars_inner(
105    expr: &ast::Expr,
106    bound: &std::collections::HashSet<String>,
107    free: &mut Vec<String>,
108) {
109    match expr {
110        ast::Expr::Path(type_path) => {
111            // Check if this is a simple identifier (single segment path)
112            if type_path.segments.len() == 1 && type_path.segments[0].generics.is_none() {
113                let name = &type_path.segments[0].ident.name;
114                if !bound.contains(name) && !free.contains(name) {
115                    free.push(name.clone());
116                }
117            }
118        }
119        ast::Expr::Binary { left, right, .. } => {
120            collect_free_vars_inner(left, bound, free);
121            collect_free_vars_inner(right, bound, free);
122        }
123        ast::Expr::Unary { expr, .. } => {
124            collect_free_vars_inner(expr, bound, free);
125        }
126        ast::Expr::Call { func, args, .. } => {
127            collect_free_vars_inner(func, bound, free);
128            for arg in args {
129                collect_free_vars_inner(arg, bound, free);
130            }
131        }
132        ast::Expr::MethodCall { receiver, args, .. } => {
133            collect_free_vars_inner(receiver, bound, free);
134            for arg in args {
135                collect_free_vars_inner(arg, bound, free);
136            }
137        }
138        ast::Expr::Field { expr, .. } => {
139            collect_free_vars_inner(expr, bound, free);
140        }
141        ast::Expr::Index { expr, index, .. } => {
142            collect_free_vars_inner(expr, bound, free);
143            collect_free_vars_inner(index, bound, free);
144        }
145        ast::Expr::Block(block) => {
146            collect_free_vars_block(block, bound, free);
147        }
148        ast::Expr::If { condition, then_branch, else_branch, .. } => {
149            collect_free_vars_inner(condition, bound, free);
150            collect_free_vars_block(then_branch, bound, free);
151            if let Some(else_expr) = else_branch {
152                collect_free_vars_inner(else_expr, bound, free);
153            }
154        }
155        ast::Expr::Match { expr, arms, .. } => {
156            collect_free_vars_inner(expr, bound, free);
157            for arm in arms {
158                // Each arm pattern binds variables
159                let mut arm_bound = bound.clone();
160                collect_pattern_bindings(&arm.pattern, &mut arm_bound);
161                if let Some(guard) = &arm.guard {
162                    collect_free_vars_inner(guard, &arm_bound, free);
163                }
164                collect_free_vars_inner(&arm.body, &arm_bound, free);
165            }
166        }
167        ast::Expr::Closure { params, body, .. } => {
168            // Closure parameters are bound inside the closure
169            let mut closure_bound = bound.clone();
170            for param in params {
171                if let Some(name) = extract_pattern_name_opt(&param.pattern) {
172                    closure_bound.insert(name);
173                }
174            }
175            collect_free_vars_inner(body, &closure_bound, free);
176        }
177        ast::Expr::Tuple(elements) | ast::Expr::Array(elements) => {
178            for elem in elements {
179                collect_free_vars_inner(elem, bound, free);
180            }
181        }
182        ast::Expr::Struct { fields, rest, .. } => {
183            for field in fields {
184                if let Some(v) = &field.value { collect_free_vars_inner(v, bound, free); } else { let name = &field.name.name; if !bound.contains(name) && !free.contains(name) { free.push(name.clone()); } }
185            }
186            if let Some(r) = rest {
187                collect_free_vars_inner(r, bound, free);
188            }
189        }
190        ast::Expr::Range { start, end, .. } => {
191            if let Some(s) = start {
192                collect_free_vars_inner(s, bound, free);
193            }
194            if let Some(e) = end {
195                collect_free_vars_inner(e, bound, free);
196            }
197        }
198        ast::Expr::Return(Some(expr)) | ast::Expr::Try(expr) | ast::Expr::Deref(expr) => {
199            collect_free_vars_inner(expr, bound, free);
200        }
201        ast::Expr::Cast { expr, .. } => {
202            collect_free_vars_inner(expr, bound, free);
203        }
204        ast::Expr::Assign { target, value, .. } => {
205            collect_free_vars_inner(target, bound, free);
206            collect_free_vars_inner(value, bound, free);
207        }
208        ast::Expr::AddrOf { expr, .. } => {
209            collect_free_vars_inner(expr, bound, free);
210        }
211        ast::Expr::While { condition, body, .. } => {
212            collect_free_vars_inner(condition, bound, free);
213            collect_free_vars_block(body, bound, free);
214        }
215        ast::Expr::For { pattern, iter, body, .. } => {
216            collect_free_vars_inner(iter, bound, free);
217            let mut for_bound = bound.clone();
218            collect_pattern_bindings(pattern, &mut for_bound);
219            collect_free_vars_block(body, &for_bound, free);
220        }
221        ast::Expr::Loop { body: block, .. } | ast::Expr::Unsafe(block) | ast::Expr::Async { block, .. } => {
222            collect_free_vars_block(block, bound, free);
223        }
224        ast::Expr::Await { expr, .. } => {
225            collect_free_vars_inner(expr, bound, free);
226        }
227        ast::Expr::Evidential { expr, .. } => {
228            collect_free_vars_inner(expr, bound, free);
229        }
230        ast::Expr::Let { value, .. } => {
231            collect_free_vars_inner(value, bound, free);
232        }
233        ast::Expr::Break { value: Some(expr), .. } => {
234            collect_free_vars_inner(expr, bound, free);
235        }
236        // Literals and other leaf expressions have no free variables
237        ast::Expr::Literal(_)
238        | ast::Expr::Return(None)
239        | ast::Expr::Continue { .. }
240        | ast::Expr::Break { value: None, .. } => {}
241        // Skip complex expressions we don't need to analyze for self capture
242        _ => {}
243    }
244}
245
246fn collect_free_vars_block(
247    block: &ast::Block,
248    bound: &std::collections::HashSet<String>,
249    free: &mut Vec<String>,
250) {
251    let mut block_bound = bound.clone();
252    for stmt in &block.stmts {
253        collect_free_vars_stmt(stmt, &mut block_bound, free);
254    }
255    if let Some(expr) = &block.expr {
256        collect_free_vars_inner(expr, &block_bound, free);
257    }
258}
259
260fn collect_free_vars_stmt(
261    stmt: &ast::Stmt,
262    bound: &mut std::collections::HashSet<String>,
263    free: &mut Vec<String>,
264) {
265    match stmt {
266        ast::Stmt::Let { pattern, init, .. } => {
267            // First collect from the init value (before binding)
268            if let Some(v) = init {
269                collect_free_vars_inner(v, bound, free);
270            }
271            // Then bind the pattern variables
272            collect_pattern_bindings(pattern, bound);
273        }
274        ast::Stmt::LetElse { pattern, init, else_branch, .. } => {
275            collect_free_vars_inner(init, bound, free);
276            collect_free_vars_inner(else_branch, bound, free);
277            collect_pattern_bindings(pattern, bound);
278        }
279        ast::Stmt::Expr(expr) | ast::Stmt::Semi(expr) => {
280            collect_free_vars_inner(expr, bound, free);
281        }
282        ast::Stmt::Item(_) => {}
283    }
284}
285
286fn collect_pattern_bindings(pattern: &ast::Pattern, bound: &mut std::collections::HashSet<String>) {
287    match pattern {
288        ast::Pattern::Ident { name, .. } => {
289            bound.insert(name.name.clone());
290        }
291        ast::Pattern::Tuple(patterns) => {
292            for p in patterns {
293                collect_pattern_bindings(p, bound);
294            }
295        }
296        ast::Pattern::Struct { fields, .. } => {
297            for field in fields {
298                if let Some(p) = &field.pattern {
299                    collect_pattern_bindings(p, bound);
300                } else {
301                    // Shorthand: `Foo { x }` binds `x`
302                    bound.insert(field.name.name.clone());
303                }
304            }
305        }
306        ast::Pattern::TupleStruct { fields, .. } => {
307            for p in fields {
308                collect_pattern_bindings(p, bound);
309            }
310        }
311        ast::Pattern::Or(patterns) => {
312            // For or-patterns, all branches should bind the same names
313            for p in patterns {
314                collect_pattern_bindings(p, bound);
315            }
316        }
317        ast::Pattern::Slice(patterns) => {
318            for p in patterns {
319                collect_pattern_bindings(p, bound);
320            }
321        }
322        _ => {}
323    }
324}
325
326fn extract_pattern_name_opt(pattern: &ast::Pattern) -> Option<String> {
327    match pattern {
328        ast::Pattern::Ident { name, .. } => Some(name.name.clone()),
329        _ => None,
330    }
331}
332
333fn lower_item(ctx: &mut LoweringContext, module: &mut IrModule, item: &ast::Item) {
334    match item {
335        ast::Item::Function(f) => {
336            if let Some(ir_fn) = lower_function(ctx, f) {
337                module.functions.push(ir_fn);
338            }
339        }
340        ast::Item::Struct(s) => {
341            module.types.push(lower_struct_def(ctx, s));
342        }
343        ast::Item::Enum(e) => {
344            module.types.push(lower_enum_def(ctx, e));
345        }
346        ast::Item::Trait(t) => {
347            module.traits.push(lower_trait_def(ctx, t));
348        }
349        ast::Item::Impl(i) => {
350            module.impls.push(lower_impl_block(ctx, i));
351        }
352        ast::Item::TypeAlias(t) => {
353            module.types.push(lower_type_alias(ctx, t));
354        }
355        ast::Item::Const(c) => {
356            module.constants.push(lower_const_def(ctx, c));
357        }
358        ast::Item::Module(m) => {
359            if let Some(items) = &m.items {
360                for item in items {
361                    lower_item(ctx, module, &item.node);
362                }
363            }
364        }
365        ast::Item::Static(_)
366        | ast::Item::Actor(_)
367        | ast::Item::Use(_)
368        | ast::Item::ExternBlock(_)
369        | ast::Item::Macro(_)
370        | ast::Item::MacroInvocation(_)
371        | ast::Item::Plurality(_) => {
372            // TODO: Handle these items (plurality lowering handled separately)
373        }
374    }
375}
376
377fn lower_function(ctx: &mut LoweringContext, f: &ast::Function) -> Option<IrFunction> {
378    let id = ctx.ids.next("fn");
379    ctx.push_scope();
380
381    let params: Vec<IrParam> = f.params.iter().map(|p| lower_param(ctx, p)).collect();
382
383    let return_type = f
384        .return_type
385        .as_ref()
386        .map(|t| lower_type_expr(t))
387        .unwrap_or(IrType::Unit);
388
389    let body = f.body.as_ref().map(|b| lower_block(ctx, b));
390
391    let mut attributes = Vec::new();
392    if f.is_async {
393        attributes.push("async".to_string());
394    }
395    if f.attrs.inline.is_some() {
396        attributes.push("inline".to_string());
397    }
398    if f.attrs.naked {
399        attributes.push("naked".to_string());
400    }
401    if f.attrs.no_mangle {
402        attributes.push("no_mangle".to_string());
403    }
404
405    ctx.pop_scope();
406
407    Some(IrFunction {
408        name: f.name.name.clone(),
409        id,
410        visibility: lower_visibility(f.visibility),
411        generics: lower_generics(&f.generics),
412        params,
413        return_type,
414        body,
415        attributes,
416        is_async: f.is_async,
417        span: None,
418    })
419}
420
421fn lower_param(ctx: &mut LoweringContext, p: &ast::Param) -> IrParam {
422    let name = extract_pattern_name(&p.pattern);
423    let evidence = extract_pattern_evidence(&p.pattern);
424    ctx.bind_var(&name);
425
426    IrParam {
427        name,
428        ty: lower_type_expr(&p.ty),
429        evidence,
430    }
431}
432
433fn extract_pattern_name(p: &ast::Pattern) -> String {
434    match p {
435        ast::Pattern::Ident { name, .. } => name.name.clone(),
436        ast::Pattern::Tuple(pats) if !pats.is_empty() => extract_pattern_name(&pats[0]),
437        _ => "_".to_string(),
438    }
439}
440
441fn extract_pattern_evidence(p: &ast::Pattern) -> IrEvidence {
442    match p {
443        ast::Pattern::Ident { evidentiality, .. } => evidentiality
444            .map(lower_evidentiality)
445            .unwrap_or(IrEvidence::Known),
446        _ => IrEvidence::Known,
447    }
448}
449
450fn lower_visibility(v: ast::Visibility) -> IrVisibility {
451    match v {
452        ast::Visibility::Public => IrVisibility::Public,
453        ast::Visibility::Private => IrVisibility::Private,
454        ast::Visibility::Crate => IrVisibility::Crate,
455        ast::Visibility::Super => IrVisibility::Private,
456    }
457}
458
459fn lower_evidentiality(e: ast::Evidentiality) -> IrEvidence {
460    match e {
461        ast::Evidentiality::Known => IrEvidence::Known,
462        ast::Evidentiality::Uncertain | ast::Evidentiality::Predicted => IrEvidence::Uncertain,
463        ast::Evidentiality::Reported => IrEvidence::Reported,
464        ast::Evidentiality::Paradox => IrEvidence::Paradox,
465    }
466}
467
468fn lower_generics(g: &Option<ast::Generics>) -> Vec<IrGenericParam> {
469    g.as_ref()
470        .map(|g| {
471            g.params
472                .iter()
473                .filter_map(|p| match p {
474                    ast::GenericParam::Type { name, bounds, .. } => Some(IrGenericParam {
475                        name: name.name.clone(),
476                        bounds: bounds.iter().map(type_expr_to_string).collect(),
477                    }),
478                    ast::GenericParam::Const { name, .. } => Some(IrGenericParam {
479                        name: name.name.clone(),
480                        bounds: vec!["const".to_string()],
481                    }),
482                    ast::GenericParam::Lifetime(l) => Some(IrGenericParam {
483                        name: l.clone(),
484                        bounds: vec!["lifetime".to_string()],
485                    }),
486                })
487                .collect()
488        })
489        .unwrap_or_default()
490}
491
492fn type_expr_to_string(t: &ast::TypeExpr) -> String {
493    match t {
494        ast::TypeExpr::Path(p) => p
495            .segments
496            .iter()
497            .map(|s| s.ident.name.clone())
498            .collect::<Vec<_>>()
499            .join("::"),
500        _ => "?".to_string(),
501    }
502}
503
504fn lower_type_expr(t: &ast::TypeExpr) -> IrType {
505    match t {
506        ast::TypeExpr::Path(p) => {
507            let name = p
508                .segments
509                .iter()
510                .map(|s| s.ident.name.clone())
511                .collect::<Vec<_>>()
512                .join("::");
513
514            // Check for primitive types
515            match name.as_str() {
516                "i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" | "u16" | "u32" | "u64"
517                | "u128" | "usize" | "f32" | "f64" | "bool" | "char" | "str" => {
518                    IrType::Primitive { name }
519                }
520                "()" => IrType::Unit,
521                "!" | "never" => IrType::Never,
522                _ => {
523                    let generics = p
524                        .segments
525                        .last()
526                        .and_then(|s| s.generics.as_ref())
527                        .map(|gs| gs.iter().map(lower_type_expr).collect())
528                        .unwrap_or_default();
529
530                    IrType::Named { name, generics }
531                }
532            }
533        }
534        ast::TypeExpr::Reference { lifetime, mutable, inner } => IrType::Reference {
535            lifetime: lifetime.clone(),
536            mutable: *mutable,
537            inner: Box::new(lower_type_expr(inner)),
538        },
539        ast::TypeExpr::Pointer { mutable, inner } => IrType::Pointer {
540            mutable: *mutable,
541            inner: Box::new(lower_type_expr(inner)),
542        },
543        ast::TypeExpr::Array { element, size: _ } => IrType::Array {
544            element: Box::new(lower_type_expr(element)),
545            size: None, // Would need const eval
546        },
547        ast::TypeExpr::Slice(inner) => IrType::Slice {
548            element: Box::new(lower_type_expr(inner)),
549        },
550        ast::TypeExpr::Tuple(elements) => IrType::Tuple {
551            elements: elements.iter().map(lower_type_expr).collect(),
552        },
553        ast::TypeExpr::Function {
554            params,
555            return_type,
556        } => IrType::Function {
557            params: params.iter().map(lower_type_expr).collect(),
558            return_type: Box::new(
559                return_type
560                    .as_ref()
561                    .map(|r| lower_type_expr(r))
562                    .unwrap_or(IrType::Unit),
563            ),
564            is_async: false,
565        },
566        ast::TypeExpr::Evidential {
567            inner,
568            evidentiality,
569            error_type,
570        } => {
571            // If error_type is specified (e.g., T?[Error]), this is Result sugar
572            // For now, we lower it as an evidential type; full Result expansion would happen later
573            let _ = error_type; // TODO: expand to Result<T, E> in type system
574            IrType::Evidential {
575                inner: Box::new(lower_type_expr(inner)),
576                evidence: lower_evidentiality(*evidentiality),
577            }
578        }
579        ast::TypeExpr::Cycle { .. } => IrType::Cycle { modulus: 0 },
580        ast::TypeExpr::Simd { element, lanes } => IrType::Simd {
581            element: Box::new(lower_type_expr(element)),
582            lanes: *lanes as usize,
583        },
584        ast::TypeExpr::Atomic(inner) => IrType::Atomic {
585            inner: Box::new(lower_type_expr(inner)),
586        },
587        ast::TypeExpr::Never => IrType::Never,
588        ast::TypeExpr::Infer => IrType::Infer,
589        ast::TypeExpr::Lifetime(name) => IrType::Lifetime { name: name.clone() },
590        ast::TypeExpr::TraitObject(bounds) => IrType::TraitObject {
591            bounds: bounds.iter().map(lower_type_expr).collect(),
592        },
593        ast::TypeExpr::Hrtb { lifetimes, bound } => IrType::Hrtb {
594            lifetimes: lifetimes.clone(),
595            bound: Box::new(lower_type_expr(bound)),
596        },
597        ast::TypeExpr::InlineStruct { fields } => IrType::InlineStruct {
598            fields: fields
599                .iter()
600                .map(|f| (f.name.name.clone(), lower_type_expr(&f.ty)))
601                .collect(),
602        },
603        ast::TypeExpr::ImplTrait(bounds) => IrType::ImplTrait {
604            bounds: bounds.iter().map(lower_type_expr).collect(),
605        },
606        ast::TypeExpr::InlineEnum { variants } => IrType::InlineEnum {
607            variants: variants.iter().map(|v| v.name.name.clone()).collect(),
608        },
609        ast::TypeExpr::AssocTypeBinding { name, ty } => IrType::AssocTypeBinding {
610            name: name.name.clone(),
611            ty: Box::new(lower_type_expr(ty)),
612        },
613        ast::TypeExpr::ConstExpr(_) => {
614            // Const expressions in type position are evaluated at compile time
615            // For now, lower as an inferred type (const eval happens later)
616            IrType::Infer
617        }
618        ast::TypeExpr::QualifiedPath { self_type, trait_path, item_path } => {
619            // Lower qualified path: <Type as Trait>::AssociatedType
620            // For now, represent as a named type with the full path
621            let trait_part = trait_path.as_ref()
622                .map(|tp| tp.segments.iter().map(|s| s.ident.name.clone()).collect::<Vec<_>>().join("::"))
623                .unwrap_or_default();
624            let item_part = item_path.segments.iter().map(|s| s.ident.name.clone()).collect::<Vec<_>>().join("::");
625            let name = if trait_part.is_empty() {
626                format!("<_>::{}", item_part)
627            } else {
628                format!("<_ as {}>::{}", trait_part, item_part)
629            };
630            IrType::Named {
631                name,
632                generics: vec![lower_type_expr(self_type)],
633            }
634        }
635    }
636}
637
638fn lower_block(ctx: &mut LoweringContext, block: &ast::Block) -> IrOperation {
639    ctx.push_scope();
640
641    let mut statements: Vec<IrOperation> = block
642        .stmts
643        .iter()
644        .filter_map(|s| lower_stmt(ctx, s))
645        .collect();
646
647    if let Some(expr) = &block.expr {
648        statements.push(lower_expr(ctx, expr));
649    }
650
651    ctx.pop_scope();
652
653    IrOperation::Block {
654        statements,
655        ty: IrType::Infer,
656        evidence: IrEvidence::Known,
657    }
658}
659
660fn lower_stmt(ctx: &mut LoweringContext, stmt: &ast::Stmt) -> Option<IrOperation> {
661    match stmt {
662        ast::Stmt::Let { pattern, ty, init } => {
663            let ir_pattern = lower_pattern(ctx, pattern);
664            let type_annotation = ty.as_ref().map(lower_type_expr);
665            let init_expr = init
666                .as_ref()
667                .map(|e| Box::new(lower_expr(ctx, e)))
668                .unwrap_or_else(|| {
669                    Box::new(IrOperation::Literal {
670                        variant: LiteralVariant::Null,
671                        value: serde_json::Value::Null,
672                        ty: IrType::Unit,
673                        evidence: IrEvidence::Known,
674                    })
675                });
676
677            Some(IrOperation::Let {
678                pattern: ir_pattern,
679                type_annotation,
680                init: init_expr,
681                evidence: IrEvidence::Known,
682            })
683        }
684        ast::Stmt::LetElse { pattern, ty, init, else_branch } => {
685            // LetElse: let PATTERN = EXPR else { ... }
686            // Lower as: let pattern = init; with conditional else handling
687            let ir_pattern = lower_pattern(ctx, pattern);
688            let type_annotation = ty.as_ref().map(lower_type_expr);
689            let init_expr = Box::new(lower_expr(ctx, init));
690            // TODO: Properly handle else_branch in IR
691            Some(IrOperation::Let {
692                pattern: ir_pattern,
693                type_annotation,
694                init: init_expr,
695                evidence: IrEvidence::Known,
696            })
697        }
698        ast::Stmt::Expr(e) => Some(lower_expr(ctx, e)),
699        ast::Stmt::Semi(e) => Some(lower_expr(ctx, e)),
700        ast::Stmt::Item(_) => None, // Handled at module level
701    }
702}
703
704fn lower_pattern(ctx: &mut LoweringContext, pattern: &ast::Pattern) -> IrPattern {
705    match pattern {
706        ast::Pattern::Ident {
707            mutable,
708            name,
709            evidentiality,
710        } => {
711            ctx.bind_var(&name.name);
712            IrPattern::Ident {
713                name: name.name.clone(),
714                mutable: *mutable,
715                evidence: evidentiality.map(lower_evidentiality),
716            }
717        }
718        ast::Pattern::Tuple(pats) => IrPattern::Tuple {
719            elements: pats.iter().map(|p| lower_pattern(ctx, p)).collect(),
720        },
721        ast::Pattern::Struct { path, fields, rest } => IrPattern::Struct {
722            path: path
723                .segments
724                .iter()
725                .map(|s| s.ident.name.clone())
726                .collect::<Vec<_>>()
727                .join("::"),
728            fields: fields
729                .iter()
730                .map(|f| {
731                    (
732                        f.name.name.clone(),
733                        f.pattern.as_ref().map(|p| lower_pattern(ctx, p)).unwrap_or(
734                            IrPattern::Ident {
735                                name: f.name.name.clone(),
736                                mutable: false,
737                                evidence: None,
738                            },
739                        ),
740                    )
741                })
742                .collect(),
743            rest: *rest,
744        },
745        ast::Pattern::TupleStruct { path, fields } => IrPattern::TupleStruct {
746            path: path
747                .segments
748                .iter()
749                .map(|s| s.ident.name.clone())
750                .collect::<Vec<_>>()
751                .join("::"),
752            fields: fields.iter().map(|p| lower_pattern(ctx, p)).collect(),
753        },
754        ast::Pattern::Slice(pats) => IrPattern::Slice {
755            elements: pats.iter().map(|p| lower_pattern(ctx, p)).collect(),
756        },
757        ast::Pattern::Or(pats) => IrPattern::Or {
758            patterns: pats.iter().map(|p| lower_pattern(ctx, p)).collect(),
759        },
760        ast::Pattern::Literal(lit) => IrPattern::Literal {
761            value: lower_literal_value(lit),
762        },
763        ast::Pattern::Range {
764            start,
765            end,
766            inclusive,
767        } => IrPattern::Range {
768            start: start.as_ref().map(|p| Box::new(lower_pattern(ctx, p))),
769            end: end.as_ref().map(|p| Box::new(lower_pattern(ctx, p))),
770            inclusive: *inclusive,
771        },
772        ast::Pattern::Wildcard | ast::Pattern::Rest => IrPattern::Wildcard,
773        ast::Pattern::Ref { mutable: _, pattern } => {
774            // Reference patterns - just lower the inner pattern
775            lower_pattern(ctx, pattern)
776        }
777        ast::Pattern::RefBinding {
778            mutable,
779            name,
780            evidentiality,
781        } => {
782            // Ref binding - bind by reference, lower as identifier
783            IrPattern::Ident {
784                name: name.name.clone(),
785                mutable: *mutable,
786                evidence: evidentiality.map(lower_evidentiality),
787            }
788        }
789        ast::Pattern::Path(path) => {
790            // Path pattern (unit variant matching) - use TupleStruct with empty fields
791            let name = path
792                .segments
793                .iter()
794                .map(|s| s.ident.name.clone())
795                .collect::<Vec<_>>()
796                .join("::");
797            IrPattern::TupleStruct {
798                path: name,
799                fields: vec![],
800            }
801        }
802    }
803}
804
805fn lower_expr(ctx: &mut LoweringContext, expr: &ast::Expr) -> IrOperation {
806    match expr {
807        ast::Expr::Literal(lit) => lower_literal(lit),
808
809        ast::Expr::Path(path) => {
810            let name = path
811                .segments
812                .iter()
813                .map(|s| s.ident.name.clone())
814                .collect::<Vec<_>>()
815                .join("::");
816            let id = ctx.lookup_var(&name).unwrap_or_else(|| name.clone());
817
818            IrOperation::Var {
819                name,
820                id,
821                ty: IrType::Infer,
822                evidence: IrEvidence::Known,
823            }
824        }
825
826        ast::Expr::Binary { left, op, right } => {
827            let left_ir = lower_expr(ctx, left);
828            let right_ir = lower_expr(ctx, right);
829            let left_ev = get_operation_evidence(&left_ir);
830            let right_ev = get_operation_evidence(&right_ir);
831
832            IrOperation::Binary {
833                operator: lower_binop(*op),
834                left: Box::new(left_ir),
835                right: Box::new(right_ir),
836                ty: IrType::Infer,
837                evidence: left_ev.join(right_ev),
838            }
839        }
840
841        ast::Expr::Unary { op, expr: inner } => {
842            let inner_ir = lower_expr(ctx, inner);
843            let evidence = get_operation_evidence(&inner_ir);
844
845            IrOperation::Unary {
846                operator: lower_unaryop(*op),
847                operand: Box::new(inner_ir),
848                ty: IrType::Infer,
849                evidence,
850            }
851        }
852
853        ast::Expr::Call { func, args } => {
854            let func_name = match func.as_ref() {
855                ast::Expr::Path(p) => p
856                    .segments
857                    .iter()
858                    .map(|s| s.ident.name.clone())
859                    .collect::<Vec<_>>()
860                    .join("::"),
861                _ => "anonymous".to_string(),
862            };
863
864            let args_ir: Vec<IrOperation> = args.iter().map(|a| lower_expr(ctx, a)).collect();
865            let evidence = args_ir
866                .iter()
867                .map(get_operation_evidence)
868                .fold(IrEvidence::Known, |acc, e| acc.join(e));
869
870            IrOperation::Call {
871                function: func_name.clone(),
872                function_id: format!("fn_{}", func_name),
873                args: args_ir,
874                type_args: vec![],
875                ty: IrType::Infer,
876                evidence,
877            }
878        }
879
880        ast::Expr::MethodCall {
881            receiver,
882            method,
883            args,
884            ..
885        } => {
886            let receiver_ir = lower_expr(ctx, receiver);
887            let args_ir: Vec<IrOperation> = args.iter().map(|a| lower_expr(ctx, a)).collect();
888            let evidence = std::iter::once(get_operation_evidence(&receiver_ir))
889                .chain(args_ir.iter().map(get_operation_evidence))
890                .fold(IrEvidence::Known, |acc, e| acc.join(e));
891
892            IrOperation::MethodCall {
893                receiver: Box::new(receiver_ir),
894                method: method.name.clone(),
895                args: args_ir,
896                type_args: vec![],
897                ty: IrType::Infer,
898                evidence,
899            }
900        }
901
902        ast::Expr::Pipe { expr, operations } => lower_pipeline(ctx, expr, operations),
903
904        ast::Expr::If {
905            condition,
906            then_branch,
907            else_branch,
908        } => {
909            let cond_ir = lower_expr(ctx, condition);
910            let then_ir = lower_block(ctx, then_branch);
911            let else_ir = else_branch.as_ref().map(|e| Box::new(lower_expr(ctx, e)));
912
913            let evidence = get_operation_evidence(&cond_ir)
914                .join(get_operation_evidence(&then_ir))
915                .join(
916                    else_ir
917                        .as_ref()
918                        .map(|e| get_operation_evidence(e))
919                        .unwrap_or(IrEvidence::Known),
920                );
921
922            IrOperation::If {
923                condition: Box::new(cond_ir),
924                then_branch: Box::new(then_ir),
925                else_branch: else_ir,
926                ty: IrType::Infer,
927                evidence,
928            }
929        }
930
931        ast::Expr::Match {
932            expr: scrutinee,
933            arms,
934        } => {
935            let scrutinee_ir = lower_expr(ctx, scrutinee);
936            let arms_ir: Vec<IrMatchArm> = arms
937                .iter()
938                .map(|arm| {
939                    ctx.push_scope();
940                    let pattern = lower_pattern(ctx, &arm.pattern);
941                    let guard = arm.guard.as_ref().map(|g| lower_expr(ctx, g));
942                    let body = lower_expr(ctx, &arm.body);
943                    ctx.pop_scope();
944                    IrMatchArm {
945                        pattern,
946                        guard,
947                        body,
948                    }
949                })
950                .collect();
951
952            IrOperation::Match {
953                scrutinee: Box::new(scrutinee_ir),
954                arms: arms_ir,
955                ty: IrType::Infer,
956                evidence: IrEvidence::Known,
957            }
958        }
959
960        ast::Expr::Loop { body: block, .. } => IrOperation::Loop {
961            variant: LoopVariant::Infinite,
962            condition: None,
963            iterator: None,
964            body: Box::new(lower_block(ctx, block)),
965            ty: IrType::Never,
966            evidence: IrEvidence::Known,
967        },
968
969        ast::Expr::While { condition, body, .. } => IrOperation::Loop {
970            variant: LoopVariant::While,
971            condition: Some(Box::new(lower_expr(ctx, condition))),
972            iterator: None,
973            body: Box::new(lower_block(ctx, body)),
974            ty: IrType::Unit,
975            evidence: IrEvidence::Known,
976        },
977
978        ast::Expr::For {
979            pattern,
980            iter,
981            body,
982            ..
983        } => {
984            ctx.push_scope();
985            let pat = lower_pattern(ctx, pattern);
986            let iter_ir = lower_expr(ctx, iter);
987            let body_ir = lower_block(ctx, body);
988            ctx.pop_scope();
989
990            IrOperation::Loop {
991                variant: LoopVariant::For,
992                condition: None,
993                iterator: Some(IrForIterator {
994                    pattern: pat,
995                    iterable: Box::new(iter_ir),
996                }),
997                body: Box::new(body_ir),
998                ty: IrType::Unit,
999                evidence: IrEvidence::Known,
1000            }
1001        }
1002
1003        ast::Expr::Closure { params, body, .. } => {
1004            // Collect bound variables in current scope for capture analysis
1005            let mut bound: std::collections::HashSet<String> = std::collections::HashSet::new();
1006            for scope in &ctx.scope {
1007                for name in scope.keys() {
1008                    bound.insert(name.clone());
1009                }
1010            }
1011            // Add closure parameters to bound set
1012            for p in params {
1013                if let Some(name) = extract_pattern_name_opt(&p.pattern) {
1014                    bound.insert(name.clone());
1015                }
1016            }
1017            // Collect free variables from the body
1018            let free_vars = collect_free_variables(body, &mut bound.clone());
1019            // Filter to only include variables that are in current scope (not closure params)
1020            let captures: Vec<String> = free_vars
1021                .into_iter()
1022                .filter(|name| {
1023                    // Check if this variable exists in the enclosing scope
1024                    for scope in &ctx.scope {
1025                        if scope.contains_key(name) {
1026                            return true;
1027                        }
1028                    }
1029                    false
1030                })
1031                .collect();
1032
1033            ctx.push_scope();
1034            let params_ir: Vec<IrParam> = params
1035                .iter()
1036                .map(|p| {
1037                    let name = extract_pattern_name(&p.pattern);
1038                    ctx.bind_var(&name);
1039                    IrParam {
1040                        name,
1041                        ty: p.ty.as_ref().map(lower_type_expr).unwrap_or(IrType::Infer),
1042                        evidence: IrEvidence::Known,
1043                    }
1044                })
1045                .collect();
1046            let body_ir = lower_expr(ctx, body);
1047            ctx.pop_scope();
1048
1049            IrOperation::Closure {
1050                params: params_ir,
1051                body: Box::new(body_ir),
1052                captures,
1053                ty: IrType::Infer,
1054                evidence: IrEvidence::Known,
1055            }
1056        }
1057
1058        ast::Expr::Block(block) => lower_block(ctx, block),
1059
1060        ast::Expr::Array(elements) => {
1061            let elements_ir: Vec<IrOperation> =
1062                elements.iter().map(|e| lower_expr(ctx, e)).collect();
1063            IrOperation::Array {
1064                elements: elements_ir,
1065                ty: IrType::Infer,
1066                evidence: IrEvidence::Known,
1067            }
1068        }
1069
1070        ast::Expr::Tuple(elements) => {
1071            let elements_ir: Vec<IrOperation> =
1072                elements.iter().map(|e| lower_expr(ctx, e)).collect();
1073            IrOperation::Tuple {
1074                elements: elements_ir,
1075                ty: IrType::Infer,
1076                evidence: IrEvidence::Known,
1077            }
1078        }
1079
1080        ast::Expr::Struct { path, fields, rest } => {
1081            let name = path
1082                .segments
1083                .iter()
1084                .map(|s| s.ident.name.clone())
1085                .collect::<Vec<_>>()
1086                .join("::");
1087
1088            let fields_ir: Vec<(String, IrOperation)> = fields
1089                .iter()
1090                .map(|f| {
1091                    let value = f
1092                        .value
1093                        .as_ref()
1094                        .map(|v| lower_expr(ctx, v))
1095                        .unwrap_or_else(|| IrOperation::Var {
1096                            name: f.name.name.clone(),
1097                            id: ctx.lookup_var(&f.name.name).unwrap_or_default(),
1098                            ty: IrType::Infer,
1099                            evidence: IrEvidence::Known,
1100                        });
1101                    (f.name.name.clone(), value)
1102                })
1103                .collect();
1104
1105            IrOperation::StructInit {
1106                name,
1107                fields: fields_ir,
1108                rest: rest.as_ref().map(|r| Box::new(lower_expr(ctx, r))),
1109                ty: IrType::Infer,
1110                evidence: IrEvidence::Known,
1111            }
1112        }
1113
1114        ast::Expr::Field { expr: inner, field } => IrOperation::Field {
1115            expr: Box::new(lower_expr(ctx, inner)),
1116            field: field.name.clone(),
1117            ty: IrType::Infer,
1118            evidence: IrEvidence::Known,
1119        },
1120
1121        ast::Expr::Index { expr: inner, index } => IrOperation::Index {
1122            expr: Box::new(lower_expr(ctx, inner)),
1123            index: Box::new(lower_expr(ctx, index)),
1124            ty: IrType::Infer,
1125            evidence: IrEvidence::Known,
1126        },
1127
1128        ast::Expr::Assign { target, value } => IrOperation::Assign {
1129            target: Box::new(lower_expr(ctx, target)),
1130            value: Box::new(lower_expr(ctx, value)),
1131            evidence: IrEvidence::Known,
1132        },
1133
1134        ast::Expr::Return(value) => IrOperation::Return {
1135            value: value.as_ref().map(|v| Box::new(lower_expr(ctx, v))),
1136            evidence: IrEvidence::Known,
1137        },
1138
1139        ast::Expr::Break { value, .. } => IrOperation::Break {
1140            value: value.as_ref().map(|v| Box::new(lower_expr(ctx, v))),
1141            evidence: IrEvidence::Known,
1142        },
1143
1144        ast::Expr::Continue { .. } => IrOperation::Continue {
1145            evidence: IrEvidence::Known,
1146        },
1147
1148        ast::Expr::Await {
1149            expr: inner,
1150            evidentiality,
1151        } => {
1152            let inner_ir = lower_expr(ctx, inner);
1153            // Use the explicit evidentiality marker if provided, otherwise infer from inner
1154            let evidence = match evidentiality {
1155                Some(ast::Evidentiality::Known) => IrEvidence::Known,
1156                Some(ast::Evidentiality::Uncertain) | Some(ast::Evidentiality::Predicted) => IrEvidence::Uncertain,
1157                Some(ast::Evidentiality::Reported) => IrEvidence::Reported,
1158                Some(ast::Evidentiality::Paradox) => IrEvidence::Uncertain, // Trust boundary
1159                None => get_operation_evidence(&inner_ir),
1160            };
1161            IrOperation::Await {
1162                expr: Box::new(inner_ir),
1163                ty: IrType::Infer,
1164                evidence,
1165            }
1166        }
1167
1168        ast::Expr::Try(inner) => {
1169            let inner_ir = lower_expr(ctx, inner);
1170            IrOperation::Try {
1171                expr: Box::new(inner_ir),
1172                ty: IrType::Infer,
1173                evidence: IrEvidence::Uncertain, // Try always produces uncertain
1174            }
1175        }
1176
1177        ast::Expr::Unsafe(block) => IrOperation::Unsafe {
1178            body: Box::new(lower_block(ctx, block)),
1179            ty: IrType::Infer,
1180            evidence: IrEvidence::Paradox, // Unsafe always produces paradox
1181        },
1182
1183        ast::Expr::Async { block, is_move } => IrOperation::Async {
1184            body: Box::new(lower_block(ctx, block)),
1185            is_move: *is_move,
1186            ty: IrType::Infer,
1187            evidence: IrEvidence::Reported, // Async produces reported (external)
1188        },
1189
1190        ast::Expr::Cast { expr: inner, ty } => {
1191            let inner_ir = lower_expr(ctx, inner);
1192            let evidence = get_operation_evidence(&inner_ir);
1193            IrOperation::Cast {
1194                expr: Box::new(inner_ir),
1195                target_type: lower_type_expr(ty),
1196                ty: lower_type_expr(ty),
1197                evidence,
1198            }
1199        }
1200
1201        ast::Expr::Evidential {
1202            expr: inner,
1203            evidentiality,
1204        } => {
1205            let inner_ir = lower_expr(ctx, inner);
1206            let from_evidence = get_operation_evidence(&inner_ir);
1207            let to_evidence = lower_evidentiality(*evidentiality);
1208
1209            IrOperation::EvidenceCoerce {
1210                operation: EvidenceOp::Mark,
1211                expr: Box::new(inner_ir),
1212                from_evidence,
1213                to_evidence,
1214                ty: IrType::Infer,
1215            }
1216        }
1217
1218        ast::Expr::Morpheme { kind, body } => {
1219            let body_ir = lower_expr(ctx, body);
1220            IrOperation::Morpheme {
1221                morpheme: lower_morpheme_kind(*kind),
1222                symbol: morpheme_symbol(*kind).to_string(),
1223                input: Box::new(IrOperation::Var {
1224                    name: "_".to_string(),
1225                    id: "implicit_input".to_string(),
1226                    ty: IrType::Infer,
1227                    evidence: IrEvidence::Known,
1228                }),
1229                body: Some(Box::new(body_ir)),
1230                ty: IrType::Infer,
1231                evidence: IrEvidence::Known,
1232            }
1233        }
1234
1235        ast::Expr::Incorporation { segments } => {
1236            let segs: Vec<IncorporationSegment> = segments
1237                .iter()
1238                .map(|s| {
1239                    if s.args.is_some() {
1240                        IncorporationSegment::Verb {
1241                            name: s.name.name.clone(),
1242                        }
1243                    } else {
1244                        IncorporationSegment::Noun {
1245                            name: s.name.name.clone(),
1246                        }
1247                    }
1248                })
1249                .collect();
1250
1251            // Collect all args from all segments
1252            let mut args: Vec<IrOperation> = Vec::new();
1253            for seg in segments {
1254                if let Some(ref seg_args) = seg.args {
1255                    for a in seg_args {
1256                        args.push(lower_expr(ctx, a));
1257                    }
1258                }
1259            }
1260
1261            IrOperation::Incorporation {
1262                segments: segs,
1263                args,
1264                ty: IrType::Infer,
1265                evidence: IrEvidence::Known,
1266            }
1267        }
1268
1269        // Protocol operations - all yield Reported evidence
1270        ast::Expr::HttpRequest {
1271            method,
1272            url,
1273            headers,
1274            body,
1275            timeout,
1276        } => IrOperation::HttpRequest {
1277            method: lower_http_method(*method),
1278            url: Box::new(lower_expr(ctx, url)),
1279            headers: if headers.is_empty() {
1280                None
1281            } else {
1282                Some(Box::new(IrOperation::Array {
1283                    elements: headers
1284                        .iter()
1285                        .map(|(k, v)| IrOperation::Tuple {
1286                            elements: vec![lower_expr(ctx, k), lower_expr(ctx, v)],
1287                            ty: IrType::Infer,
1288                            evidence: IrEvidence::Known,
1289                        })
1290                        .collect(),
1291                    ty: IrType::Infer,
1292                    evidence: IrEvidence::Known,
1293                }))
1294            },
1295            body: body.as_ref().map(|b| Box::new(lower_expr(ctx, b))),
1296            timeout: timeout.as_ref().map(|t| Box::new(lower_expr(ctx, t))),
1297            ty: IrType::Infer,
1298            evidence: IrEvidence::Reported,
1299        },
1300
1301        ast::Expr::GrpcCall {
1302            service,
1303            method,
1304            message,
1305            metadata,
1306            timeout,
1307        } => IrOperation::GrpcCall {
1308            service: expr_to_string(service),
1309            method: expr_to_string(method),
1310            message: Box::new(message.as_ref().map(|m| lower_expr(ctx, m)).unwrap_or(
1311                IrOperation::Literal {
1312                    variant: LiteralVariant::Null,
1313                    value: serde_json::Value::Null,
1314                    ty: IrType::Unit,
1315                    evidence: IrEvidence::Known,
1316                },
1317            )),
1318            metadata: if metadata.is_empty() {
1319                None
1320            } else {
1321                Some(Box::new(IrOperation::Array {
1322                    elements: metadata
1323                        .iter()
1324                        .map(|(k, v)| IrOperation::Tuple {
1325                            elements: vec![lower_expr(ctx, k), lower_expr(ctx, v)],
1326                            ty: IrType::Infer,
1327                            evidence: IrEvidence::Known,
1328                        })
1329                        .collect(),
1330                    ty: IrType::Infer,
1331                    evidence: IrEvidence::Known,
1332                }))
1333            },
1334            timeout: timeout.as_ref().map(|t| Box::new(lower_expr(ctx, t))),
1335            ty: IrType::Infer,
1336            evidence: IrEvidence::Reported,
1337        },
1338
1339        // Let expression (for if-let, while-let patterns): `let pattern = expr`
1340        // Returns a boolean indicating if the pattern matches
1341        ast::Expr::Let { pattern, value } => {
1342            ctx.push_scope();
1343            let pattern_ir = lower_pattern(ctx, pattern);
1344            let value_ir = lower_expr(ctx, value);
1345            let evidence = get_operation_evidence(&value_ir);
1346            ctx.pop_scope();
1347
1348            IrOperation::LetMatch {
1349                pattern: pattern_ir,
1350                value: Box::new(value_ir),
1351                ty: IrType::Primitive { name: "bool".to_string() },
1352                evidence,
1353            }
1354        }
1355
1356        // Handle remaining expression types with placeholders
1357        _ => IrOperation::Literal {
1358            variant: LiteralVariant::Null,
1359            value: serde_json::json!({"unhandled": format!("{:?}", std::mem::discriminant(expr))}),
1360            ty: IrType::Infer,
1361            evidence: IrEvidence::Known,
1362        },
1363    }
1364}
1365
1366fn lower_pipeline(
1367    ctx: &mut LoweringContext,
1368    input: &ast::Expr,
1369    operations: &[ast::PipeOp],
1370) -> IrOperation {
1371    let input_ir = lower_expr(ctx, input);
1372    let mut evidence = get_operation_evidence(&input_ir);
1373
1374    let steps: Vec<IrPipelineStep> = operations
1375        .iter()
1376        .map(|op| {
1377            let step = lower_pipe_op(ctx, op);
1378            // Update evidence based on operation
1379            evidence = evidence.join(pipe_op_evidence(op));
1380            step
1381        })
1382        .collect();
1383
1384    IrOperation::Pipeline {
1385        input: Box::new(input_ir),
1386        steps,
1387        ty: IrType::Infer,
1388        evidence,
1389    }
1390}
1391
1392fn lower_pipe_op(ctx: &mut LoweringContext, op: &ast::PipeOp) -> IrPipelineStep {
1393    match op {
1394        ast::PipeOp::Transform(body) => IrPipelineStep::Morpheme {
1395            morpheme: MorphemeKind::Transform,
1396            symbol: "τ".to_string(),
1397            body: Some(Box::new(lower_expr(ctx, body))),
1398        },
1399        ast::PipeOp::Filter(body) => IrPipelineStep::Morpheme {
1400            morpheme: MorphemeKind::Filter,
1401            symbol: "φ".to_string(),
1402            body: Some(Box::new(lower_expr(ctx, body))),
1403        },
1404        ast::PipeOp::Sort(field) => IrPipelineStep::Morpheme {
1405            morpheme: MorphemeKind::Sort,
1406            symbol: "σ".to_string(),
1407            body: field.as_ref().map(|f| {
1408                Box::new(IrOperation::Var {
1409                    name: f.name.clone(),
1410                    id: f.name.clone(),
1411                    ty: IrType::Infer,
1412                    evidence: IrEvidence::Known,
1413                })
1414            }),
1415        },
1416        ast::PipeOp::Reduce(body) => IrPipelineStep::Morpheme {
1417            morpheme: MorphemeKind::Reduce,
1418            symbol: "ρ".to_string(),
1419            body: Some(Box::new(lower_expr(ctx, body))),
1420        },
1421        ast::PipeOp::ReduceSum => IrPipelineStep::Morpheme {
1422            morpheme: MorphemeKind::Sum,
1423            symbol: "ρ+".to_string(),
1424            body: None,
1425        },
1426        ast::PipeOp::ReduceProd => IrPipelineStep::Morpheme {
1427            morpheme: MorphemeKind::Product,
1428            symbol: "ρ*".to_string(),
1429            body: None,
1430        },
1431        ast::PipeOp::ReduceMin => IrPipelineStep::Morpheme {
1432            morpheme: MorphemeKind::Min,
1433            symbol: "ρ_min".to_string(),
1434            body: None,
1435        },
1436        ast::PipeOp::ReduceMax => IrPipelineStep::Morpheme {
1437            morpheme: MorphemeKind::Max,
1438            symbol: "ρ_max".to_string(),
1439            body: None,
1440        },
1441        ast::PipeOp::ReduceConcat => IrPipelineStep::Morpheme {
1442            morpheme: MorphemeKind::Concat,
1443            symbol: "ρ++".to_string(),
1444            body: None,
1445        },
1446        ast::PipeOp::ReduceAll => IrPipelineStep::Morpheme {
1447            morpheme: MorphemeKind::All,
1448            symbol: "ρ&".to_string(),
1449            body: None,
1450        },
1451        ast::PipeOp::ReduceAny => IrPipelineStep::Morpheme {
1452            morpheme: MorphemeKind::Any,
1453            symbol: "ρ|".to_string(),
1454            body: None,
1455        },
1456        ast::PipeOp::First => IrPipelineStep::Morpheme {
1457            morpheme: MorphemeKind::First,
1458            symbol: "α".to_string(),
1459            body: None,
1460        },
1461        ast::PipeOp::Last => IrPipelineStep::Morpheme {
1462            morpheme: MorphemeKind::Last,
1463            symbol: "ω".to_string(),
1464            body: None,
1465        },
1466        ast::PipeOp::Middle => IrPipelineStep::Morpheme {
1467            morpheme: MorphemeKind::Middle,
1468            symbol: "μ".to_string(),
1469            body: None,
1470        },
1471        ast::PipeOp::Choice => IrPipelineStep::Morpheme {
1472            morpheme: MorphemeKind::Choice,
1473            symbol: "χ".to_string(),
1474            body: None,
1475        },
1476        ast::PipeOp::Nth(n) => IrPipelineStep::Morpheme {
1477            morpheme: MorphemeKind::Nth,
1478            symbol: "ν".to_string(),
1479            body: Some(Box::new(lower_expr(ctx, n))),
1480        },
1481        ast::PipeOp::Next => IrPipelineStep::Morpheme {
1482            morpheme: MorphemeKind::Next,
1483            symbol: "ξ".to_string(),
1484            body: None,
1485        },
1486        ast::PipeOp::Method { name, type_args: _, args } => IrPipelineStep::Method {
1487            name: name.name.clone(),
1488            args: args.iter().map(|a| lower_expr(ctx, a)).collect(),
1489        },
1490        ast::PipeOp::Await => IrPipelineStep::Await,
1491        ast::PipeOp::Match(_) => {
1492            // Match in pipes is handled by interpreter; IR falls back to identity
1493            // (proper implementation would lower to branching IR)
1494            IrPipelineStep::Identity
1495        }
1496        ast::PipeOp::TryMap(_) => {
1497            // Try/error transformation handled by interpreter
1498            IrPipelineStep::Identity
1499        }
1500        ast::PipeOp::Named { prefix, body } => {
1501            let fn_name = prefix
1502                .iter()
1503                .map(|i| i.name.clone())
1504                .collect::<Vec<_>>()
1505                .join("·");
1506            IrPipelineStep::Call {
1507                function: fn_name,
1508                args: body
1509                    .as_ref()
1510                    .map(|b| vec![lower_expr(ctx, b)])
1511                    .unwrap_or_default(),
1512            }
1513        }
1514        // Protocol operations in pipeline
1515        ast::PipeOp::Send(data) => IrPipelineStep::Protocol {
1516            operation: ProtocolOp::Send,
1517            config: Some(Box::new(lower_expr(ctx, data))),
1518        },
1519        ast::PipeOp::Recv => IrPipelineStep::Protocol {
1520            operation: ProtocolOp::Recv,
1521            config: None,
1522        },
1523        ast::PipeOp::Stream(handler) => IrPipelineStep::Protocol {
1524            operation: ProtocolOp::Stream,
1525            config: Some(Box::new(lower_expr(ctx, handler))),
1526        },
1527        ast::PipeOp::Connect(config) => IrPipelineStep::Protocol {
1528            operation: ProtocolOp::Connect,
1529            config: config.as_ref().map(|c| Box::new(lower_expr(ctx, c))),
1530        },
1531        ast::PipeOp::Close => IrPipelineStep::Protocol {
1532            operation: ProtocolOp::Close,
1533            config: None,
1534        },
1535        ast::PipeOp::Timeout(ms) => IrPipelineStep::Protocol {
1536            operation: ProtocolOp::Timeout,
1537            config: Some(Box::new(lower_expr(ctx, ms))),
1538        },
1539        ast::PipeOp::Retry { count, strategy } => IrPipelineStep::Protocol {
1540            operation: ProtocolOp::Retry,
1541            config: Some(Box::new(IrOperation::Tuple {
1542                elements: vec![
1543                    lower_expr(ctx, count),
1544                    strategy
1545                        .as_ref()
1546                        .map(|s| lower_expr(ctx, s))
1547                        .unwrap_or(IrOperation::Literal {
1548                            variant: LiteralVariant::String,
1549                            value: serde_json::json!("exponential"),
1550                            ty: IrType::Primitive {
1551                                name: "str".to_string(),
1552                            },
1553                            evidence: IrEvidence::Known,
1554                        }),
1555                ],
1556                ty: IrType::Infer,
1557                evidence: IrEvidence::Known,
1558            })),
1559        },
1560        _ => IrPipelineStep::Identity,
1561    }
1562}
1563
1564fn pipe_op_evidence(op: &ast::PipeOp) -> IrEvidence {
1565    match op {
1566        // Protocol operations always produce Reported evidence
1567        ast::PipeOp::Send(_)
1568        | ast::PipeOp::Recv
1569        | ast::PipeOp::Stream(_)
1570        | ast::PipeOp::Connect(_)
1571        | ast::PipeOp::Close => IrEvidence::Reported,
1572
1573        // Evidence promotion operations change evidence level
1574        ast::PipeOp::Validate {
1575            target_evidence, ..
1576        } => ast_evidence_to_ir(*target_evidence),
1577        ast::PipeOp::Assume {
1578            target_evidence, ..
1579        } => ast_evidence_to_ir(*target_evidence),
1580        ast::PipeOp::AssertEvidence(_) => IrEvidence::Known, // Assertion doesn't change evidence
1581
1582        _ => IrEvidence::Known,
1583    }
1584}
1585
1586fn ast_evidence_to_ir(ev: ast::Evidentiality) -> IrEvidence {
1587    match ev {
1588        ast::Evidentiality::Known => IrEvidence::Known,
1589        ast::Evidentiality::Uncertain | ast::Evidentiality::Predicted => IrEvidence::Uncertain,
1590        ast::Evidentiality::Reported => IrEvidence::Reported,
1591        ast::Evidentiality::Paradox => IrEvidence::Paradox,
1592    }
1593}
1594
1595fn lower_literal(lit: &ast::Literal) -> IrOperation {
1596    match lit {
1597        ast::Literal::Int {
1598            value,
1599            base,
1600            suffix,
1601        } => IrOperation::Literal {
1602            variant: LiteralVariant::Int,
1603            value: serde_json::json!({
1604                "value": value,
1605                "base": format!("{:?}", base),
1606                "suffix": suffix
1607            }),
1608            ty: suffix
1609                .as_ref()
1610                .map(|s| IrType::Primitive { name: s.clone() })
1611                .unwrap_or(IrType::Primitive {
1612                    name: "i64".to_string(),
1613                }),
1614            evidence: IrEvidence::Known,
1615        },
1616        ast::Literal::Float { value, suffix } => IrOperation::Literal {
1617            variant: LiteralVariant::Float,
1618            value: serde_json::json!({"value": value, "suffix": suffix}),
1619            ty: suffix
1620                .as_ref()
1621                .map(|s| IrType::Primitive { name: s.clone() })
1622                .unwrap_or(IrType::Primitive {
1623                    name: "f64".to_string(),
1624                }),
1625            evidence: IrEvidence::Known,
1626        },
1627        ast::Literal::String(s) | ast::Literal::MultiLineString(s) | ast::Literal::RawString(s) => {
1628            IrOperation::Literal {
1629                variant: LiteralVariant::String,
1630                value: serde_json::Value::String(s.clone()),
1631                ty: IrType::Primitive {
1632                    name: "str".to_string(),
1633                },
1634                evidence: IrEvidence::Known,
1635            }
1636        }
1637        ast::Literal::Char(c) => IrOperation::Literal {
1638            variant: LiteralVariant::Char,
1639            value: serde_json::Value::String(c.to_string()),
1640            ty: IrType::Primitive {
1641                name: "char".to_string(),
1642            },
1643            evidence: IrEvidence::Known,
1644        },
1645        ast::Literal::Bool(b) => IrOperation::Literal {
1646            variant: LiteralVariant::Bool,
1647            value: serde_json::Value::Bool(*b),
1648            ty: IrType::Primitive {
1649                name: "bool".to_string(),
1650            },
1651            evidence: IrEvidence::Known,
1652        },
1653        ast::Literal::Null | ast::Literal::Empty => IrOperation::Literal {
1654            variant: LiteralVariant::Null,
1655            value: serde_json::Value::Null,
1656            ty: IrType::Unit,
1657            evidence: IrEvidence::Known,
1658        },
1659        _ => IrOperation::Literal {
1660            variant: LiteralVariant::Null,
1661            value: serde_json::Value::Null,
1662            ty: IrType::Infer,
1663            evidence: IrEvidence::Known,
1664        },
1665    }
1666}
1667
1668fn lower_literal_value(lit: &ast::Literal) -> serde_json::Value {
1669    match lit {
1670        ast::Literal::Int { value, .. } => serde_json::json!(value),
1671        ast::Literal::Float { value, .. } => serde_json::json!(value),
1672        ast::Literal::String(s) => serde_json::Value::String(s.clone()),
1673        ast::Literal::Bool(b) => serde_json::Value::Bool(*b),
1674        ast::Literal::Char(c) => serde_json::Value::String(c.to_string()),
1675        ast::Literal::Null => serde_json::Value::Null,
1676        _ => serde_json::Value::Null,
1677    }
1678}
1679
1680fn lower_binop(op: ast::BinOp) -> BinaryOp {
1681    match op {
1682        ast::BinOp::Add => BinaryOp::Add,
1683        ast::BinOp::Sub => BinaryOp::Sub,
1684        ast::BinOp::Mul => BinaryOp::Mul,
1685        ast::BinOp::Div => BinaryOp::Div,
1686        ast::BinOp::Rem => BinaryOp::Rem,
1687        ast::BinOp::Pow => BinaryOp::Pow,
1688        ast::BinOp::And => BinaryOp::And,
1689        ast::BinOp::Or => BinaryOp::Or,
1690        ast::BinOp::BitAnd => BinaryOp::BitAnd,
1691        ast::BinOp::BitOr => BinaryOp::BitOr,
1692        ast::BinOp::BitXor => BinaryOp::BitXor,
1693        ast::BinOp::Shl => BinaryOp::Shl,
1694        ast::BinOp::Shr => BinaryOp::Shr,
1695        ast::BinOp::Eq => BinaryOp::Eq,
1696        ast::BinOp::Ne => BinaryOp::Ne,
1697        ast::BinOp::Lt => BinaryOp::Lt,
1698        ast::BinOp::Le => BinaryOp::Le,
1699        ast::BinOp::Gt => BinaryOp::Gt,
1700        ast::BinOp::Ge => BinaryOp::Ge,
1701        ast::BinOp::Concat => BinaryOp::Concat,
1702        ast::BinOp::MatMul => BinaryOp::MatMul,
1703        ast::BinOp::Hadamard => BinaryOp::Hadamard,
1704        ast::BinOp::TensorProd => BinaryOp::TensorProd,
1705    }
1706}
1707
1708fn lower_unaryop(op: ast::UnaryOp) -> UnaryOp {
1709    match op {
1710        ast::UnaryOp::Neg => UnaryOp::Neg,
1711        ast::UnaryOp::Not => UnaryOp::Not,
1712        ast::UnaryOp::Deref => UnaryOp::Deref,
1713        ast::UnaryOp::Ref => UnaryOp::Ref,
1714        ast::UnaryOp::RefMut => UnaryOp::RefMut,
1715    }
1716}
1717
1718fn lower_morpheme_kind(kind: ast::MorphemeKind) -> MorphemeKind {
1719    match kind {
1720        ast::MorphemeKind::Transform => MorphemeKind::Transform,
1721        ast::MorphemeKind::Filter => MorphemeKind::Filter,
1722        ast::MorphemeKind::Sort => MorphemeKind::Sort,
1723        ast::MorphemeKind::Reduce => MorphemeKind::Reduce,
1724        ast::MorphemeKind::Lambda => MorphemeKind::Lambda,
1725        ast::MorphemeKind::Sum => MorphemeKind::Sum,
1726        ast::MorphemeKind::Product => MorphemeKind::Product,
1727        ast::MorphemeKind::First => MorphemeKind::First,
1728        ast::MorphemeKind::Last => MorphemeKind::Last,
1729        ast::MorphemeKind::Middle => MorphemeKind::Middle,
1730        ast::MorphemeKind::Choice => MorphemeKind::Choice,
1731        ast::MorphemeKind::Nth => MorphemeKind::Nth,
1732        ast::MorphemeKind::Next => MorphemeKind::Next,
1733    }
1734}
1735
1736fn morpheme_symbol(kind: ast::MorphemeKind) -> &'static str {
1737    match kind {
1738        ast::MorphemeKind::Transform => "τ",
1739        ast::MorphemeKind::Filter => "φ",
1740        ast::MorphemeKind::Sort => "σ",
1741        ast::MorphemeKind::Reduce => "ρ",
1742        ast::MorphemeKind::Lambda => "λ",
1743        ast::MorphemeKind::Sum => "Σ",
1744        ast::MorphemeKind::Product => "Π",
1745        ast::MorphemeKind::First => "α",
1746        ast::MorphemeKind::Last => "ω",
1747        ast::MorphemeKind::Middle => "μ",
1748        ast::MorphemeKind::Choice => "χ",
1749        ast::MorphemeKind::Nth => "ν",
1750        ast::MorphemeKind::Next => "ξ",
1751    }
1752}
1753
1754fn lower_http_method(method: ast::HttpMethod) -> HttpMethod {
1755    match method {
1756        ast::HttpMethod::Get => HttpMethod::Get,
1757        ast::HttpMethod::Post => HttpMethod::Post,
1758        ast::HttpMethod::Put => HttpMethod::Put,
1759        ast::HttpMethod::Delete => HttpMethod::Delete,
1760        ast::HttpMethod::Patch => HttpMethod::Patch,
1761        ast::HttpMethod::Head => HttpMethod::Head,
1762        ast::HttpMethod::Options => HttpMethod::Options,
1763        ast::HttpMethod::Connect => HttpMethod::Connect,
1764        ast::HttpMethod::Trace => HttpMethod::Trace,
1765    }
1766}
1767
1768fn get_operation_evidence(op: &IrOperation) -> IrEvidence {
1769    match op {
1770        IrOperation::Literal { evidence, .. }
1771        | IrOperation::Var { evidence, .. }
1772        | IrOperation::Let { evidence, .. }
1773        | IrOperation::Binary { evidence, .. }
1774        | IrOperation::Unary { evidence, .. }
1775        | IrOperation::Call { evidence, .. }
1776        | IrOperation::MethodCall { evidence, .. }
1777        | IrOperation::Closure { evidence, .. }
1778        | IrOperation::If { evidence, .. }
1779        | IrOperation::Match { evidence, .. }
1780        | IrOperation::LetMatch { evidence, .. }
1781        | IrOperation::Loop { evidence, .. }
1782        | IrOperation::Block { evidence, .. }
1783        | IrOperation::Pipeline { evidence, .. }
1784        | IrOperation::Morpheme { evidence, .. }
1785        | IrOperation::Fork { evidence, .. }
1786        | IrOperation::Identity { evidence, .. }
1787        | IrOperation::Array { evidence, .. }
1788        | IrOperation::Tuple { evidence, .. }
1789        | IrOperation::StructInit { evidence, .. }
1790        | IrOperation::Field { evidence, .. }
1791        | IrOperation::Index { evidence, .. }
1792        | IrOperation::Assign { evidence, .. }
1793        | IrOperation::Break { evidence, .. }
1794        | IrOperation::Continue { evidence }
1795        | IrOperation::Return { evidence, .. }
1796        | IrOperation::Incorporation { evidence, .. }
1797        | IrOperation::Affect { evidence, .. }
1798        | IrOperation::HttpRequest { evidence, .. }
1799        | IrOperation::GrpcCall { evidence, .. }
1800        | IrOperation::WebSocket { evidence, .. }
1801        | IrOperation::KafkaOp { evidence, .. }
1802        | IrOperation::Await { evidence, .. }
1803        | IrOperation::Unsafe { evidence, .. }
1804        | IrOperation::Async { evidence, .. }
1805        | IrOperation::Cast { evidence, .. }
1806        | IrOperation::Try { evidence, .. } => *evidence,
1807        IrOperation::EvidenceCoerce { to_evidence, .. } => *to_evidence,
1808    }
1809}
1810
1811fn expr_to_string(e: &ast::Expr) -> String {
1812    match e {
1813        ast::Expr::Path(p) => p
1814            .segments
1815            .iter()
1816            .map(|s| s.ident.name.clone())
1817            .collect::<Vec<_>>()
1818            .join("::"),
1819        ast::Expr::Literal(ast::Literal::String(s)) => s.clone(),
1820        _ => "dynamic".to_string(),
1821    }
1822}
1823
1824// === Type definitions lowering ===
1825
1826fn lower_struct_def(_ctx: &mut LoweringContext, s: &ast::StructDef) -> IrTypeDef {
1827    let fields = match &s.fields {
1828        ast::StructFields::Named(fields) => fields
1829            .iter()
1830            .map(|f| IrField {
1831                name: f.name.name.clone(),
1832                ty: lower_type_expr(&f.ty),
1833                visibility: lower_visibility(f.visibility),
1834            })
1835            .collect(),
1836        ast::StructFields::Tuple(types) => types
1837            .iter()
1838            .enumerate()
1839            .map(|(i, t)| IrField {
1840                name: format!("{}", i),
1841                ty: lower_type_expr(t),
1842                visibility: IrVisibility::Public,
1843            })
1844            .collect(),
1845        ast::StructFields::Unit => vec![],
1846    };
1847
1848    IrTypeDef::Struct {
1849        name: s.name.name.clone(),
1850        generics: lower_generics(&s.generics),
1851        fields,
1852        span: None,
1853    }
1854}
1855
1856fn lower_enum_def(_ctx: &mut LoweringContext, e: &ast::EnumDef) -> IrTypeDef {
1857    let variants = e
1858        .variants
1859        .iter()
1860        .map(|v| {
1861            let fields = match &v.fields {
1862                ast::StructFields::Named(fields) => Some(
1863                    fields
1864                        .iter()
1865                        .map(|f| IrField {
1866                            name: f.name.name.clone(),
1867                            ty: lower_type_expr(&f.ty),
1868                            visibility: lower_visibility(f.visibility),
1869                        })
1870                        .collect(),
1871                ),
1872                ast::StructFields::Tuple(types) => Some(
1873                    types
1874                        .iter()
1875                        .enumerate()
1876                        .map(|(i, t)| IrField {
1877                            name: format!("{}", i),
1878                            ty: lower_type_expr(t),
1879                            visibility: IrVisibility::Public,
1880                        })
1881                        .collect(),
1882                ),
1883                ast::StructFields::Unit => None,
1884            };
1885
1886            IrVariant {
1887                name: v.name.name.clone(),
1888                fields,
1889                discriminant: None,
1890            }
1891        })
1892        .collect();
1893
1894    IrTypeDef::Enum {
1895        name: e.name.name.clone(),
1896        generics: lower_generics(&e.generics),
1897        variants,
1898        span: None,
1899    }
1900}
1901
1902fn lower_type_alias(_ctx: &mut LoweringContext, t: &ast::TypeAlias) -> IrTypeDef {
1903    IrTypeDef::TypeAlias {
1904        name: t.name.name.clone(),
1905        generics: lower_generics(&t.generics),
1906        target: lower_type_expr(&t.ty),
1907        span: None,
1908    }
1909}
1910
1911fn lower_trait_def(ctx: &mut LoweringContext, t: &ast::TraitDef) -> IrTraitDef {
1912    let methods = t
1913        .items
1914        .iter()
1915        .filter_map(|item| match item {
1916            ast::TraitItem::Function(f) => lower_function(ctx, f),
1917            _ => None,
1918        })
1919        .collect();
1920
1921    IrTraitDef {
1922        name: t.name.name.clone(),
1923        generics: lower_generics(&t.generics),
1924        super_traits: t.supertraits.iter().map(type_expr_to_string).collect(),
1925        methods,
1926        span: None,
1927    }
1928}
1929
1930fn lower_impl_block(ctx: &mut LoweringContext, i: &ast::ImplBlock) -> IrImplBlock {
1931    let methods = i
1932        .items
1933        .iter()
1934        .filter_map(|item| match item {
1935            ast::ImplItem::Function(f) => lower_function(ctx, f),
1936            _ => None,
1937        })
1938        .collect();
1939
1940    IrImplBlock {
1941        trait_name: i.trait_.as_ref().map(|t| {
1942            t.segments
1943                .iter()
1944                .map(|s| s.ident.name.clone())
1945                .collect::<Vec<_>>()
1946                .join("::")
1947        }),
1948        target_type: lower_type_expr(&i.self_ty),
1949        generics: lower_generics(&i.generics),
1950        methods,
1951        span: None,
1952    }
1953}
1954
1955fn lower_const_def(ctx: &mut LoweringContext, c: &ast::ConstDef) -> IrConstant {
1956    IrConstant {
1957        name: c.name.name.clone(),
1958        ty: lower_type_expr(&c.ty),
1959        value: lower_expr(ctx, &c.value),
1960        visibility: lower_visibility(c.visibility),
1961        span: None,
1962    }
1963}