Skip to main content

sigil_parser/
lower.rs

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