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
95fn lower_item(ctx: &mut LoweringContext, module: &mut IrModule, item: &ast::Item) {
96    match item {
97        ast::Item::Function(f) => {
98            if let Some(ir_fn) = lower_function(ctx, f) {
99                module.functions.push(ir_fn);
100            }
101        }
102        ast::Item::Struct(s) => {
103            module.types.push(lower_struct_def(ctx, s));
104        }
105        ast::Item::Enum(e) => {
106            module.types.push(lower_enum_def(ctx, e));
107        }
108        ast::Item::Trait(t) => {
109            module.traits.push(lower_trait_def(ctx, t));
110        }
111        ast::Item::Impl(i) => {
112            module.impls.push(lower_impl_block(ctx, i));
113        }
114        ast::Item::TypeAlias(t) => {
115            module.types.push(lower_type_alias(ctx, t));
116        }
117        ast::Item::Const(c) => {
118            module.constants.push(lower_const_def(ctx, c));
119        }
120        ast::Item::Module(m) => {
121            if let Some(items) = &m.items {
122                for item in items {
123                    lower_item(ctx, module, &item.node);
124                }
125            }
126        }
127        ast::Item::Static(_)
128        | ast::Item::Actor(_)
129        | ast::Item::Use(_)
130        | ast::Item::ExternBlock(_) => {
131            // TODO: Handle these items
132        }
133    }
134}
135
136fn lower_function(ctx: &mut LoweringContext, f: &ast::Function) -> Option<IrFunction> {
137    let id = ctx.ids.next("fn");
138    ctx.push_scope();
139
140    let params: Vec<IrParam> = f.params.iter().map(|p| lower_param(ctx, p)).collect();
141
142    let return_type = f
143        .return_type
144        .as_ref()
145        .map(|t| lower_type_expr(t))
146        .unwrap_or(IrType::Unit);
147
148    let body = f.body.as_ref().map(|b| lower_block(ctx, b));
149
150    let mut attributes = Vec::new();
151    if f.is_async {
152        attributes.push("async".to_string());
153    }
154    if f.attrs.inline.is_some() {
155        attributes.push("inline".to_string());
156    }
157    if f.attrs.naked {
158        attributes.push("naked".to_string());
159    }
160    if f.attrs.no_mangle {
161        attributes.push("no_mangle".to_string());
162    }
163
164    ctx.pop_scope();
165
166    Some(IrFunction {
167        name: f.name.name.clone(),
168        id,
169        visibility: lower_visibility(f.visibility),
170        generics: lower_generics(&f.generics),
171        params,
172        return_type,
173        body,
174        attributes,
175        is_async: f.is_async,
176        span: None,
177    })
178}
179
180fn lower_param(ctx: &mut LoweringContext, p: &ast::Param) -> IrParam {
181    let name = extract_pattern_name(&p.pattern);
182    let evidence = extract_pattern_evidence(&p.pattern);
183    ctx.bind_var(&name);
184
185    IrParam {
186        name,
187        ty: lower_type_expr(&p.ty),
188        evidence,
189    }
190}
191
192fn extract_pattern_name(p: &ast::Pattern) -> String {
193    match p {
194        ast::Pattern::Ident { name, .. } => name.name.clone(),
195        ast::Pattern::Tuple(pats) if !pats.is_empty() => extract_pattern_name(&pats[0]),
196        _ => "_".to_string(),
197    }
198}
199
200fn extract_pattern_evidence(p: &ast::Pattern) -> IrEvidence {
201    match p {
202        ast::Pattern::Ident { evidentiality, .. } => evidentiality
203            .map(lower_evidentiality)
204            .unwrap_or(IrEvidence::Known),
205        _ => IrEvidence::Known,
206    }
207}
208
209fn lower_visibility(v: ast::Visibility) -> IrVisibility {
210    match v {
211        ast::Visibility::Public => IrVisibility::Public,
212        ast::Visibility::Private => IrVisibility::Private,
213        ast::Visibility::Crate => IrVisibility::Crate,
214        ast::Visibility::Super => IrVisibility::Private,
215    }
216}
217
218fn lower_evidentiality(e: ast::Evidentiality) -> IrEvidence {
219    match e {
220        ast::Evidentiality::Known => IrEvidence::Known,
221        ast::Evidentiality::Uncertain => IrEvidence::Uncertain,
222        ast::Evidentiality::Reported => IrEvidence::Reported,
223        ast::Evidentiality::Paradox => IrEvidence::Paradox,
224    }
225}
226
227fn lower_generics(g: &Option<ast::Generics>) -> Vec<IrGenericParam> {
228    g.as_ref()
229        .map(|g| {
230            g.params
231                .iter()
232                .filter_map(|p| match p {
233                    ast::GenericParam::Type { name, bounds, .. } => Some(IrGenericParam {
234                        name: name.name.clone(),
235                        bounds: bounds.iter().map(type_expr_to_string).collect(),
236                    }),
237                    ast::GenericParam::Const { name, .. } => Some(IrGenericParam {
238                        name: name.name.clone(),
239                        bounds: vec!["const".to_string()],
240                    }),
241                    ast::GenericParam::Lifetime(l) => Some(IrGenericParam {
242                        name: l.clone(),
243                        bounds: vec!["lifetime".to_string()],
244                    }),
245                })
246                .collect()
247        })
248        .unwrap_or_default()
249}
250
251fn type_expr_to_string(t: &ast::TypeExpr) -> String {
252    match t {
253        ast::TypeExpr::Path(p) => p
254            .segments
255            .iter()
256            .map(|s| s.ident.name.clone())
257            .collect::<Vec<_>>()
258            .join("::"),
259        _ => "?".to_string(),
260    }
261}
262
263fn lower_type_expr(t: &ast::TypeExpr) -> IrType {
264    match t {
265        ast::TypeExpr::Path(p) => {
266            let name = p
267                .segments
268                .iter()
269                .map(|s| s.ident.name.clone())
270                .collect::<Vec<_>>()
271                .join("::");
272
273            // Check for primitive types
274            match name.as_str() {
275                "i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" | "u16" | "u32" | "u64"
276                | "u128" | "usize" | "f32" | "f64" | "bool" | "char" | "str" => {
277                    IrType::Primitive { name }
278                }
279                "()" => IrType::Unit,
280                "!" | "never" => IrType::Never,
281                _ => {
282                    let generics = p
283                        .segments
284                        .last()
285                        .and_then(|s| s.generics.as_ref())
286                        .map(|gs| gs.iter().map(lower_type_expr).collect())
287                        .unwrap_or_default();
288
289                    IrType::Named { name, generics }
290                }
291            }
292        }
293        ast::TypeExpr::Reference { mutable, inner } => IrType::Reference {
294            mutable: *mutable,
295            inner: Box::new(lower_type_expr(inner)),
296        },
297        ast::TypeExpr::Pointer { mutable, inner } => IrType::Pointer {
298            mutable: *mutable,
299            inner: Box::new(lower_type_expr(inner)),
300        },
301        ast::TypeExpr::Array { element, size: _ } => IrType::Array {
302            element: Box::new(lower_type_expr(element)),
303            size: None, // Would need const eval
304        },
305        ast::TypeExpr::Slice(inner) => IrType::Slice {
306            element: Box::new(lower_type_expr(inner)),
307        },
308        ast::TypeExpr::Tuple(elements) => IrType::Tuple {
309            elements: elements.iter().map(lower_type_expr).collect(),
310        },
311        ast::TypeExpr::Function {
312            params,
313            return_type,
314        } => IrType::Function {
315            params: params.iter().map(lower_type_expr).collect(),
316            return_type: Box::new(
317                return_type
318                    .as_ref()
319                    .map(|r| lower_type_expr(r))
320                    .unwrap_or(IrType::Unit),
321            ),
322            is_async: false,
323        },
324        ast::TypeExpr::Evidential {
325            inner,
326            evidentiality,
327            error_type,
328        } => {
329            // If error_type is specified (e.g., T?[Error]), this is Result sugar
330            // For now, we lower it as an evidential type; full Result expansion would happen later
331            let _ = error_type; // TODO: expand to Result<T, E> in type system
332            IrType::Evidential {
333                inner: Box::new(lower_type_expr(inner)),
334                evidence: lower_evidentiality(*evidentiality),
335            }
336        }
337        ast::TypeExpr::Cycle { .. } => IrType::Cycle { modulus: 0 },
338        ast::TypeExpr::Simd { element, lanes } => IrType::Simd {
339            element: Box::new(lower_type_expr(element)),
340            lanes: *lanes as usize,
341        },
342        ast::TypeExpr::Atomic(inner) => IrType::Atomic {
343            inner: Box::new(lower_type_expr(inner)),
344        },
345        ast::TypeExpr::Never => IrType::Never,
346        ast::TypeExpr::Infer => IrType::Infer,
347    }
348}
349
350fn lower_block(ctx: &mut LoweringContext, block: &ast::Block) -> IrOperation {
351    ctx.push_scope();
352
353    let mut statements: Vec<IrOperation> = block
354        .stmts
355        .iter()
356        .filter_map(|s| lower_stmt(ctx, s))
357        .collect();
358
359    if let Some(expr) = &block.expr {
360        statements.push(lower_expr(ctx, expr));
361    }
362
363    ctx.pop_scope();
364
365    IrOperation::Block {
366        statements,
367        ty: IrType::Infer,
368        evidence: IrEvidence::Known,
369    }
370}
371
372fn lower_stmt(ctx: &mut LoweringContext, stmt: &ast::Stmt) -> Option<IrOperation> {
373    match stmt {
374        ast::Stmt::Let { pattern, ty, init } => {
375            let ir_pattern = lower_pattern(ctx, pattern);
376            let type_annotation = ty.as_ref().map(lower_type_expr);
377            let init_expr = init
378                .as_ref()
379                .map(|e| Box::new(lower_expr(ctx, e)))
380                .unwrap_or_else(|| {
381                    Box::new(IrOperation::Literal {
382                        variant: LiteralVariant::Null,
383                        value: serde_json::Value::Null,
384                        ty: IrType::Unit,
385                        evidence: IrEvidence::Known,
386                    })
387                });
388
389            Some(IrOperation::Let {
390                pattern: ir_pattern,
391                type_annotation,
392                init: init_expr,
393                evidence: IrEvidence::Known,
394            })
395        }
396        ast::Stmt::Expr(e) => Some(lower_expr(ctx, e)),
397        ast::Stmt::Semi(e) => Some(lower_expr(ctx, e)),
398        ast::Stmt::Item(_) => None, // Handled at module level
399    }
400}
401
402fn lower_pattern(ctx: &mut LoweringContext, pattern: &ast::Pattern) -> IrPattern {
403    match pattern {
404        ast::Pattern::Ident {
405            mutable,
406            name,
407            evidentiality,
408        } => {
409            ctx.bind_var(&name.name);
410            IrPattern::Ident {
411                name: name.name.clone(),
412                mutable: *mutable,
413                evidence: evidentiality.map(lower_evidentiality),
414            }
415        }
416        ast::Pattern::Tuple(pats) => IrPattern::Tuple {
417            elements: pats.iter().map(|p| lower_pattern(ctx, p)).collect(),
418        },
419        ast::Pattern::Struct { path, fields, rest } => IrPattern::Struct {
420            path: path
421                .segments
422                .iter()
423                .map(|s| s.ident.name.clone())
424                .collect::<Vec<_>>()
425                .join("::"),
426            fields: fields
427                .iter()
428                .map(|f| {
429                    (
430                        f.name.name.clone(),
431                        f.pattern.as_ref().map(|p| lower_pattern(ctx, p)).unwrap_or(
432                            IrPattern::Ident {
433                                name: f.name.name.clone(),
434                                mutable: false,
435                                evidence: None,
436                            },
437                        ),
438                    )
439                })
440                .collect(),
441            rest: *rest,
442        },
443        ast::Pattern::TupleStruct { path, fields } => IrPattern::TupleStruct {
444            path: path
445                .segments
446                .iter()
447                .map(|s| s.ident.name.clone())
448                .collect::<Vec<_>>()
449                .join("::"),
450            fields: fields.iter().map(|p| lower_pattern(ctx, p)).collect(),
451        },
452        ast::Pattern::Slice(pats) => IrPattern::Slice {
453            elements: pats.iter().map(|p| lower_pattern(ctx, p)).collect(),
454        },
455        ast::Pattern::Or(pats) => IrPattern::Or {
456            patterns: pats.iter().map(|p| lower_pattern(ctx, p)).collect(),
457        },
458        ast::Pattern::Literal(lit) => IrPattern::Literal {
459            value: lower_literal_value(lit),
460        },
461        ast::Pattern::Range {
462            start,
463            end,
464            inclusive,
465        } => IrPattern::Range {
466            start: start.as_ref().map(|p| Box::new(lower_pattern(ctx, p))),
467            end: end.as_ref().map(|p| Box::new(lower_pattern(ctx, p))),
468            inclusive: *inclusive,
469        },
470        ast::Pattern::Wildcard | ast::Pattern::Rest => IrPattern::Wildcard,
471    }
472}
473
474fn lower_expr(ctx: &mut LoweringContext, expr: &ast::Expr) -> IrOperation {
475    match expr {
476        ast::Expr::Literal(lit) => lower_literal(lit),
477
478        ast::Expr::Path(path) => {
479            let name = path
480                .segments
481                .iter()
482                .map(|s| s.ident.name.clone())
483                .collect::<Vec<_>>()
484                .join("::");
485            let id = ctx.lookup_var(&name).unwrap_or_else(|| name.clone());
486
487            IrOperation::Var {
488                name,
489                id,
490                ty: IrType::Infer,
491                evidence: IrEvidence::Known,
492            }
493        }
494
495        ast::Expr::Binary { left, op, right } => {
496            let left_ir = lower_expr(ctx, left);
497            let right_ir = lower_expr(ctx, right);
498            let left_ev = get_operation_evidence(&left_ir);
499            let right_ev = get_operation_evidence(&right_ir);
500
501            IrOperation::Binary {
502                operator: lower_binop(*op),
503                left: Box::new(left_ir),
504                right: Box::new(right_ir),
505                ty: IrType::Infer,
506                evidence: left_ev.join(right_ev),
507            }
508        }
509
510        ast::Expr::Unary { op, expr: inner } => {
511            let inner_ir = lower_expr(ctx, inner);
512            let evidence = get_operation_evidence(&inner_ir);
513
514            IrOperation::Unary {
515                operator: lower_unaryop(*op),
516                operand: Box::new(inner_ir),
517                ty: IrType::Infer,
518                evidence,
519            }
520        }
521
522        ast::Expr::Call { func, args } => {
523            let func_name = match func.as_ref() {
524                ast::Expr::Path(p) => p
525                    .segments
526                    .iter()
527                    .map(|s| s.ident.name.clone())
528                    .collect::<Vec<_>>()
529                    .join("::"),
530                _ => "anonymous".to_string(),
531            };
532
533            let args_ir: Vec<IrOperation> = args.iter().map(|a| lower_expr(ctx, a)).collect();
534            let evidence = args_ir
535                .iter()
536                .map(get_operation_evidence)
537                .fold(IrEvidence::Known, |acc, e| acc.join(e));
538
539            IrOperation::Call {
540                function: func_name.clone(),
541                function_id: format!("fn_{}", func_name),
542                args: args_ir,
543                type_args: vec![],
544                ty: IrType::Infer,
545                evidence,
546            }
547        }
548
549        ast::Expr::MethodCall {
550            receiver,
551            method,
552            args,
553        } => {
554            let receiver_ir = lower_expr(ctx, receiver);
555            let args_ir: Vec<IrOperation> = args.iter().map(|a| lower_expr(ctx, a)).collect();
556            let evidence = std::iter::once(get_operation_evidence(&receiver_ir))
557                .chain(args_ir.iter().map(get_operation_evidence))
558                .fold(IrEvidence::Known, |acc, e| acc.join(e));
559
560            IrOperation::MethodCall {
561                receiver: Box::new(receiver_ir),
562                method: method.name.clone(),
563                args: args_ir,
564                type_args: vec![],
565                ty: IrType::Infer,
566                evidence,
567            }
568        }
569
570        ast::Expr::Pipe { expr, operations } => lower_pipeline(ctx, expr, operations),
571
572        ast::Expr::If {
573            condition,
574            then_branch,
575            else_branch,
576        } => {
577            let cond_ir = lower_expr(ctx, condition);
578            let then_ir = lower_block(ctx, then_branch);
579            let else_ir = else_branch.as_ref().map(|e| Box::new(lower_expr(ctx, e)));
580
581            let evidence = get_operation_evidence(&cond_ir)
582                .join(get_operation_evidence(&then_ir))
583                .join(
584                    else_ir
585                        .as_ref()
586                        .map(|e| get_operation_evidence(e))
587                        .unwrap_or(IrEvidence::Known),
588                );
589
590            IrOperation::If {
591                condition: Box::new(cond_ir),
592                then_branch: Box::new(then_ir),
593                else_branch: else_ir,
594                ty: IrType::Infer,
595                evidence,
596            }
597        }
598
599        ast::Expr::Match {
600            expr: scrutinee,
601            arms,
602        } => {
603            let scrutinee_ir = lower_expr(ctx, scrutinee);
604            let arms_ir: Vec<IrMatchArm> = arms
605                .iter()
606                .map(|arm| {
607                    ctx.push_scope();
608                    let pattern = lower_pattern(ctx, &arm.pattern);
609                    let guard = arm.guard.as_ref().map(|g| lower_expr(ctx, g));
610                    let body = lower_expr(ctx, &arm.body);
611                    ctx.pop_scope();
612                    IrMatchArm {
613                        pattern,
614                        guard,
615                        body,
616                    }
617                })
618                .collect();
619
620            IrOperation::Match {
621                scrutinee: Box::new(scrutinee_ir),
622                arms: arms_ir,
623                ty: IrType::Infer,
624                evidence: IrEvidence::Known,
625            }
626        }
627
628        ast::Expr::Loop(block) => IrOperation::Loop {
629            variant: LoopVariant::Infinite,
630            condition: None,
631            iterator: None,
632            body: Box::new(lower_block(ctx, block)),
633            ty: IrType::Never,
634            evidence: IrEvidence::Known,
635        },
636
637        ast::Expr::While { condition, body } => IrOperation::Loop {
638            variant: LoopVariant::While,
639            condition: Some(Box::new(lower_expr(ctx, condition))),
640            iterator: None,
641            body: Box::new(lower_block(ctx, body)),
642            ty: IrType::Unit,
643            evidence: IrEvidence::Known,
644        },
645
646        ast::Expr::For {
647            pattern,
648            iter,
649            body,
650        } => {
651            ctx.push_scope();
652            let pat = lower_pattern(ctx, pattern);
653            let iter_ir = lower_expr(ctx, iter);
654            let body_ir = lower_block(ctx, body);
655            ctx.pop_scope();
656
657            IrOperation::Loop {
658                variant: LoopVariant::For,
659                condition: None,
660                iterator: Some(IrForIterator {
661                    pattern: pat,
662                    iterable: Box::new(iter_ir),
663                }),
664                body: Box::new(body_ir),
665                ty: IrType::Unit,
666                evidence: IrEvidence::Known,
667            }
668        }
669
670        ast::Expr::Closure { params, body } => {
671            ctx.push_scope();
672            let params_ir: Vec<IrParam> = params
673                .iter()
674                .map(|p| {
675                    let name = extract_pattern_name(&p.pattern);
676                    ctx.bind_var(&name);
677                    IrParam {
678                        name,
679                        ty: p.ty.as_ref().map(lower_type_expr).unwrap_or(IrType::Infer),
680                        evidence: IrEvidence::Known,
681                    }
682                })
683                .collect();
684            let body_ir = lower_expr(ctx, body);
685            ctx.pop_scope();
686
687            IrOperation::Closure {
688                params: params_ir,
689                body: Box::new(body_ir),
690                captures: vec![],
691                ty: IrType::Infer,
692                evidence: IrEvidence::Known,
693            }
694        }
695
696        ast::Expr::Block(block) => lower_block(ctx, block),
697
698        ast::Expr::Array(elements) => {
699            let elements_ir: Vec<IrOperation> =
700                elements.iter().map(|e| lower_expr(ctx, e)).collect();
701            IrOperation::Array {
702                elements: elements_ir,
703                ty: IrType::Infer,
704                evidence: IrEvidence::Known,
705            }
706        }
707
708        ast::Expr::Tuple(elements) => {
709            let elements_ir: Vec<IrOperation> =
710                elements.iter().map(|e| lower_expr(ctx, e)).collect();
711            IrOperation::Tuple {
712                elements: elements_ir,
713                ty: IrType::Infer,
714                evidence: IrEvidence::Known,
715            }
716        }
717
718        ast::Expr::Struct { path, fields, rest } => {
719            let name = path
720                .segments
721                .iter()
722                .map(|s| s.ident.name.clone())
723                .collect::<Vec<_>>()
724                .join("::");
725
726            let fields_ir: Vec<(String, IrOperation)> = fields
727                .iter()
728                .map(|f| {
729                    let value = f
730                        .value
731                        .as_ref()
732                        .map(|v| lower_expr(ctx, v))
733                        .unwrap_or_else(|| IrOperation::Var {
734                            name: f.name.name.clone(),
735                            id: ctx.lookup_var(&f.name.name).unwrap_or_default(),
736                            ty: IrType::Infer,
737                            evidence: IrEvidence::Known,
738                        });
739                    (f.name.name.clone(), value)
740                })
741                .collect();
742
743            IrOperation::StructInit {
744                name,
745                fields: fields_ir,
746                rest: rest.as_ref().map(|r| Box::new(lower_expr(ctx, r))),
747                ty: IrType::Infer,
748                evidence: IrEvidence::Known,
749            }
750        }
751
752        ast::Expr::Field { expr: inner, field } => IrOperation::Field {
753            expr: Box::new(lower_expr(ctx, inner)),
754            field: field.name.clone(),
755            ty: IrType::Infer,
756            evidence: IrEvidence::Known,
757        },
758
759        ast::Expr::Index { expr: inner, index } => IrOperation::Index {
760            expr: Box::new(lower_expr(ctx, inner)),
761            index: Box::new(lower_expr(ctx, index)),
762            ty: IrType::Infer,
763            evidence: IrEvidence::Known,
764        },
765
766        ast::Expr::Assign { target, value } => IrOperation::Assign {
767            target: Box::new(lower_expr(ctx, target)),
768            value: Box::new(lower_expr(ctx, value)),
769            evidence: IrEvidence::Known,
770        },
771
772        ast::Expr::Return(value) => IrOperation::Return {
773            value: value.as_ref().map(|v| Box::new(lower_expr(ctx, v))),
774            evidence: IrEvidence::Known,
775        },
776
777        ast::Expr::Break(value) => IrOperation::Break {
778            value: value.as_ref().map(|v| Box::new(lower_expr(ctx, v))),
779            evidence: IrEvidence::Known,
780        },
781
782        ast::Expr::Continue => IrOperation::Continue {
783            evidence: IrEvidence::Known,
784        },
785
786        ast::Expr::Await {
787            expr: inner,
788            evidentiality,
789        } => {
790            let inner_ir = lower_expr(ctx, inner);
791            // Use the explicit evidentiality marker if provided, otherwise infer from inner
792            let evidence = match evidentiality {
793                Some(ast::Evidentiality::Known) => IrEvidence::Known,
794                Some(ast::Evidentiality::Uncertain) => IrEvidence::Uncertain,
795                Some(ast::Evidentiality::Reported) => IrEvidence::Reported,
796                Some(ast::Evidentiality::Paradox) => IrEvidence::Uncertain, // Trust boundary
797                None => get_operation_evidence(&inner_ir),
798            };
799            IrOperation::Await {
800                expr: Box::new(inner_ir),
801                ty: IrType::Infer,
802                evidence,
803            }
804        }
805
806        ast::Expr::Try(inner) => {
807            let inner_ir = lower_expr(ctx, inner);
808            IrOperation::Try {
809                expr: Box::new(inner_ir),
810                ty: IrType::Infer,
811                evidence: IrEvidence::Uncertain, // Try always produces uncertain
812            }
813        }
814
815        ast::Expr::Unsafe(block) => IrOperation::Unsafe {
816            body: Box::new(lower_block(ctx, block)),
817            ty: IrType::Infer,
818            evidence: IrEvidence::Paradox, // Unsafe always produces paradox
819        },
820
821        ast::Expr::Cast { expr: inner, ty } => {
822            let inner_ir = lower_expr(ctx, inner);
823            let evidence = get_operation_evidence(&inner_ir);
824            IrOperation::Cast {
825                expr: Box::new(inner_ir),
826                target_type: lower_type_expr(ty),
827                ty: lower_type_expr(ty),
828                evidence,
829            }
830        }
831
832        ast::Expr::Evidential {
833            expr: inner,
834            evidentiality,
835        } => {
836            let inner_ir = lower_expr(ctx, inner);
837            let from_evidence = get_operation_evidence(&inner_ir);
838            let to_evidence = lower_evidentiality(*evidentiality);
839
840            IrOperation::EvidenceCoerce {
841                operation: EvidenceOp::Mark,
842                expr: Box::new(inner_ir),
843                from_evidence,
844                to_evidence,
845                ty: IrType::Infer,
846            }
847        }
848
849        ast::Expr::Morpheme { kind, body } => {
850            let body_ir = lower_expr(ctx, body);
851            IrOperation::Morpheme {
852                morpheme: lower_morpheme_kind(*kind),
853                symbol: morpheme_symbol(*kind).to_string(),
854                input: Box::new(IrOperation::Var {
855                    name: "_".to_string(),
856                    id: "implicit_input".to_string(),
857                    ty: IrType::Infer,
858                    evidence: IrEvidence::Known,
859                }),
860                body: Some(Box::new(body_ir)),
861                ty: IrType::Infer,
862                evidence: IrEvidence::Known,
863            }
864        }
865
866        ast::Expr::Incorporation { segments } => {
867            let segs: Vec<IncorporationSegment> = segments
868                .iter()
869                .map(|s| {
870                    if s.args.is_some() {
871                        IncorporationSegment::Verb {
872                            name: s.name.name.clone(),
873                        }
874                    } else {
875                        IncorporationSegment::Noun {
876                            name: s.name.name.clone(),
877                        }
878                    }
879                })
880                .collect();
881
882            // Collect all args from all segments
883            let mut args: Vec<IrOperation> = Vec::new();
884            for seg in segments {
885                if let Some(ref seg_args) = seg.args {
886                    for a in seg_args {
887                        args.push(lower_expr(ctx, a));
888                    }
889                }
890            }
891
892            IrOperation::Incorporation {
893                segments: segs,
894                args,
895                ty: IrType::Infer,
896                evidence: IrEvidence::Known,
897            }
898        }
899
900        // Protocol operations - all yield Reported evidence
901        ast::Expr::HttpRequest {
902            method,
903            url,
904            headers,
905            body,
906            timeout,
907        } => IrOperation::HttpRequest {
908            method: lower_http_method(*method),
909            url: Box::new(lower_expr(ctx, url)),
910            headers: if headers.is_empty() {
911                None
912            } else {
913                Some(Box::new(IrOperation::Array {
914                    elements: headers
915                        .iter()
916                        .map(|(k, v)| IrOperation::Tuple {
917                            elements: vec![lower_expr(ctx, k), lower_expr(ctx, v)],
918                            ty: IrType::Infer,
919                            evidence: IrEvidence::Known,
920                        })
921                        .collect(),
922                    ty: IrType::Infer,
923                    evidence: IrEvidence::Known,
924                }))
925            },
926            body: body.as_ref().map(|b| Box::new(lower_expr(ctx, b))),
927            timeout: timeout.as_ref().map(|t| Box::new(lower_expr(ctx, t))),
928            ty: IrType::Infer,
929            evidence: IrEvidence::Reported,
930        },
931
932        ast::Expr::GrpcCall {
933            service,
934            method,
935            message,
936            metadata,
937            timeout,
938        } => IrOperation::GrpcCall {
939            service: expr_to_string(service),
940            method: expr_to_string(method),
941            message: Box::new(message.as_ref().map(|m| lower_expr(ctx, m)).unwrap_or(
942                IrOperation::Literal {
943                    variant: LiteralVariant::Null,
944                    value: serde_json::Value::Null,
945                    ty: IrType::Unit,
946                    evidence: IrEvidence::Known,
947                },
948            )),
949            metadata: if metadata.is_empty() {
950                None
951            } else {
952                Some(Box::new(IrOperation::Array {
953                    elements: metadata
954                        .iter()
955                        .map(|(k, v)| IrOperation::Tuple {
956                            elements: vec![lower_expr(ctx, k), lower_expr(ctx, v)],
957                            ty: IrType::Infer,
958                            evidence: IrEvidence::Known,
959                        })
960                        .collect(),
961                    ty: IrType::Infer,
962                    evidence: IrEvidence::Known,
963                }))
964            },
965            timeout: timeout.as_ref().map(|t| Box::new(lower_expr(ctx, t))),
966            ty: IrType::Infer,
967            evidence: IrEvidence::Reported,
968        },
969
970        // Handle remaining expression types with placeholders
971        _ => IrOperation::Literal {
972            variant: LiteralVariant::Null,
973            value: serde_json::json!({"unhandled": format!("{:?}", std::mem::discriminant(expr))}),
974            ty: IrType::Infer,
975            evidence: IrEvidence::Known,
976        },
977    }
978}
979
980fn lower_pipeline(
981    ctx: &mut LoweringContext,
982    input: &ast::Expr,
983    operations: &[ast::PipeOp],
984) -> IrOperation {
985    let input_ir = lower_expr(ctx, input);
986    let mut evidence = get_operation_evidence(&input_ir);
987
988    let steps: Vec<IrPipelineStep> = operations
989        .iter()
990        .map(|op| {
991            let step = lower_pipe_op(ctx, op);
992            // Update evidence based on operation
993            evidence = evidence.join(pipe_op_evidence(op));
994            step
995        })
996        .collect();
997
998    IrOperation::Pipeline {
999        input: Box::new(input_ir),
1000        steps,
1001        ty: IrType::Infer,
1002        evidence,
1003    }
1004}
1005
1006fn lower_pipe_op(ctx: &mut LoweringContext, op: &ast::PipeOp) -> IrPipelineStep {
1007    match op {
1008        ast::PipeOp::Transform(body) => IrPipelineStep::Morpheme {
1009            morpheme: MorphemeKind::Transform,
1010            symbol: "τ".to_string(),
1011            body: Some(Box::new(lower_expr(ctx, body))),
1012        },
1013        ast::PipeOp::Filter(body) => IrPipelineStep::Morpheme {
1014            morpheme: MorphemeKind::Filter,
1015            symbol: "φ".to_string(),
1016            body: Some(Box::new(lower_expr(ctx, body))),
1017        },
1018        ast::PipeOp::Sort(field) => IrPipelineStep::Morpheme {
1019            morpheme: MorphemeKind::Sort,
1020            symbol: "σ".to_string(),
1021            body: field.as_ref().map(|f| {
1022                Box::new(IrOperation::Var {
1023                    name: f.name.clone(),
1024                    id: f.name.clone(),
1025                    ty: IrType::Infer,
1026                    evidence: IrEvidence::Known,
1027                })
1028            }),
1029        },
1030        ast::PipeOp::Reduce(body) => IrPipelineStep::Morpheme {
1031            morpheme: MorphemeKind::Reduce,
1032            symbol: "ρ".to_string(),
1033            body: Some(Box::new(lower_expr(ctx, body))),
1034        },
1035        ast::PipeOp::ReduceSum => IrPipelineStep::Morpheme {
1036            morpheme: MorphemeKind::Sum,
1037            symbol: "ρ+".to_string(),
1038            body: None,
1039        },
1040        ast::PipeOp::ReduceProd => IrPipelineStep::Morpheme {
1041            morpheme: MorphemeKind::Product,
1042            symbol: "ρ*".to_string(),
1043            body: None,
1044        },
1045        ast::PipeOp::ReduceMin => IrPipelineStep::Morpheme {
1046            morpheme: MorphemeKind::Min,
1047            symbol: "ρ_min".to_string(),
1048            body: None,
1049        },
1050        ast::PipeOp::ReduceMax => IrPipelineStep::Morpheme {
1051            morpheme: MorphemeKind::Max,
1052            symbol: "ρ_max".to_string(),
1053            body: None,
1054        },
1055        ast::PipeOp::ReduceConcat => IrPipelineStep::Morpheme {
1056            morpheme: MorphemeKind::Concat,
1057            symbol: "ρ++".to_string(),
1058            body: None,
1059        },
1060        ast::PipeOp::ReduceAll => IrPipelineStep::Morpheme {
1061            morpheme: MorphemeKind::All,
1062            symbol: "ρ&".to_string(),
1063            body: None,
1064        },
1065        ast::PipeOp::ReduceAny => IrPipelineStep::Morpheme {
1066            morpheme: MorphemeKind::Any,
1067            symbol: "ρ|".to_string(),
1068            body: None,
1069        },
1070        ast::PipeOp::First => IrPipelineStep::Morpheme {
1071            morpheme: MorphemeKind::First,
1072            symbol: "α".to_string(),
1073            body: None,
1074        },
1075        ast::PipeOp::Last => IrPipelineStep::Morpheme {
1076            morpheme: MorphemeKind::Last,
1077            symbol: "ω".to_string(),
1078            body: None,
1079        },
1080        ast::PipeOp::Middle => IrPipelineStep::Morpheme {
1081            morpheme: MorphemeKind::Middle,
1082            symbol: "μ".to_string(),
1083            body: None,
1084        },
1085        ast::PipeOp::Choice => IrPipelineStep::Morpheme {
1086            morpheme: MorphemeKind::Choice,
1087            symbol: "χ".to_string(),
1088            body: None,
1089        },
1090        ast::PipeOp::Nth(n) => IrPipelineStep::Morpheme {
1091            morpheme: MorphemeKind::Nth,
1092            symbol: "ν".to_string(),
1093            body: Some(Box::new(lower_expr(ctx, n))),
1094        },
1095        ast::PipeOp::Next => IrPipelineStep::Morpheme {
1096            morpheme: MorphemeKind::Next,
1097            symbol: "ξ".to_string(),
1098            body: None,
1099        },
1100        ast::PipeOp::Method { name, args } => IrPipelineStep::Method {
1101            name: name.name.clone(),
1102            args: args.iter().map(|a| lower_expr(ctx, a)).collect(),
1103        },
1104        ast::PipeOp::Await => IrPipelineStep::Await,
1105        ast::PipeOp::Match(_) => {
1106            // Match in pipes is handled by interpreter; IR falls back to identity
1107            // (proper implementation would lower to branching IR)
1108            IrPipelineStep::Identity
1109        }
1110        ast::PipeOp::TryMap(_) => {
1111            // Try/error transformation handled by interpreter
1112            IrPipelineStep::Identity
1113        }
1114        ast::PipeOp::Named { prefix, body } => {
1115            let fn_name = prefix
1116                .iter()
1117                .map(|i| i.name.clone())
1118                .collect::<Vec<_>>()
1119                .join("·");
1120            IrPipelineStep::Call {
1121                function: fn_name,
1122                args: body
1123                    .as_ref()
1124                    .map(|b| vec![lower_expr(ctx, b)])
1125                    .unwrap_or_default(),
1126            }
1127        }
1128        // Protocol operations in pipeline
1129        ast::PipeOp::Send(data) => IrPipelineStep::Protocol {
1130            operation: ProtocolOp::Send,
1131            config: Some(Box::new(lower_expr(ctx, data))),
1132        },
1133        ast::PipeOp::Recv => IrPipelineStep::Protocol {
1134            operation: ProtocolOp::Recv,
1135            config: None,
1136        },
1137        ast::PipeOp::Stream(handler) => IrPipelineStep::Protocol {
1138            operation: ProtocolOp::Stream,
1139            config: Some(Box::new(lower_expr(ctx, handler))),
1140        },
1141        ast::PipeOp::Connect(config) => IrPipelineStep::Protocol {
1142            operation: ProtocolOp::Connect,
1143            config: config.as_ref().map(|c| Box::new(lower_expr(ctx, c))),
1144        },
1145        ast::PipeOp::Close => IrPipelineStep::Protocol {
1146            operation: ProtocolOp::Close,
1147            config: None,
1148        },
1149        ast::PipeOp::Timeout(ms) => IrPipelineStep::Protocol {
1150            operation: ProtocolOp::Timeout,
1151            config: Some(Box::new(lower_expr(ctx, ms))),
1152        },
1153        ast::PipeOp::Retry { count, strategy } => IrPipelineStep::Protocol {
1154            operation: ProtocolOp::Retry,
1155            config: Some(Box::new(IrOperation::Tuple {
1156                elements: vec![
1157                    lower_expr(ctx, count),
1158                    strategy
1159                        .as_ref()
1160                        .map(|s| lower_expr(ctx, s))
1161                        .unwrap_or(IrOperation::Literal {
1162                            variant: LiteralVariant::String,
1163                            value: serde_json::json!("exponential"),
1164                            ty: IrType::Primitive {
1165                                name: "str".to_string(),
1166                            },
1167                            evidence: IrEvidence::Known,
1168                        }),
1169                ],
1170                ty: IrType::Infer,
1171                evidence: IrEvidence::Known,
1172            })),
1173        },
1174        _ => IrPipelineStep::Identity,
1175    }
1176}
1177
1178fn pipe_op_evidence(op: &ast::PipeOp) -> IrEvidence {
1179    match op {
1180        // Protocol operations always produce Reported evidence
1181        ast::PipeOp::Send(_)
1182        | ast::PipeOp::Recv
1183        | ast::PipeOp::Stream(_)
1184        | ast::PipeOp::Connect(_)
1185        | ast::PipeOp::Close => IrEvidence::Reported,
1186
1187        // Evidence promotion operations change evidence level
1188        ast::PipeOp::Validate {
1189            target_evidence, ..
1190        } => ast_evidence_to_ir(*target_evidence),
1191        ast::PipeOp::Assume {
1192            target_evidence, ..
1193        } => ast_evidence_to_ir(*target_evidence),
1194        ast::PipeOp::AssertEvidence(_) => IrEvidence::Known, // Assertion doesn't change evidence
1195
1196        _ => IrEvidence::Known,
1197    }
1198}
1199
1200fn ast_evidence_to_ir(ev: ast::Evidentiality) -> IrEvidence {
1201    match ev {
1202        ast::Evidentiality::Known => IrEvidence::Known,
1203        ast::Evidentiality::Uncertain => IrEvidence::Uncertain,
1204        ast::Evidentiality::Reported => IrEvidence::Reported,
1205        ast::Evidentiality::Paradox => IrEvidence::Paradox,
1206    }
1207}
1208
1209fn lower_literal(lit: &ast::Literal) -> IrOperation {
1210    match lit {
1211        ast::Literal::Int {
1212            value,
1213            base,
1214            suffix,
1215        } => IrOperation::Literal {
1216            variant: LiteralVariant::Int,
1217            value: serde_json::json!({
1218                "value": value,
1219                "base": format!("{:?}", base),
1220                "suffix": suffix
1221            }),
1222            ty: suffix
1223                .as_ref()
1224                .map(|s| IrType::Primitive { name: s.clone() })
1225                .unwrap_or(IrType::Primitive {
1226                    name: "i64".to_string(),
1227                }),
1228            evidence: IrEvidence::Known,
1229        },
1230        ast::Literal::Float { value, suffix } => IrOperation::Literal {
1231            variant: LiteralVariant::Float,
1232            value: serde_json::json!({"value": value, "suffix": suffix}),
1233            ty: suffix
1234                .as_ref()
1235                .map(|s| IrType::Primitive { name: s.clone() })
1236                .unwrap_or(IrType::Primitive {
1237                    name: "f64".to_string(),
1238                }),
1239            evidence: IrEvidence::Known,
1240        },
1241        ast::Literal::String(s) | ast::Literal::MultiLineString(s) | ast::Literal::RawString(s) => {
1242            IrOperation::Literal {
1243                variant: LiteralVariant::String,
1244                value: serde_json::Value::String(s.clone()),
1245                ty: IrType::Primitive {
1246                    name: "str".to_string(),
1247                },
1248                evidence: IrEvidence::Known,
1249            }
1250        }
1251        ast::Literal::Char(c) => IrOperation::Literal {
1252            variant: LiteralVariant::Char,
1253            value: serde_json::Value::String(c.to_string()),
1254            ty: IrType::Primitive {
1255                name: "char".to_string(),
1256            },
1257            evidence: IrEvidence::Known,
1258        },
1259        ast::Literal::Bool(b) => IrOperation::Literal {
1260            variant: LiteralVariant::Bool,
1261            value: serde_json::Value::Bool(*b),
1262            ty: IrType::Primitive {
1263                name: "bool".to_string(),
1264            },
1265            evidence: IrEvidence::Known,
1266        },
1267        ast::Literal::Null | ast::Literal::Empty => IrOperation::Literal {
1268            variant: LiteralVariant::Null,
1269            value: serde_json::Value::Null,
1270            ty: IrType::Unit,
1271            evidence: IrEvidence::Known,
1272        },
1273        _ => IrOperation::Literal {
1274            variant: LiteralVariant::Null,
1275            value: serde_json::Value::Null,
1276            ty: IrType::Infer,
1277            evidence: IrEvidence::Known,
1278        },
1279    }
1280}
1281
1282fn lower_literal_value(lit: &ast::Literal) -> serde_json::Value {
1283    match lit {
1284        ast::Literal::Int { value, .. } => serde_json::json!(value),
1285        ast::Literal::Float { value, .. } => serde_json::json!(value),
1286        ast::Literal::String(s) => serde_json::Value::String(s.clone()),
1287        ast::Literal::Bool(b) => serde_json::Value::Bool(*b),
1288        ast::Literal::Char(c) => serde_json::Value::String(c.to_string()),
1289        ast::Literal::Null => serde_json::Value::Null,
1290        _ => serde_json::Value::Null,
1291    }
1292}
1293
1294fn lower_binop(op: ast::BinOp) -> BinaryOp {
1295    match op {
1296        ast::BinOp::Add => BinaryOp::Add,
1297        ast::BinOp::Sub => BinaryOp::Sub,
1298        ast::BinOp::Mul => BinaryOp::Mul,
1299        ast::BinOp::Div => BinaryOp::Div,
1300        ast::BinOp::Rem => BinaryOp::Rem,
1301        ast::BinOp::Pow => BinaryOp::Pow,
1302        ast::BinOp::And => BinaryOp::And,
1303        ast::BinOp::Or => BinaryOp::Or,
1304        ast::BinOp::BitAnd => BinaryOp::BitAnd,
1305        ast::BinOp::BitOr => BinaryOp::BitOr,
1306        ast::BinOp::BitXor => BinaryOp::BitXor,
1307        ast::BinOp::Shl => BinaryOp::Shl,
1308        ast::BinOp::Shr => BinaryOp::Shr,
1309        ast::BinOp::Eq => BinaryOp::Eq,
1310        ast::BinOp::Ne => BinaryOp::Ne,
1311        ast::BinOp::Lt => BinaryOp::Lt,
1312        ast::BinOp::Le => BinaryOp::Le,
1313        ast::BinOp::Gt => BinaryOp::Gt,
1314        ast::BinOp::Ge => BinaryOp::Ge,
1315        ast::BinOp::Concat => BinaryOp::Concat,
1316    }
1317}
1318
1319fn lower_unaryop(op: ast::UnaryOp) -> UnaryOp {
1320    match op {
1321        ast::UnaryOp::Neg => UnaryOp::Neg,
1322        ast::UnaryOp::Not => UnaryOp::Not,
1323        ast::UnaryOp::Deref => UnaryOp::Deref,
1324        ast::UnaryOp::Ref => UnaryOp::Ref,
1325        ast::UnaryOp::RefMut => UnaryOp::RefMut,
1326    }
1327}
1328
1329fn lower_morpheme_kind(kind: ast::MorphemeKind) -> MorphemeKind {
1330    match kind {
1331        ast::MorphemeKind::Transform => MorphemeKind::Transform,
1332        ast::MorphemeKind::Filter => MorphemeKind::Filter,
1333        ast::MorphemeKind::Sort => MorphemeKind::Sort,
1334        ast::MorphemeKind::Reduce => MorphemeKind::Reduce,
1335        ast::MorphemeKind::Lambda => MorphemeKind::Lambda,
1336        ast::MorphemeKind::Sum => MorphemeKind::Sum,
1337        ast::MorphemeKind::Product => MorphemeKind::Product,
1338        ast::MorphemeKind::First => MorphemeKind::First,
1339        ast::MorphemeKind::Last => MorphemeKind::Last,
1340        ast::MorphemeKind::Middle => MorphemeKind::Middle,
1341        ast::MorphemeKind::Choice => MorphemeKind::Choice,
1342        ast::MorphemeKind::Nth => MorphemeKind::Nth,
1343        ast::MorphemeKind::Next => MorphemeKind::Next,
1344    }
1345}
1346
1347fn morpheme_symbol(kind: ast::MorphemeKind) -> &'static str {
1348    match kind {
1349        ast::MorphemeKind::Transform => "τ",
1350        ast::MorphemeKind::Filter => "φ",
1351        ast::MorphemeKind::Sort => "σ",
1352        ast::MorphemeKind::Reduce => "ρ",
1353        ast::MorphemeKind::Lambda => "λ",
1354        ast::MorphemeKind::Sum => "Σ",
1355        ast::MorphemeKind::Product => "Π",
1356        ast::MorphemeKind::First => "α",
1357        ast::MorphemeKind::Last => "ω",
1358        ast::MorphemeKind::Middle => "μ",
1359        ast::MorphemeKind::Choice => "χ",
1360        ast::MorphemeKind::Nth => "ν",
1361        ast::MorphemeKind::Next => "ξ",
1362    }
1363}
1364
1365fn lower_http_method(method: ast::HttpMethod) -> HttpMethod {
1366    match method {
1367        ast::HttpMethod::Get => HttpMethod::Get,
1368        ast::HttpMethod::Post => HttpMethod::Post,
1369        ast::HttpMethod::Put => HttpMethod::Put,
1370        ast::HttpMethod::Delete => HttpMethod::Delete,
1371        ast::HttpMethod::Patch => HttpMethod::Patch,
1372        ast::HttpMethod::Head => HttpMethod::Head,
1373        ast::HttpMethod::Options => HttpMethod::Options,
1374        ast::HttpMethod::Connect => HttpMethod::Connect,
1375        ast::HttpMethod::Trace => HttpMethod::Trace,
1376    }
1377}
1378
1379fn get_operation_evidence(op: &IrOperation) -> IrEvidence {
1380    match op {
1381        IrOperation::Literal { evidence, .. }
1382        | IrOperation::Var { evidence, .. }
1383        | IrOperation::Let { evidence, .. }
1384        | IrOperation::Binary { evidence, .. }
1385        | IrOperation::Unary { evidence, .. }
1386        | IrOperation::Call { evidence, .. }
1387        | IrOperation::MethodCall { evidence, .. }
1388        | IrOperation::Closure { evidence, .. }
1389        | IrOperation::If { evidence, .. }
1390        | IrOperation::Match { evidence, .. }
1391        | IrOperation::Loop { evidence, .. }
1392        | IrOperation::Block { evidence, .. }
1393        | IrOperation::Pipeline { evidence, .. }
1394        | IrOperation::Morpheme { evidence, .. }
1395        | IrOperation::Fork { evidence, .. }
1396        | IrOperation::Identity { evidence, .. }
1397        | IrOperation::Array { evidence, .. }
1398        | IrOperation::Tuple { evidence, .. }
1399        | IrOperation::StructInit { evidence, .. }
1400        | IrOperation::Field { evidence, .. }
1401        | IrOperation::Index { evidence, .. }
1402        | IrOperation::Assign { evidence, .. }
1403        | IrOperation::Break { evidence, .. }
1404        | IrOperation::Continue { evidence }
1405        | IrOperation::Return { evidence, .. }
1406        | IrOperation::Incorporation { evidence, .. }
1407        | IrOperation::Affect { evidence, .. }
1408        | IrOperation::HttpRequest { evidence, .. }
1409        | IrOperation::GrpcCall { evidence, .. }
1410        | IrOperation::WebSocket { evidence, .. }
1411        | IrOperation::KafkaOp { evidence, .. }
1412        | IrOperation::Await { evidence, .. }
1413        | IrOperation::Unsafe { evidence, .. }
1414        | IrOperation::Cast { evidence, .. }
1415        | IrOperation::Try { evidence, .. } => *evidence,
1416        IrOperation::EvidenceCoerce { to_evidence, .. } => *to_evidence,
1417    }
1418}
1419
1420fn expr_to_string(e: &ast::Expr) -> String {
1421    match e {
1422        ast::Expr::Path(p) => p
1423            .segments
1424            .iter()
1425            .map(|s| s.ident.name.clone())
1426            .collect::<Vec<_>>()
1427            .join("::"),
1428        ast::Expr::Literal(ast::Literal::String(s)) => s.clone(),
1429        _ => "dynamic".to_string(),
1430    }
1431}
1432
1433// === Type definitions lowering ===
1434
1435fn lower_struct_def(_ctx: &mut LoweringContext, s: &ast::StructDef) -> IrTypeDef {
1436    let fields = match &s.fields {
1437        ast::StructFields::Named(fields) => fields
1438            .iter()
1439            .map(|f| IrField {
1440                name: f.name.name.clone(),
1441                ty: lower_type_expr(&f.ty),
1442                visibility: lower_visibility(f.visibility),
1443            })
1444            .collect(),
1445        ast::StructFields::Tuple(types) => types
1446            .iter()
1447            .enumerate()
1448            .map(|(i, t)| IrField {
1449                name: format!("{}", i),
1450                ty: lower_type_expr(t),
1451                visibility: IrVisibility::Public,
1452            })
1453            .collect(),
1454        ast::StructFields::Unit => vec![],
1455    };
1456
1457    IrTypeDef::Struct {
1458        name: s.name.name.clone(),
1459        generics: lower_generics(&s.generics),
1460        fields,
1461        span: None,
1462    }
1463}
1464
1465fn lower_enum_def(_ctx: &mut LoweringContext, e: &ast::EnumDef) -> IrTypeDef {
1466    let variants = e
1467        .variants
1468        .iter()
1469        .map(|v| {
1470            let fields = match &v.fields {
1471                ast::StructFields::Named(fields) => Some(
1472                    fields
1473                        .iter()
1474                        .map(|f| IrField {
1475                            name: f.name.name.clone(),
1476                            ty: lower_type_expr(&f.ty),
1477                            visibility: lower_visibility(f.visibility),
1478                        })
1479                        .collect(),
1480                ),
1481                ast::StructFields::Tuple(types) => Some(
1482                    types
1483                        .iter()
1484                        .enumerate()
1485                        .map(|(i, t)| IrField {
1486                            name: format!("{}", i),
1487                            ty: lower_type_expr(t),
1488                            visibility: IrVisibility::Public,
1489                        })
1490                        .collect(),
1491                ),
1492                ast::StructFields::Unit => None,
1493            };
1494
1495            IrVariant {
1496                name: v.name.name.clone(),
1497                fields,
1498                discriminant: None,
1499            }
1500        })
1501        .collect();
1502
1503    IrTypeDef::Enum {
1504        name: e.name.name.clone(),
1505        generics: lower_generics(&e.generics),
1506        variants,
1507        span: None,
1508    }
1509}
1510
1511fn lower_type_alias(_ctx: &mut LoweringContext, t: &ast::TypeAlias) -> IrTypeDef {
1512    IrTypeDef::TypeAlias {
1513        name: t.name.name.clone(),
1514        generics: lower_generics(&t.generics),
1515        target: lower_type_expr(&t.ty),
1516        span: None,
1517    }
1518}
1519
1520fn lower_trait_def(ctx: &mut LoweringContext, t: &ast::TraitDef) -> IrTraitDef {
1521    let methods = t
1522        .items
1523        .iter()
1524        .filter_map(|item| match item {
1525            ast::TraitItem::Function(f) => lower_function(ctx, f),
1526            _ => None,
1527        })
1528        .collect();
1529
1530    IrTraitDef {
1531        name: t.name.name.clone(),
1532        generics: lower_generics(&t.generics),
1533        super_traits: t.supertraits.iter().map(type_expr_to_string).collect(),
1534        methods,
1535        span: None,
1536    }
1537}
1538
1539fn lower_impl_block(ctx: &mut LoweringContext, i: &ast::ImplBlock) -> IrImplBlock {
1540    let methods = i
1541        .items
1542        .iter()
1543        .filter_map(|item| match item {
1544            ast::ImplItem::Function(f) => lower_function(ctx, f),
1545            _ => None,
1546        })
1547        .collect();
1548
1549    IrImplBlock {
1550        trait_name: i.trait_.as_ref().map(|t| {
1551            t.segments
1552                .iter()
1553                .map(|s| s.ident.name.clone())
1554                .collect::<Vec<_>>()
1555                .join("::")
1556        }),
1557        target_type: lower_type_expr(&i.self_ty),
1558        generics: lower_generics(&i.generics),
1559        methods,
1560        span: None,
1561    }
1562}
1563
1564fn lower_const_def(ctx: &mut LoweringContext, c: &ast::ConstDef) -> IrConstant {
1565    IrConstant {
1566        name: c.name.name.clone(),
1567        ty: lower_type_expr(&c.ty),
1568        value: lower_expr(ctx, &c.value),
1569        visibility: lower_visibility(c.visibility),
1570        span: None,
1571    }
1572}