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::Linear(inner) => {
622            // Linear types - for now, lower as the inner type
623            // Linear tracking is done at runtime/type-check time
624            lower_type_expr(inner)
625        }
626        ast::TypeExpr::Never => IrType::Never,
627        ast::TypeExpr::Infer => IrType::Infer,
628        ast::TypeExpr::Lifetime(name) => IrType::Lifetime { name: name.clone() },
629        ast::TypeExpr::TraitObject(bounds) => IrType::TraitObject {
630            bounds: bounds.iter().map(lower_type_expr).collect(),
631        },
632        ast::TypeExpr::Hrtb { lifetimes, bound } => IrType::Hrtb {
633            lifetimes: lifetimes.clone(),
634            bound: Box::new(lower_type_expr(bound)),
635        },
636        ast::TypeExpr::InlineStruct { fields } => IrType::InlineStruct {
637            fields: fields
638                .iter()
639                .map(|f| (f.name.name.clone(), lower_type_expr(&f.ty)))
640                .collect(),
641        },
642        ast::TypeExpr::ImplTrait(bounds) => IrType::ImplTrait {
643            bounds: bounds.iter().map(lower_type_expr).collect(),
644        },
645        ast::TypeExpr::InlineEnum { variants } => IrType::InlineEnum {
646            variants: variants.iter().map(|v| v.name.name.clone()).collect(),
647        },
648        ast::TypeExpr::AssocTypeBinding { name, ty } => IrType::AssocTypeBinding {
649            name: name.name.clone(),
650            ty: Box::new(lower_type_expr(ty)),
651        },
652        ast::TypeExpr::ConstExpr(_) => {
653            // Const expressions in type position are evaluated at compile time
654            // For now, lower as an inferred type (const eval happens later)
655            IrType::Infer
656        }
657        ast::TypeExpr::QualifiedPath {
658            self_type,
659            trait_path,
660            item_path,
661        } => {
662            // Lower qualified path: <Type as Trait>::AssociatedType
663            // For now, represent as a named type with the full path
664            let trait_part = trait_path
665                .as_ref()
666                .map(|tp| {
667                    tp.segments
668                        .iter()
669                        .map(|s| s.ident.name.clone())
670                        .collect::<Vec<_>>()
671                        .join("::")
672                })
673                .unwrap_or_default();
674            let item_part = item_path
675                .segments
676                .iter()
677                .map(|s| s.ident.name.clone())
678                .collect::<Vec<_>>()
679                .join("::");
680            let name = if trait_part.is_empty() {
681                format!("<_>::{}", item_part)
682            } else {
683                format!("<_ as {}>::{}", trait_part, item_part)
684            };
685            IrType::Named {
686                name,
687                generics: vec![lower_type_expr(self_type)],
688            }
689        }
690    }
691}
692
693fn lower_block(ctx: &mut LoweringContext, block: &ast::Block) -> IrOperation {
694    ctx.push_scope();
695
696    let mut statements: Vec<IrOperation> = block
697        .stmts
698        .iter()
699        .filter_map(|s| lower_stmt(ctx, s))
700        .collect();
701
702    if let Some(expr) = &block.expr {
703        statements.push(lower_expr(ctx, expr));
704    }
705
706    ctx.pop_scope();
707
708    IrOperation::Block {
709        statements,
710        ty: IrType::Infer,
711        evidence: IrEvidence::Known,
712    }
713}
714
715fn lower_stmt(ctx: &mut LoweringContext, stmt: &ast::Stmt) -> Option<IrOperation> {
716    match stmt {
717        ast::Stmt::Let { pattern, ty, init } => {
718            let ir_pattern = lower_pattern(ctx, pattern);
719            let type_annotation = ty.as_ref().map(lower_type_expr);
720            let init_expr = init
721                .as_ref()
722                .map(|e| Box::new(lower_expr(ctx, e)))
723                .unwrap_or_else(|| {
724                    Box::new(IrOperation::Literal {
725                        variant: LiteralVariant::Null,
726                        value: serde_json::Value::Null,
727                        ty: IrType::Unit,
728                        evidence: IrEvidence::Known,
729                    })
730                });
731
732            Some(IrOperation::Let {
733                pattern: ir_pattern,
734                type_annotation,
735                init: init_expr,
736                evidence: IrEvidence::Known,
737            })
738        }
739        ast::Stmt::LetElse {
740            pattern,
741            ty,
742            init,
743            else_branch,
744        } => {
745            // LetElse: let PATTERN = EXPR else { ... }
746            // Lower as: let pattern = init; with conditional else handling
747            let ir_pattern = lower_pattern(ctx, pattern);
748            let type_annotation = ty.as_ref().map(lower_type_expr);
749            let init_expr = Box::new(lower_expr(ctx, init));
750            // TODO: Properly handle else_branch in IR
751            Some(IrOperation::Let {
752                pattern: ir_pattern,
753                type_annotation,
754                init: init_expr,
755                evidence: IrEvidence::Known,
756            })
757        }
758        ast::Stmt::Expr(e) => Some(lower_expr(ctx, e)),
759        ast::Stmt::Semi(e) => Some(lower_expr(ctx, e)),
760        ast::Stmt::Item(_) => None, // Handled at module level
761    }
762}
763
764fn lower_pattern(ctx: &mut LoweringContext, pattern: &ast::Pattern) -> IrPattern {
765    match pattern {
766        ast::Pattern::Ident {
767            mutable,
768            name,
769            evidentiality,
770        } => {
771            ctx.bind_var(&name.name);
772            IrPattern::Ident {
773                name: name.name.clone(),
774                mutable: *mutable,
775                evidence: evidentiality.map(lower_evidentiality),
776            }
777        }
778        ast::Pattern::Tuple(pats) => IrPattern::Tuple {
779            elements: pats.iter().map(|p| lower_pattern(ctx, p)).collect(),
780        },
781        ast::Pattern::Struct { path, fields, rest } => IrPattern::Struct {
782            path: path
783                .segments
784                .iter()
785                .map(|s| s.ident.name.clone())
786                .collect::<Vec<_>>()
787                .join("::"),
788            fields: fields
789                .iter()
790                .map(|f| {
791                    (
792                        f.name.name.clone(),
793                        f.pattern.as_ref().map(|p| lower_pattern(ctx, p)).unwrap_or(
794                            IrPattern::Ident {
795                                name: f.name.name.clone(),
796                                mutable: false,
797                                evidence: None,
798                            },
799                        ),
800                    )
801                })
802                .collect(),
803            rest: *rest,
804        },
805        ast::Pattern::TupleStruct { path, fields } => IrPattern::TupleStruct {
806            path: path
807                .segments
808                .iter()
809                .map(|s| s.ident.name.clone())
810                .collect::<Vec<_>>()
811                .join("::"),
812            fields: fields.iter().map(|p| lower_pattern(ctx, p)).collect(),
813        },
814        ast::Pattern::Slice(pats) => IrPattern::Slice {
815            elements: pats.iter().map(|p| lower_pattern(ctx, p)).collect(),
816        },
817        ast::Pattern::Or(pats) => IrPattern::Or {
818            patterns: pats.iter().map(|p| lower_pattern(ctx, p)).collect(),
819        },
820        ast::Pattern::Literal(lit) => IrPattern::Literal {
821            value: lower_literal_value(lit),
822        },
823        ast::Pattern::Range {
824            start,
825            end,
826            inclusive,
827        } => IrPattern::Range {
828            start: start.as_ref().map(|p| Box::new(lower_pattern(ctx, p))),
829            end: end.as_ref().map(|p| Box::new(lower_pattern(ctx, p))),
830            inclusive: *inclusive,
831        },
832        ast::Pattern::Wildcard | ast::Pattern::Rest => IrPattern::Wildcard,
833        ast::Pattern::Ref {
834            mutable: _,
835            pattern,
836        } => {
837            // Reference patterns - just lower the inner pattern
838            lower_pattern(ctx, pattern)
839        }
840        ast::Pattern::RefBinding {
841            mutable,
842            name,
843            evidentiality,
844        } => {
845            // Ref binding - bind by reference, lower as identifier
846            IrPattern::Ident {
847                name: name.name.clone(),
848                mutable: *mutable,
849                evidence: evidentiality.map(lower_evidentiality),
850            }
851        }
852        ast::Pattern::Path(path) => {
853            // Path pattern (unit variant matching) - use TupleStruct with empty fields
854            let name = path
855                .segments
856                .iter()
857                .map(|s| s.ident.name.clone())
858                .collect::<Vec<_>>()
859                .join("::");
860            IrPattern::TupleStruct {
861                path: name,
862                fields: vec![],
863            }
864        }
865    }
866}
867
868fn lower_expr(ctx: &mut LoweringContext, expr: &ast::Expr) -> IrOperation {
869    match expr {
870        ast::Expr::Literal(lit) => lower_literal(lit),
871
872        ast::Expr::Path(path) => {
873            let name = path
874                .segments
875                .iter()
876                .map(|s| s.ident.name.clone())
877                .collect::<Vec<_>>()
878                .join("::");
879            let id = ctx.lookup_var(&name).unwrap_or_else(|| name.clone());
880
881            IrOperation::Var {
882                name,
883                id,
884                ty: IrType::Infer,
885                evidence: IrEvidence::Known,
886            }
887        }
888
889        ast::Expr::Binary { left, op, right } => {
890            let left_ir = lower_expr(ctx, left);
891            let right_ir = lower_expr(ctx, right);
892            let left_ev = get_operation_evidence(&left_ir);
893            let right_ev = get_operation_evidence(&right_ir);
894
895            IrOperation::Binary {
896                operator: lower_binop(*op),
897                left: Box::new(left_ir),
898                right: Box::new(right_ir),
899                ty: IrType::Infer,
900                evidence: left_ev.join(right_ev),
901            }
902        }
903
904        ast::Expr::Unary { op, expr: inner } => {
905            let inner_ir = lower_expr(ctx, inner);
906            let evidence = get_operation_evidence(&inner_ir);
907
908            IrOperation::Unary {
909                operator: lower_unaryop(*op),
910                operand: Box::new(inner_ir),
911                ty: IrType::Infer,
912                evidence,
913            }
914        }
915
916        ast::Expr::Call { func, args } => {
917            let func_name = match func.as_ref() {
918                ast::Expr::Path(p) => p
919                    .segments
920                    .iter()
921                    .map(|s| s.ident.name.clone())
922                    .collect::<Vec<_>>()
923                    .join("::"),
924                _ => "anonymous".to_string(),
925            };
926
927            let args_ir: Vec<IrOperation> = args.iter().map(|a| lower_expr(ctx, a)).collect();
928            let evidence = args_ir
929                .iter()
930                .map(get_operation_evidence)
931                .fold(IrEvidence::Known, |acc, e| acc.join(e));
932
933            IrOperation::Call {
934                function: func_name.clone(),
935                function_id: format!("fn_{}", func_name),
936                args: args_ir,
937                type_args: vec![],
938                ty: IrType::Infer,
939                evidence,
940            }
941        }
942
943        ast::Expr::MethodCall {
944            receiver,
945            method,
946            args,
947            ..
948        } => {
949            let receiver_ir = lower_expr(ctx, receiver);
950            let args_ir: Vec<IrOperation> = args.iter().map(|a| lower_expr(ctx, a)).collect();
951            let evidence = std::iter::once(get_operation_evidence(&receiver_ir))
952                .chain(args_ir.iter().map(get_operation_evidence))
953                .fold(IrEvidence::Known, |acc, e| acc.join(e));
954
955            IrOperation::MethodCall {
956                receiver: Box::new(receiver_ir),
957                method: method.name.clone(),
958                args: args_ir,
959                type_args: vec![],
960                ty: IrType::Infer,
961                evidence,
962            }
963        }
964
965        ast::Expr::Pipe { expr, operations } => lower_pipeline(ctx, expr, operations),
966
967        ast::Expr::If {
968            condition,
969            then_branch,
970            else_branch,
971        } => {
972            let cond_ir = lower_expr(ctx, condition);
973            let then_ir = lower_block(ctx, then_branch);
974            let else_ir = else_branch.as_ref().map(|e| Box::new(lower_expr(ctx, e)));
975
976            let evidence = get_operation_evidence(&cond_ir)
977                .join(get_operation_evidence(&then_ir))
978                .join(
979                    else_ir
980                        .as_ref()
981                        .map(|e| get_operation_evidence(e))
982                        .unwrap_or(IrEvidence::Known),
983                );
984
985            IrOperation::If {
986                condition: Box::new(cond_ir),
987                then_branch: Box::new(then_ir),
988                else_branch: else_ir,
989                ty: IrType::Infer,
990                evidence,
991            }
992        }
993
994        ast::Expr::Match {
995            expr: scrutinee,
996            arms,
997        } => {
998            let scrutinee_ir = lower_expr(ctx, scrutinee);
999            let arms_ir: Vec<IrMatchArm> = arms
1000                .iter()
1001                .map(|arm| {
1002                    ctx.push_scope();
1003                    let pattern = lower_pattern(ctx, &arm.pattern);
1004                    let guard = arm.guard.as_ref().map(|g| lower_expr(ctx, g));
1005                    let body = lower_expr(ctx, &arm.body);
1006                    ctx.pop_scope();
1007                    IrMatchArm {
1008                        pattern,
1009                        guard,
1010                        body,
1011                    }
1012                })
1013                .collect();
1014
1015            IrOperation::Match {
1016                scrutinee: Box::new(scrutinee_ir),
1017                arms: arms_ir,
1018                ty: IrType::Infer,
1019                evidence: IrEvidence::Known,
1020            }
1021        }
1022
1023        ast::Expr::Loop { body: block, .. } => IrOperation::Loop {
1024            variant: LoopVariant::Infinite,
1025            condition: None,
1026            iterator: None,
1027            body: Box::new(lower_block(ctx, block)),
1028            ty: IrType::Never,
1029            evidence: IrEvidence::Known,
1030        },
1031
1032        ast::Expr::While {
1033            condition, body, ..
1034        } => IrOperation::Loop {
1035            variant: LoopVariant::While,
1036            condition: Some(Box::new(lower_expr(ctx, condition))),
1037            iterator: None,
1038            body: Box::new(lower_block(ctx, body)),
1039            ty: IrType::Unit,
1040            evidence: IrEvidence::Known,
1041        },
1042
1043        ast::Expr::For {
1044            pattern,
1045            iter,
1046            body,
1047            ..
1048        } => {
1049            ctx.push_scope();
1050            let pat = lower_pattern(ctx, pattern);
1051            let iter_ir = lower_expr(ctx, iter);
1052            let body_ir = lower_block(ctx, body);
1053            ctx.pop_scope();
1054
1055            IrOperation::Loop {
1056                variant: LoopVariant::For,
1057                condition: None,
1058                iterator: Some(IrForIterator {
1059                    pattern: pat,
1060                    iterable: Box::new(iter_ir),
1061                }),
1062                body: Box::new(body_ir),
1063                ty: IrType::Unit,
1064                evidence: IrEvidence::Known,
1065            }
1066        }
1067
1068        ast::Expr::Closure { params, body, .. } => {
1069            // Collect bound variables in current scope for capture analysis
1070            let mut bound: std::collections::HashSet<String> = std::collections::HashSet::new();
1071            for scope in &ctx.scope {
1072                for name in scope.keys() {
1073                    bound.insert(name.clone());
1074                }
1075            }
1076            // Add closure parameters to bound set
1077            for p in params {
1078                if let Some(name) = extract_pattern_name_opt(&p.pattern) {
1079                    bound.insert(name.clone());
1080                }
1081            }
1082            // Collect free variables from the body
1083            let free_vars = collect_free_variables(body, &mut bound.clone());
1084            // Filter to only include variables that are in current scope (not closure params)
1085            let captures: Vec<String> = free_vars
1086                .into_iter()
1087                .filter(|name| {
1088                    // Check if this variable exists in the enclosing scope
1089                    for scope in &ctx.scope {
1090                        if scope.contains_key(name) {
1091                            return true;
1092                        }
1093                    }
1094                    false
1095                })
1096                .collect();
1097
1098            ctx.push_scope();
1099            let params_ir: Vec<IrParam> = params
1100                .iter()
1101                .map(|p| {
1102                    let name = extract_pattern_name(&p.pattern);
1103                    ctx.bind_var(&name);
1104                    IrParam {
1105                        name,
1106                        ty: p.ty.as_ref().map(lower_type_expr).unwrap_or(IrType::Infer),
1107                        evidence: IrEvidence::Known,
1108                    }
1109                })
1110                .collect();
1111            let body_ir = lower_expr(ctx, body);
1112            ctx.pop_scope();
1113
1114            IrOperation::Closure {
1115                params: params_ir,
1116                body: Box::new(body_ir),
1117                captures,
1118                ty: IrType::Infer,
1119                evidence: IrEvidence::Known,
1120            }
1121        }
1122
1123        ast::Expr::Block(block) => lower_block(ctx, block),
1124
1125        ast::Expr::Array(elements) => {
1126            let elements_ir: Vec<IrOperation> =
1127                elements.iter().map(|e| lower_expr(ctx, e)).collect();
1128            IrOperation::Array {
1129                elements: elements_ir,
1130                ty: IrType::Infer,
1131                evidence: IrEvidence::Known,
1132            }
1133        }
1134
1135        ast::Expr::Tuple(elements) => {
1136            let elements_ir: Vec<IrOperation> =
1137                elements.iter().map(|e| lower_expr(ctx, e)).collect();
1138            IrOperation::Tuple {
1139                elements: elements_ir,
1140                ty: IrType::Infer,
1141                evidence: IrEvidence::Known,
1142            }
1143        }
1144
1145        ast::Expr::Struct { path, fields, rest } => {
1146            let name = path
1147                .segments
1148                .iter()
1149                .map(|s| s.ident.name.clone())
1150                .collect::<Vec<_>>()
1151                .join("::");
1152
1153            let fields_ir: Vec<(String, IrOperation)> = fields
1154                .iter()
1155                .map(|f| {
1156                    let value = f
1157                        .value
1158                        .as_ref()
1159                        .map(|v| lower_expr(ctx, v))
1160                        .unwrap_or_else(|| IrOperation::Var {
1161                            name: f.name.name.clone(),
1162                            id: ctx.lookup_var(&f.name.name).unwrap_or_default(),
1163                            ty: IrType::Infer,
1164                            evidence: IrEvidence::Known,
1165                        });
1166                    (f.name.name.clone(), value)
1167                })
1168                .collect();
1169
1170            IrOperation::StructInit {
1171                name,
1172                fields: fields_ir,
1173                rest: rest.as_ref().map(|r| Box::new(lower_expr(ctx, r))),
1174                ty: IrType::Infer,
1175                evidence: IrEvidence::Known,
1176            }
1177        }
1178
1179        ast::Expr::Field { expr: inner, field } => IrOperation::Field {
1180            expr: Box::new(lower_expr(ctx, inner)),
1181            field: field.name.clone(),
1182            ty: IrType::Infer,
1183            evidence: IrEvidence::Known,
1184        },
1185
1186        ast::Expr::Index { expr: inner, index } => IrOperation::Index {
1187            expr: Box::new(lower_expr(ctx, inner)),
1188            index: Box::new(lower_expr(ctx, index)),
1189            ty: IrType::Infer,
1190            evidence: IrEvidence::Known,
1191        },
1192
1193        ast::Expr::Assign { target, value } => IrOperation::Assign {
1194            target: Box::new(lower_expr(ctx, target)),
1195            value: Box::new(lower_expr(ctx, value)),
1196            evidence: IrEvidence::Known,
1197        },
1198
1199        ast::Expr::Return(value) => IrOperation::Return {
1200            value: value.as_ref().map(|v| Box::new(lower_expr(ctx, v))),
1201            evidence: IrEvidence::Known,
1202        },
1203
1204        ast::Expr::Break { value, .. } => IrOperation::Break {
1205            value: value.as_ref().map(|v| Box::new(lower_expr(ctx, v))),
1206            evidence: IrEvidence::Known,
1207        },
1208
1209        ast::Expr::Continue { .. } => IrOperation::Continue {
1210            evidence: IrEvidence::Known,
1211        },
1212
1213        ast::Expr::Await {
1214            expr: inner,
1215            evidentiality,
1216        } => {
1217            let inner_ir = lower_expr(ctx, inner);
1218            // Use the explicit evidentiality marker if provided, otherwise infer from inner
1219            let evidence = match evidentiality {
1220                Some(ast::Evidentiality::Known) => IrEvidence::Known,
1221                Some(ast::Evidentiality::Uncertain) | Some(ast::Evidentiality::Predicted) => {
1222                    IrEvidence::Uncertain
1223                }
1224                Some(ast::Evidentiality::Reported) => IrEvidence::Reported,
1225                Some(ast::Evidentiality::Paradox) => IrEvidence::Uncertain, // Trust boundary
1226                None => get_operation_evidence(&inner_ir),
1227            };
1228            IrOperation::Await {
1229                expr: Box::new(inner_ir),
1230                ty: IrType::Infer,
1231                evidence,
1232            }
1233        }
1234
1235        ast::Expr::Try(inner) => {
1236            let inner_ir = lower_expr(ctx, inner);
1237            IrOperation::Try {
1238                expr: Box::new(inner_ir),
1239                ty: IrType::Infer,
1240                evidence: IrEvidence::Uncertain, // Try always produces uncertain
1241            }
1242        }
1243
1244        ast::Expr::Unsafe(block) => IrOperation::Unsafe {
1245            body: Box::new(lower_block(ctx, block)),
1246            ty: IrType::Infer,
1247            evidence: IrEvidence::Paradox, // Unsafe always produces paradox
1248        },
1249
1250        ast::Expr::Async { block, is_move } => IrOperation::Async {
1251            body: Box::new(lower_block(ctx, block)),
1252            is_move: *is_move,
1253            ty: IrType::Infer,
1254            evidence: IrEvidence::Reported, // Async produces reported (external)
1255        },
1256
1257        ast::Expr::Cast { expr: inner, ty } => {
1258            let inner_ir = lower_expr(ctx, inner);
1259            let evidence = get_operation_evidence(&inner_ir);
1260            IrOperation::Cast {
1261                expr: Box::new(inner_ir),
1262                target_type: lower_type_expr(ty),
1263                ty: lower_type_expr(ty),
1264                evidence,
1265            }
1266        }
1267
1268        ast::Expr::Evidential {
1269            expr: inner,
1270            evidentiality,
1271        } => {
1272            let inner_ir = lower_expr(ctx, inner);
1273            let from_evidence = get_operation_evidence(&inner_ir);
1274            let to_evidence = lower_evidentiality(*evidentiality);
1275
1276            IrOperation::EvidenceCoerce {
1277                operation: EvidenceOp::Mark,
1278                expr: Box::new(inner_ir),
1279                from_evidence,
1280                to_evidence,
1281                ty: IrType::Infer,
1282            }
1283        }
1284
1285        ast::Expr::Morpheme { kind, body } => {
1286            let body_ir = lower_expr(ctx, body);
1287            IrOperation::Morpheme {
1288                morpheme: lower_morpheme_kind(*kind),
1289                symbol: morpheme_symbol(*kind).to_string(),
1290                input: Box::new(IrOperation::Var {
1291                    name: "_".to_string(),
1292                    id: "implicit_input".to_string(),
1293                    ty: IrType::Infer,
1294                    evidence: IrEvidence::Known,
1295                }),
1296                body: Some(Box::new(body_ir)),
1297                ty: IrType::Infer,
1298                evidence: IrEvidence::Known,
1299            }
1300        }
1301
1302        ast::Expr::Incorporation { segments } => {
1303            let segs: Vec<IncorporationSegment> = segments
1304                .iter()
1305                .map(|s| {
1306                    if s.args.is_some() {
1307                        IncorporationSegment::Verb {
1308                            name: s.name.name.clone(),
1309                        }
1310                    } else {
1311                        IncorporationSegment::Noun {
1312                            name: s.name.name.clone(),
1313                        }
1314                    }
1315                })
1316                .collect();
1317
1318            // Collect all args from all segments
1319            let mut args: Vec<IrOperation> = Vec::new();
1320            for seg in segments {
1321                if let Some(ref seg_args) = seg.args {
1322                    for a in seg_args {
1323                        args.push(lower_expr(ctx, a));
1324                    }
1325                }
1326            }
1327
1328            IrOperation::Incorporation {
1329                segments: segs,
1330                args,
1331                ty: IrType::Infer,
1332                evidence: IrEvidence::Known,
1333            }
1334        }
1335
1336        // Protocol operations - all yield Reported evidence
1337        ast::Expr::HttpRequest {
1338            method,
1339            url,
1340            headers,
1341            body,
1342            timeout,
1343        } => IrOperation::HttpRequest {
1344            method: lower_http_method(*method),
1345            url: Box::new(lower_expr(ctx, url)),
1346            headers: if headers.is_empty() {
1347                None
1348            } else {
1349                Some(Box::new(IrOperation::Array {
1350                    elements: headers
1351                        .iter()
1352                        .map(|(k, v)| IrOperation::Tuple {
1353                            elements: vec![lower_expr(ctx, k), lower_expr(ctx, v)],
1354                            ty: IrType::Infer,
1355                            evidence: IrEvidence::Known,
1356                        })
1357                        .collect(),
1358                    ty: IrType::Infer,
1359                    evidence: IrEvidence::Known,
1360                }))
1361            },
1362            body: body.as_ref().map(|b| Box::new(lower_expr(ctx, b))),
1363            timeout: timeout.as_ref().map(|t| Box::new(lower_expr(ctx, t))),
1364            ty: IrType::Infer,
1365            evidence: IrEvidence::Reported,
1366        },
1367
1368        ast::Expr::GrpcCall {
1369            service,
1370            method,
1371            message,
1372            metadata,
1373            timeout,
1374        } => IrOperation::GrpcCall {
1375            service: expr_to_string(service),
1376            method: expr_to_string(method),
1377            message: Box::new(message.as_ref().map(|m| lower_expr(ctx, m)).unwrap_or(
1378                IrOperation::Literal {
1379                    variant: LiteralVariant::Null,
1380                    value: serde_json::Value::Null,
1381                    ty: IrType::Unit,
1382                    evidence: IrEvidence::Known,
1383                },
1384            )),
1385            metadata: if metadata.is_empty() {
1386                None
1387            } else {
1388                Some(Box::new(IrOperation::Array {
1389                    elements: metadata
1390                        .iter()
1391                        .map(|(k, v)| IrOperation::Tuple {
1392                            elements: vec![lower_expr(ctx, k), lower_expr(ctx, v)],
1393                            ty: IrType::Infer,
1394                            evidence: IrEvidence::Known,
1395                        })
1396                        .collect(),
1397                    ty: IrType::Infer,
1398                    evidence: IrEvidence::Known,
1399                }))
1400            },
1401            timeout: timeout.as_ref().map(|t| Box::new(lower_expr(ctx, t))),
1402            ty: IrType::Infer,
1403            evidence: IrEvidence::Reported,
1404        },
1405
1406        // Let expression (for if-let, while-let patterns): `let pattern = expr`
1407        // Returns a boolean indicating if the pattern matches
1408        ast::Expr::Let { pattern, value } => {
1409            ctx.push_scope();
1410            let pattern_ir = lower_pattern(ctx, pattern);
1411            let value_ir = lower_expr(ctx, value);
1412            let evidence = get_operation_evidence(&value_ir);
1413            ctx.pop_scope();
1414
1415            IrOperation::LetMatch {
1416                pattern: pattern_ir,
1417                value: Box::new(value_ir),
1418                ty: IrType::Primitive {
1419                    name: "bool".to_string(),
1420                },
1421                evidence,
1422            }
1423        }
1424
1425        // Handle remaining expression types with placeholders
1426        _ => IrOperation::Literal {
1427            variant: LiteralVariant::Null,
1428            value: serde_json::json!({"unhandled": format!("{:?}", std::mem::discriminant(expr))}),
1429            ty: IrType::Infer,
1430            evidence: IrEvidence::Known,
1431        },
1432    }
1433}
1434
1435fn lower_pipeline(
1436    ctx: &mut LoweringContext,
1437    input: &ast::Expr,
1438    operations: &[ast::PipeOp],
1439) -> IrOperation {
1440    let input_ir = lower_expr(ctx, input);
1441    let mut evidence = get_operation_evidence(&input_ir);
1442
1443    let steps: Vec<IrPipelineStep> = operations
1444        .iter()
1445        .map(|op| {
1446            let step = lower_pipe_op(ctx, op);
1447            // Update evidence based on operation
1448            evidence = evidence.join(pipe_op_evidence(op));
1449            step
1450        })
1451        .collect();
1452
1453    IrOperation::Pipeline {
1454        input: Box::new(input_ir),
1455        steps,
1456        ty: IrType::Infer,
1457        evidence,
1458    }
1459}
1460
1461fn lower_pipe_op(ctx: &mut LoweringContext, op: &ast::PipeOp) -> IrPipelineStep {
1462    match op {
1463        ast::PipeOp::Transform(body) => IrPipelineStep::Morpheme {
1464            morpheme: MorphemeKind::Transform,
1465            symbol: "τ".to_string(),
1466            body: Some(Box::new(lower_expr(ctx, body))),
1467        },
1468        ast::PipeOp::Filter(body) => IrPipelineStep::Morpheme {
1469            morpheme: MorphemeKind::Filter,
1470            symbol: "φ".to_string(),
1471            body: Some(Box::new(lower_expr(ctx, body))),
1472        },
1473        ast::PipeOp::Sort(field) => IrPipelineStep::Morpheme {
1474            morpheme: MorphemeKind::Sort,
1475            symbol: "σ".to_string(),
1476            body: field.as_ref().map(|f| {
1477                Box::new(IrOperation::Var {
1478                    name: f.name.clone(),
1479                    id: f.name.clone(),
1480                    ty: IrType::Infer,
1481                    evidence: IrEvidence::Known,
1482                })
1483            }),
1484        },
1485        ast::PipeOp::Reduce(body) => IrPipelineStep::Morpheme {
1486            morpheme: MorphemeKind::Reduce,
1487            symbol: "ρ".to_string(),
1488            body: Some(Box::new(lower_expr(ctx, body))),
1489        },
1490        ast::PipeOp::ReduceSum => IrPipelineStep::Morpheme {
1491            morpheme: MorphemeKind::Sum,
1492            symbol: "ρ+".to_string(),
1493            body: None,
1494        },
1495        ast::PipeOp::ReduceProd => IrPipelineStep::Morpheme {
1496            morpheme: MorphemeKind::Product,
1497            symbol: "ρ*".to_string(),
1498            body: None,
1499        },
1500        ast::PipeOp::ReduceMin => IrPipelineStep::Morpheme {
1501            morpheme: MorphemeKind::Min,
1502            symbol: "ρ_min".to_string(),
1503            body: None,
1504        },
1505        ast::PipeOp::ReduceMax => IrPipelineStep::Morpheme {
1506            morpheme: MorphemeKind::Max,
1507            symbol: "ρ_max".to_string(),
1508            body: None,
1509        },
1510        ast::PipeOp::ReduceConcat => IrPipelineStep::Morpheme {
1511            morpheme: MorphemeKind::Concat,
1512            symbol: "ρ++".to_string(),
1513            body: None,
1514        },
1515        ast::PipeOp::ReduceAll => IrPipelineStep::Morpheme {
1516            morpheme: MorphemeKind::All,
1517            symbol: "ρ&".to_string(),
1518            body: None,
1519        },
1520        ast::PipeOp::ReduceAny => IrPipelineStep::Morpheme {
1521            morpheme: MorphemeKind::Any,
1522            symbol: "ρ|".to_string(),
1523            body: None,
1524        },
1525        ast::PipeOp::First => IrPipelineStep::Morpheme {
1526            morpheme: MorphemeKind::First,
1527            symbol: "α".to_string(),
1528            body: None,
1529        },
1530        ast::PipeOp::Last => IrPipelineStep::Morpheme {
1531            morpheme: MorphemeKind::Last,
1532            symbol: "ω".to_string(),
1533            body: None,
1534        },
1535        ast::PipeOp::Middle => IrPipelineStep::Morpheme {
1536            morpheme: MorphemeKind::Middle,
1537            symbol: "μ".to_string(),
1538            body: None,
1539        },
1540        ast::PipeOp::Choice => IrPipelineStep::Morpheme {
1541            morpheme: MorphemeKind::Choice,
1542            symbol: "χ".to_string(),
1543            body: None,
1544        },
1545        ast::PipeOp::Nth(n) => IrPipelineStep::Morpheme {
1546            morpheme: MorphemeKind::Nth,
1547            symbol: "ν".to_string(),
1548            body: Some(Box::new(lower_expr(ctx, n))),
1549        },
1550        ast::PipeOp::Next => IrPipelineStep::Morpheme {
1551            morpheme: MorphemeKind::Next,
1552            symbol: "ξ".to_string(),
1553            body: None,
1554        },
1555        ast::PipeOp::Method {
1556            name,
1557            type_args: _,
1558            args,
1559        } => IrPipelineStep::Method {
1560            name: name.name.clone(),
1561            args: args.iter().map(|a| lower_expr(ctx, a)).collect(),
1562        },
1563        ast::PipeOp::Await => IrPipelineStep::Await,
1564        ast::PipeOp::Match(_) => {
1565            // Match in pipes is handled by interpreter; IR falls back to identity
1566            // (proper implementation would lower to branching IR)
1567            IrPipelineStep::Identity
1568        }
1569        ast::PipeOp::TryMap(_) => {
1570            // Try/error transformation handled by interpreter
1571            IrPipelineStep::Identity
1572        }
1573        ast::PipeOp::Named { prefix, body } => {
1574            let fn_name = prefix
1575                .iter()
1576                .map(|i| i.name.clone())
1577                .collect::<Vec<_>>()
1578                .join("·");
1579            IrPipelineStep::Call {
1580                function: fn_name,
1581                args: body
1582                    .as_ref()
1583                    .map(|b| vec![lower_expr(ctx, b)])
1584                    .unwrap_or_default(),
1585            }
1586        }
1587        // Protocol operations in pipeline
1588        ast::PipeOp::Send(data) => IrPipelineStep::Protocol {
1589            operation: ProtocolOp::Send,
1590            config: Some(Box::new(lower_expr(ctx, data))),
1591        },
1592        ast::PipeOp::Recv => IrPipelineStep::Protocol {
1593            operation: ProtocolOp::Recv,
1594            config: None,
1595        },
1596        ast::PipeOp::Stream(handler) => IrPipelineStep::Protocol {
1597            operation: ProtocolOp::Stream,
1598            config: Some(Box::new(lower_expr(ctx, handler))),
1599        },
1600        ast::PipeOp::Connect(config) => IrPipelineStep::Protocol {
1601            operation: ProtocolOp::Connect,
1602            config: config.as_ref().map(|c| Box::new(lower_expr(ctx, c))),
1603        },
1604        ast::PipeOp::Close => IrPipelineStep::Protocol {
1605            operation: ProtocolOp::Close,
1606            config: None,
1607        },
1608        ast::PipeOp::Timeout(ms) => IrPipelineStep::Protocol {
1609            operation: ProtocolOp::Timeout,
1610            config: Some(Box::new(lower_expr(ctx, ms))),
1611        },
1612        ast::PipeOp::Retry { count, strategy } => IrPipelineStep::Protocol {
1613            operation: ProtocolOp::Retry,
1614            config: Some(Box::new(IrOperation::Tuple {
1615                elements: vec![
1616                    lower_expr(ctx, count),
1617                    strategy
1618                        .as_ref()
1619                        .map(|s| lower_expr(ctx, s))
1620                        .unwrap_or(IrOperation::Literal {
1621                            variant: LiteralVariant::String,
1622                            value: serde_json::json!("exponential"),
1623                            ty: IrType::Primitive {
1624                                name: "str".to_string(),
1625                            },
1626                            evidence: IrEvidence::Known,
1627                        }),
1628                ],
1629                ty: IrType::Infer,
1630                evidence: IrEvidence::Known,
1631            })),
1632        },
1633        _ => IrPipelineStep::Identity,
1634    }
1635}
1636
1637fn pipe_op_evidence(op: &ast::PipeOp) -> IrEvidence {
1638    match op {
1639        // Protocol operations always produce Reported evidence
1640        ast::PipeOp::Send(_)
1641        | ast::PipeOp::Recv
1642        | ast::PipeOp::Stream(_)
1643        | ast::PipeOp::Connect(_)
1644        | ast::PipeOp::Close => IrEvidence::Reported,
1645
1646        // Evidence promotion operations change evidence level
1647        ast::PipeOp::Validate {
1648            target_evidence, ..
1649        } => ast_evidence_to_ir(*target_evidence),
1650        ast::PipeOp::Assume {
1651            target_evidence, ..
1652        } => ast_evidence_to_ir(*target_evidence),
1653        ast::PipeOp::AssertEvidence(_) => IrEvidence::Known, // Assertion doesn't change evidence
1654
1655        _ => IrEvidence::Known,
1656    }
1657}
1658
1659fn ast_evidence_to_ir(ev: ast::Evidentiality) -> IrEvidence {
1660    match ev {
1661        ast::Evidentiality::Known => IrEvidence::Known,
1662        ast::Evidentiality::Uncertain | ast::Evidentiality::Predicted => IrEvidence::Uncertain,
1663        ast::Evidentiality::Reported => IrEvidence::Reported,
1664        ast::Evidentiality::Paradox => IrEvidence::Paradox,
1665    }
1666}
1667
1668fn lower_literal(lit: &ast::Literal) -> IrOperation {
1669    match lit {
1670        ast::Literal::Int {
1671            value,
1672            base,
1673            suffix,
1674        } => IrOperation::Literal {
1675            variant: LiteralVariant::Int,
1676            value: serde_json::json!({
1677                "value": value,
1678                "base": format!("{:?}", base),
1679                "suffix": suffix
1680            }),
1681            ty: suffix
1682                .as_ref()
1683                .map(|s| IrType::Primitive { name: s.clone() })
1684                .unwrap_or(IrType::Primitive {
1685                    name: "i64".to_string(),
1686                }),
1687            evidence: IrEvidence::Known,
1688        },
1689        ast::Literal::Float { value, suffix } => IrOperation::Literal {
1690            variant: LiteralVariant::Float,
1691            value: serde_json::json!({"value": value, "suffix": suffix}),
1692            ty: suffix
1693                .as_ref()
1694                .map(|s| IrType::Primitive { name: s.clone() })
1695                .unwrap_or(IrType::Primitive {
1696                    name: "f64".to_string(),
1697                }),
1698            evidence: IrEvidence::Known,
1699        },
1700        ast::Literal::String(s) | ast::Literal::MultiLineString(s) | ast::Literal::RawString(s) => {
1701            IrOperation::Literal {
1702                variant: LiteralVariant::String,
1703                value: serde_json::Value::String(s.clone()),
1704                ty: IrType::Primitive {
1705                    name: "str".to_string(),
1706                },
1707                evidence: IrEvidence::Known,
1708            }
1709        }
1710        ast::Literal::Char(c) => IrOperation::Literal {
1711            variant: LiteralVariant::Char,
1712            value: serde_json::Value::String(c.to_string()),
1713            ty: IrType::Primitive {
1714                name: "char".to_string(),
1715            },
1716            evidence: IrEvidence::Known,
1717        },
1718        ast::Literal::Bool(b) => IrOperation::Literal {
1719            variant: LiteralVariant::Bool,
1720            value: serde_json::Value::Bool(*b),
1721            ty: IrType::Primitive {
1722                name: "bool".to_string(),
1723            },
1724            evidence: IrEvidence::Known,
1725        },
1726        ast::Literal::Null | ast::Literal::Empty => IrOperation::Literal {
1727            variant: LiteralVariant::Null,
1728            value: serde_json::Value::Null,
1729            ty: IrType::Unit,
1730            evidence: IrEvidence::Known,
1731        },
1732        _ => IrOperation::Literal {
1733            variant: LiteralVariant::Null,
1734            value: serde_json::Value::Null,
1735            ty: IrType::Infer,
1736            evidence: IrEvidence::Known,
1737        },
1738    }
1739}
1740
1741fn lower_literal_value(lit: &ast::Literal) -> serde_json::Value {
1742    match lit {
1743        ast::Literal::Int { value, .. } => serde_json::json!(value),
1744        ast::Literal::Float { value, .. } => serde_json::json!(value),
1745        ast::Literal::String(s) => serde_json::Value::String(s.clone()),
1746        ast::Literal::Bool(b) => serde_json::Value::Bool(*b),
1747        ast::Literal::Char(c) => serde_json::Value::String(c.to_string()),
1748        ast::Literal::Null => serde_json::Value::Null,
1749        _ => serde_json::Value::Null,
1750    }
1751}
1752
1753fn lower_binop(op: ast::BinOp) -> BinaryOp {
1754    match op {
1755        ast::BinOp::Add => BinaryOp::Add,
1756        ast::BinOp::Sub => BinaryOp::Sub,
1757        ast::BinOp::Mul => BinaryOp::Mul,
1758        ast::BinOp::Div => BinaryOp::Div,
1759        ast::BinOp::Rem => BinaryOp::Rem,
1760        ast::BinOp::Pow => BinaryOp::Pow,
1761        ast::BinOp::And => BinaryOp::And,
1762        ast::BinOp::Or => BinaryOp::Or,
1763        ast::BinOp::BitAnd => BinaryOp::BitAnd,
1764        ast::BinOp::BitOr => BinaryOp::BitOr,
1765        ast::BinOp::BitXor => BinaryOp::BitXor,
1766        ast::BinOp::Shl => BinaryOp::Shl,
1767        ast::BinOp::Shr => BinaryOp::Shr,
1768        ast::BinOp::Eq => BinaryOp::Eq,
1769        ast::BinOp::Ne => BinaryOp::Ne,
1770        ast::BinOp::Lt => BinaryOp::Lt,
1771        ast::BinOp::Le => BinaryOp::Le,
1772        ast::BinOp::Gt => BinaryOp::Gt,
1773        ast::BinOp::Ge => BinaryOp::Ge,
1774        ast::BinOp::Concat => BinaryOp::Concat,
1775        ast::BinOp::MatMul => BinaryOp::MatMul,
1776        ast::BinOp::Hadamard => BinaryOp::Hadamard,
1777        ast::BinOp::TensorProd => BinaryOp::TensorProd,
1778        ast::BinOp::Convolve => BinaryOp::Convolve,
1779    }
1780}
1781
1782fn lower_unaryop(op: ast::UnaryOp) -> UnaryOp {
1783    match op {
1784        ast::UnaryOp::Neg => UnaryOp::Neg,
1785        ast::UnaryOp::Not => UnaryOp::Not,
1786        ast::UnaryOp::Deref => UnaryOp::Deref,
1787        ast::UnaryOp::Ref => UnaryOp::Ref,
1788        ast::UnaryOp::RefMut => UnaryOp::RefMut,
1789    }
1790}
1791
1792fn lower_morpheme_kind(kind: ast::MorphemeKind) -> MorphemeKind {
1793    match kind {
1794        ast::MorphemeKind::Transform => MorphemeKind::Transform,
1795        ast::MorphemeKind::Filter => MorphemeKind::Filter,
1796        ast::MorphemeKind::Sort => MorphemeKind::Sort,
1797        ast::MorphemeKind::Reduce => MorphemeKind::Reduce,
1798        ast::MorphemeKind::Lambda => MorphemeKind::Lambda,
1799        ast::MorphemeKind::Sum => MorphemeKind::Sum,
1800        ast::MorphemeKind::Product => MorphemeKind::Product,
1801        ast::MorphemeKind::First => MorphemeKind::First,
1802        ast::MorphemeKind::Last => MorphemeKind::Last,
1803        ast::MorphemeKind::Middle => MorphemeKind::Middle,
1804        ast::MorphemeKind::Choice => MorphemeKind::Choice,
1805        ast::MorphemeKind::Nth => MorphemeKind::Nth,
1806        ast::MorphemeKind::Next => MorphemeKind::Next,
1807    }
1808}
1809
1810fn morpheme_symbol(kind: ast::MorphemeKind) -> &'static str {
1811    match kind {
1812        ast::MorphemeKind::Transform => "τ",
1813        ast::MorphemeKind::Filter => "φ",
1814        ast::MorphemeKind::Sort => "σ",
1815        ast::MorphemeKind::Reduce => "ρ",
1816        ast::MorphemeKind::Lambda => "λ",
1817        ast::MorphemeKind::Sum => "Σ",
1818        ast::MorphemeKind::Product => "Π",
1819        ast::MorphemeKind::First => "α",
1820        ast::MorphemeKind::Last => "ω",
1821        ast::MorphemeKind::Middle => "μ",
1822        ast::MorphemeKind::Choice => "χ",
1823        ast::MorphemeKind::Nth => "ν",
1824        ast::MorphemeKind::Next => "ξ",
1825    }
1826}
1827
1828fn lower_http_method(method: ast::HttpMethod) -> HttpMethod {
1829    match method {
1830        ast::HttpMethod::Get => HttpMethod::Get,
1831        ast::HttpMethod::Post => HttpMethod::Post,
1832        ast::HttpMethod::Put => HttpMethod::Put,
1833        ast::HttpMethod::Delete => HttpMethod::Delete,
1834        ast::HttpMethod::Patch => HttpMethod::Patch,
1835        ast::HttpMethod::Head => HttpMethod::Head,
1836        ast::HttpMethod::Options => HttpMethod::Options,
1837        ast::HttpMethod::Connect => HttpMethod::Connect,
1838        ast::HttpMethod::Trace => HttpMethod::Trace,
1839    }
1840}
1841
1842fn get_operation_evidence(op: &IrOperation) -> IrEvidence {
1843    match op {
1844        IrOperation::Literal { evidence, .. }
1845        | IrOperation::Var { evidence, .. }
1846        | IrOperation::Let { evidence, .. }
1847        | IrOperation::Binary { evidence, .. }
1848        | IrOperation::Unary { evidence, .. }
1849        | IrOperation::Call { evidence, .. }
1850        | IrOperation::MethodCall { evidence, .. }
1851        | IrOperation::Closure { evidence, .. }
1852        | IrOperation::If { evidence, .. }
1853        | IrOperation::Match { evidence, .. }
1854        | IrOperation::LetMatch { evidence, .. }
1855        | IrOperation::Loop { evidence, .. }
1856        | IrOperation::Block { evidence, .. }
1857        | IrOperation::Pipeline { evidence, .. }
1858        | IrOperation::Morpheme { evidence, .. }
1859        | IrOperation::Fork { evidence, .. }
1860        | IrOperation::Identity { evidence, .. }
1861        | IrOperation::Array { evidence, .. }
1862        | IrOperation::Tuple { evidence, .. }
1863        | IrOperation::StructInit { evidence, .. }
1864        | IrOperation::Field { evidence, .. }
1865        | IrOperation::Index { evidence, .. }
1866        | IrOperation::Assign { evidence, .. }
1867        | IrOperation::Break { evidence, .. }
1868        | IrOperation::Continue { evidence }
1869        | IrOperation::Return { evidence, .. }
1870        | IrOperation::Incorporation { evidence, .. }
1871        | IrOperation::Affect { evidence, .. }
1872        | IrOperation::HttpRequest { evidence, .. }
1873        | IrOperation::GrpcCall { evidence, .. }
1874        | IrOperation::WebSocket { evidence, .. }
1875        | IrOperation::KafkaOp { evidence, .. }
1876        | IrOperation::Await { evidence, .. }
1877        | IrOperation::Unsafe { evidence, .. }
1878        | IrOperation::Async { evidence, .. }
1879        | IrOperation::Cast { evidence, .. }
1880        | IrOperation::Try { evidence, .. } => *evidence,
1881        IrOperation::EvidenceCoerce { to_evidence, .. } => *to_evidence,
1882    }
1883}
1884
1885fn expr_to_string(e: &ast::Expr) -> String {
1886    match e {
1887        ast::Expr::Path(p) => p
1888            .segments
1889            .iter()
1890            .map(|s| s.ident.name.clone())
1891            .collect::<Vec<_>>()
1892            .join("::"),
1893        ast::Expr::Literal(ast::Literal::String(s)) => s.clone(),
1894        _ => "dynamic".to_string(),
1895    }
1896}
1897
1898// === Type definitions lowering ===
1899
1900fn lower_struct_def(_ctx: &mut LoweringContext, s: &ast::StructDef) -> IrTypeDef {
1901    let fields = match &s.fields {
1902        ast::StructFields::Named(fields) => fields
1903            .iter()
1904            .map(|f| IrField {
1905                name: f.name.name.clone(),
1906                ty: lower_type_expr(&f.ty),
1907                visibility: lower_visibility(f.visibility),
1908            })
1909            .collect(),
1910        ast::StructFields::Tuple(types) => types
1911            .iter()
1912            .enumerate()
1913            .map(|(i, t)| IrField {
1914                name: format!("{}", i),
1915                ty: lower_type_expr(t),
1916                visibility: IrVisibility::Public,
1917            })
1918            .collect(),
1919        ast::StructFields::Unit => vec![],
1920    };
1921
1922    IrTypeDef::Struct {
1923        name: s.name.name.clone(),
1924        generics: lower_generics(&s.generics),
1925        fields,
1926        span: None,
1927    }
1928}
1929
1930fn lower_enum_def(_ctx: &mut LoweringContext, e: &ast::EnumDef) -> IrTypeDef {
1931    let variants = e
1932        .variants
1933        .iter()
1934        .map(|v| {
1935            let fields = match &v.fields {
1936                ast::StructFields::Named(fields) => Some(
1937                    fields
1938                        .iter()
1939                        .map(|f| IrField {
1940                            name: f.name.name.clone(),
1941                            ty: lower_type_expr(&f.ty),
1942                            visibility: lower_visibility(f.visibility),
1943                        })
1944                        .collect(),
1945                ),
1946                ast::StructFields::Tuple(types) => Some(
1947                    types
1948                        .iter()
1949                        .enumerate()
1950                        .map(|(i, t)| IrField {
1951                            name: format!("{}", i),
1952                            ty: lower_type_expr(t),
1953                            visibility: IrVisibility::Public,
1954                        })
1955                        .collect(),
1956                ),
1957                ast::StructFields::Unit => None,
1958            };
1959
1960            IrVariant {
1961                name: v.name.name.clone(),
1962                fields,
1963                discriminant: None,
1964            }
1965        })
1966        .collect();
1967
1968    IrTypeDef::Enum {
1969        name: e.name.name.clone(),
1970        generics: lower_generics(&e.generics),
1971        variants,
1972        span: None,
1973    }
1974}
1975
1976fn lower_type_alias(_ctx: &mut LoweringContext, t: &ast::TypeAlias) -> IrTypeDef {
1977    IrTypeDef::TypeAlias {
1978        name: t.name.name.clone(),
1979        generics: lower_generics(&t.generics),
1980        target: lower_type_expr(&t.ty),
1981        span: None,
1982    }
1983}
1984
1985fn lower_trait_def(ctx: &mut LoweringContext, t: &ast::TraitDef) -> IrTraitDef {
1986    let methods = t
1987        .items
1988        .iter()
1989        .filter_map(|item| match item {
1990            ast::TraitItem::Function(f) => lower_function(ctx, f),
1991            _ => None,
1992        })
1993        .collect();
1994
1995    IrTraitDef {
1996        name: t.name.name.clone(),
1997        generics: lower_generics(&t.generics),
1998        super_traits: t.supertraits.iter().map(type_expr_to_string).collect(),
1999        methods,
2000        span: None,
2001    }
2002}
2003
2004fn lower_impl_block(ctx: &mut LoweringContext, i: &ast::ImplBlock) -> IrImplBlock {
2005    let methods = i
2006        .items
2007        .iter()
2008        .filter_map(|item| match item {
2009            ast::ImplItem::Function(f) => lower_function(ctx, f),
2010            _ => None,
2011        })
2012        .collect();
2013
2014    IrImplBlock {
2015        trait_name: i.trait_.as_ref().map(|t| {
2016            t.segments
2017                .iter()
2018                .map(|s| s.ident.name.clone())
2019                .collect::<Vec<_>>()
2020                .join("::")
2021        }),
2022        target_type: lower_type_expr(&i.self_ty),
2023        generics: lower_generics(&i.generics),
2024        methods,
2025        span: None,
2026    }
2027}
2028
2029fn lower_const_def(ctx: &mut LoweringContext, c: &ast::ConstDef) -> IrConstant {
2030    IrConstant {
2031        name: c.name.name.clone(),
2032        ty: lower_type_expr(&c.ty),
2033        value: lower_expr(ctx, &c.value),
2034        visibility: lower_visibility(c.visibility),
2035        span: None,
2036    }
2037}