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