Skip to main content

yulang_runtime/lower/
mod.rs

1//! Lower principal core IR into typed runtime IR.
2//!
3//! This stage consumes principal schemes and local evidence from `yulang-infer`
4//! and introduces runtime constructs such as thunks, `bind_here`, effect id
5//! administration, and runtime coercions.  Later stages may specialize and
6//! preserve these constructs, but should not need to rediscover why they were
7//! introduced.
8
9use std::collections::{BTreeMap, BTreeSet, HashMap};
10
11use yulang_typed_ir as typed_ir;
12
13use crate::diagnostic::{
14    RuntimeCalleeLabel, RuntimeError, RuntimeResult, TypeMismatchContext, TypeMismatchPhase,
15    TypeSource,
16};
17use crate::invariant::{RuntimeStage, check_runtime_invariants};
18use crate::ir::{
19    Binding, EffectIdRef, EffectIdVar, Expr, ExprKind, HandleArm, HandleEffect, JoinEvidence,
20    MatchArm, Module, Pattern, RecordExprField, RecordPatternField, RecordSpreadExpr,
21    RecordSpreadPattern, ResumeBinding, Root, Stmt, Type as RuntimeType, TypeInstantiation,
22    TypeSubstitution,
23};
24use crate::types::{
25    BoundsChoice, TypeChoice, choose_bounds_type, choose_core_type, choose_optional_core_type,
26    collect_hir_type_vars, collect_type_vars, contains_non_runtime_effect_type,
27    contains_non_runtime_type, core_type_has_vars, core_type_is_imprecise_runtime_slot,
28    core_types_compatible, diagnostic_core_type, effect_compatible, effect_is_empty, effect_path,
29    effect_paths, effect_paths_match, effect_row_from_items, hir_type_imprecision_count,
30    hir_type_is_hole, infer_type_substitutions, infer_type_substitutions_prefer_non_never,
31    infer_type_substitutions_prefer_non_never_skip_empty_effects, is_qualified_runtime_path,
32    needs_runtime_coercion, project_runtime_bounds, project_runtime_effect,
33    project_runtime_hir_type_with_vars, project_runtime_type_with_vars, runtime_core_type,
34    runtime_type_contains_unknown, runtime_type_is_imprecise_runtime_slot, should_thunk_effect,
35    strict_core_type as core_type, substitute_bounds, substitute_hir_type, substitute_type,
36    thunk_effect, type_compatible, wildcard_effect_type,
37};
38use crate::validate::validate_module;
39
40mod core_shape;
41mod diagnostics;
42mod effects;
43mod evidence;
44mod expr;
45mod function;
46mod hints;
47mod lowerer;
48mod patterns;
49mod primitive_types;
50mod substitutions;
51mod thunk;
52
53pub use core_shape::CoreShapeProfile;
54use core_shape::*;
55use diagnostics::*;
56use effects::*;
57use evidence::*;
58use expr::*;
59use function::*;
60use hints::*;
61use patterns::*;
62use primitive_types::*;
63use substitutions::*;
64use thunk::*;
65
66pub struct RuntimeLowerOutput {
67    pub module: Module,
68    pub profile: RuntimeLowerProfile,
69}
70
71#[derive(Debug, Default, Clone, PartialEq, Eq)]
72pub struct RuntimeLowerProfile {
73    pub core_shape: CoreShapeProfile,
74    pub expected_arg_evidence: ExpectedArgEvidenceProfile,
75    pub expected_adapter_evidence: ExpectedAdapterEvidenceProfile,
76    pub derived_expected_evidence: DerivedExpectedEvidenceProfile,
77    pub runtime_adapters: RuntimeAdapterProfile,
78}
79
80#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
81pub struct ExpectedArgEvidenceProfile {
82    pub present: usize,
83    pub converted: usize,
84    pub usable_by_table: usize,
85    pub usable_by_bounds: usize,
86    pub used_as_arg_type_hint: usize,
87    pub used_as_lowering_expected: usize,
88    pub ignored_no_expected_arg: usize,
89    pub ignored_not_convertible: usize,
90    pub ignored_table_open: usize,
91    pub ignored_table_uninformative: usize,
92    pub ignored_table_not_runtime_usable: usize,
93    pub ignored_bounds_unusable: usize,
94    pub ignored_unusable: usize,
95    pub ignored_no_push: usize,
96}
97
98#[derive(Debug, Default, Clone, PartialEq, Eq)]
99pub struct RuntimeAdapterProfile {
100    pub collect_events: bool,
101    pub value_to_thunk: usize,
102    pub thunk_to_value: usize,
103    pub bind_here: usize,
104    pub apply_evidence_value_to_thunk: usize,
105    pub apply_evidence_thunk_to_value: usize,
106    pub apply_evidence_bind_here: usize,
107    pub apply_evidence_adapter_with_evidence: usize,
108    pub apply_evidence_adapter_with_source_edge: usize,
109    pub apply_evidence_adapter_without_evidence: usize,
110    pub apply_evidence_value_to_thunk_with_source_edge: usize,
111    pub apply_evidence_thunk_to_value_with_source_edge: usize,
112    pub apply_evidence_bind_here_with_source_edge: usize,
113    pub apply_lower_callee_value_to_thunk: usize,
114    pub apply_lower_callee_thunk_to_value: usize,
115    pub apply_lower_callee_bind_here: usize,
116    pub apply_lower_argument_value_to_thunk: usize,
117    pub apply_lower_argument_thunk_to_value: usize,
118    pub apply_lower_argument_bind_here: usize,
119    pub apply_prepare_final_argument_value_to_thunk: usize,
120    pub apply_prepare_final_argument_thunk_to_value: usize,
121    pub apply_prepare_final_argument_bind_here: usize,
122    pub apply_prepare_effect_operation_argument_value_to_thunk: usize,
123    pub apply_prepare_effect_operation_argument_thunk_to_value: usize,
124    pub apply_prepare_effect_operation_argument_bind_here: usize,
125    pub reused_thunk: usize,
126    pub forced_effect_thunk: usize,
127    pub matched_expected_adapter: usize,
128    pub unmatched_expected_adapter: usize,
129    pub unmatched_value_to_thunk: usize,
130    pub unmatched_thunk_to_value: usize,
131    pub unmatched_bind_here: usize,
132    pub matched_derived_expected_edge_parent: usize,
133    pub unmatched_derived_expected_edge_parent: usize,
134    pub observed_adapter_with_source_expected_edge: usize,
135    pub observed_adapter_without_source_expected_edge: usize,
136    pub observed_adapter_source_application_callee: usize,
137    pub observed_adapter_source_application_argument: usize,
138    pub observed_adapter_source_other_expected_edge: usize,
139    pub observed_adapter_source_with_derived_parent: usize,
140    pub observed_adapter_source_without_derived_parent: usize,
141    pub events: Vec<RuntimeAdapterEvent>,
142    pub observed_adapter_evidence: Vec<ObservedAdapterEvidence>,
143}
144
145#[derive(Debug, Clone, PartialEq, Eq)]
146pub struct ObservedAdapterEvidence {
147    pub kind: ObservedAdapterEvidenceKind,
148    pub phase: RuntimeApplyAdapterPhase,
149    pub owner: Option<typed_ir::Path>,
150    pub apply_target: Option<typed_ir::Path>,
151    pub source_expected_edge: Option<u32>,
152    pub actual: RuntimeType,
153    pub expected: RuntimeType,
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
157pub enum ObservedAdapterEvidenceKind {
158    ValueToThunk,
159    ForceThunkToValue,
160}
161
162#[derive(Debug, Clone, PartialEq, Eq)]
163pub struct RuntimeAdapterEvent {
164    pub kind: RuntimeAdapterEventKind,
165    pub phase: RuntimeApplyAdapterPhase,
166    pub owner: Option<typed_ir::Path>,
167    pub apply_target: Option<typed_ir::Path>,
168    pub callee_source_edge: Option<u32>,
169    pub arg_source_edge: Option<u32>,
170    pub actual: RuntimeType,
171    pub expected: RuntimeType,
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
175pub enum RuntimeAdapterEventKind {
176    ValueToThunk,
177    ThunkToValue,
178    BindHere,
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
182pub enum RuntimeApplyAdapterPhase {
183    LowerCallee,
184    LowerArgument,
185    PrepareFinalArgument,
186    PrepareEffectOperationArgument,
187}
188
189#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
190pub struct ExpectedAdapterEvidenceProfile {
191    pub total: usize,
192    pub runtime_usable: usize,
193    pub closed: usize,
194    pub informative: usize,
195    pub effect_operation_argument: usize,
196    pub value_to_thunk: usize,
197    pub thunk_to_value: usize,
198    pub bind_here: usize,
199    pub handler_residual: usize,
200    pub handler_return: usize,
201    pub resume_argument: usize,
202}
203
204#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
205pub struct DerivedExpectedEvidenceProfile {
206    pub total: usize,
207    pub record_field: usize,
208    pub tuple_item: usize,
209    pub variant_payload: usize,
210    pub function_param: usize,
211    pub function_return: usize,
212    pub covariant: usize,
213    pub contravariant: usize,
214    pub invariant: usize,
215}
216
217pub fn lower_core_program(program: typed_ir::CoreProgram) -> RuntimeResult<Module> {
218    let graph = program.graph;
219    let evidence = program.evidence;
220    lower_principal_module_with_graph_and_evidence(program.program, &graph, &evidence)
221}
222
223pub fn lower_core_program_profiled(
224    program: typed_ir::CoreProgram,
225) -> RuntimeResult<RuntimeLowerOutput> {
226    let core_shape = profile_core_program(&program);
227    let graph = program.graph;
228    let evidence = program.evidence;
229    lower_principal_module_with_graph_and_evidence_profiled(
230        program.program,
231        &graph,
232        &evidence,
233        core_shape,
234    )
235}
236
237pub fn lower_principal_module(module: typed_ir::PrincipalModule) -> RuntimeResult<Module> {
238    lower_principal_module_with_graph(module, &typed_ir::CoreGraphView::default())
239}
240
241pub fn lower_principal_module_with_graph(
242    module: typed_ir::PrincipalModule,
243    graph: &typed_ir::CoreGraphView,
244) -> RuntimeResult<Module> {
245    let evidence = typed_ir::PrincipalEvidence::default();
246    lower_principal_module_with_graph_and_evidence(module, graph, &evidence)
247}
248
249fn lower_principal_module_with_graph_and_evidence(
250    module: typed_ir::PrincipalModule,
251    graph: &typed_ir::CoreGraphView,
252    evidence: &typed_ir::PrincipalEvidence,
253) -> RuntimeResult<Module> {
254    lower_principal_module_with_graph_and_evidence_inner(
255        module,
256        graph,
257        evidence,
258        CoreShapeProfile::default(),
259        false,
260    )
261    .map(|output| output.module)
262}
263
264fn lower_principal_module_with_graph_and_evidence_profiled(
265    module: typed_ir::PrincipalModule,
266    graph: &typed_ir::CoreGraphView,
267    evidence: &typed_ir::PrincipalEvidence,
268    core_shape: CoreShapeProfile,
269) -> RuntimeResult<RuntimeLowerOutput> {
270    lower_principal_module_with_graph_and_evidence_inner(module, graph, evidence, core_shape, true)
271}
272
273fn lower_principal_module_with_graph_and_evidence_inner(
274    module: typed_ir::PrincipalModule,
275    graph: &typed_ir::CoreGraphView,
276    evidence: &typed_ir::PrincipalEvidence,
277    core_shape: CoreShapeProfile,
278    collect_profile: bool,
279) -> RuntimeResult<RuntimeLowerOutput> {
280    let principal_vars = principal_module_type_vars(&module);
281    let mut binding_infos = module
282        .bindings
283        .iter()
284        .map(|binding| {
285            let ty = project_runtime_hir_type_with_vars(&binding.scheme.body, &principal_vars);
286            (
287                binding.name.clone(),
288                BindingInfo {
289                    type_params: {
290                        if is_constructor_variant_expr(&binding.body) {
291                            principal_runtime_type_params(&binding.scheme.body, &ty, true)
292                        } else {
293                            principal_runtime_type_params(&binding.scheme.body, &ty, false)
294                        }
295                    },
296                    ty,
297                    requirements: binding.scheme.requirements.clone(),
298                },
299            )
300        })
301        .collect::<HashMap<_, _>>();
302    let mut env = module
303        .bindings
304        .iter()
305        .map(|binding| {
306            (
307                binding.name.clone(),
308                binding_infos
309                    .get(&binding.name)
310                    .map(|info| info.ty.clone())
311                    .unwrap_or_else(|| {
312                        project_runtime_hir_type_with_vars(&binding.scheme.body, &principal_vars)
313                    }),
314            )
315        })
316        .collect::<HashMap<_, _>>();
317    normalize_initial_alias_types(&module.bindings, &mut env, &mut binding_infos);
318    let aliases = direct_aliases(&module.bindings);
319    let expected_edges_by_id = evidence
320        .expected_edges
321        .iter()
322        .map(|edge| (edge.id, edge))
323        .collect();
324    let mut lowerer = Lowerer {
325        env,
326        binding_infos,
327        aliases,
328        graph,
329        runtime_symbols: graph
330            .runtime_symbols
331            .iter()
332            .map(|symbol| (symbol.path.clone(), symbol.kind))
333            .collect(),
334        primitive_paths: RuntimePrimitivePathTable::from_graph(graph),
335        principal_vars,
336        expected_edges_by_id,
337        use_expected_arg_evidence: std::env::var_os("YULANG_USE_EXPECTED_ARG_EVIDENCE").is_some(),
338        use_principal_elaboration: std::env::var_os("YULANG_DISABLE_PRINCIPAL_ELABORATE").is_none(),
339        expected_arg_evidence_profile: ExpectedArgEvidenceProfile::default(),
340        runtime_adapter_profile: RuntimeAdapterProfile::default(),
341        local_param_boundaries: HashMap::new(),
342        handler_body_depth: 0,
343        current_function_boundary: None,
344        current_binding: None,
345        current_runtime_adapter_source: None,
346        next_synthetic_type_var: 0,
347        next_effect_id_var: 0,
348    };
349    lowerer.runtime_adapter_profile.collect_events = collect_profile;
350    let path = module.path;
351    let bindings = module
352        .bindings
353        .into_iter()
354        .map(|binding| lowerer.lower_binding(binding))
355        .collect::<RuntimeResult<Vec<_>>>()?;
356    let root_exprs = module
357        .root_exprs
358        .into_iter()
359        .enumerate()
360        .map(|(index, expr)| lowerer.lower_root_expr(index, expr))
361        .collect::<RuntimeResult<Vec<_>>>()?;
362    if std::env::var_os("YULANG_DEBUG_EXPECTED_ARG_EVIDENCE").is_some() {
363        eprintln!(
364            "expected-arg evidence: present={} converted={} usable-by-table={} usable-by-bounds={} used-as-arg-type-hint={} used-as-lowering-expected={} ignored-no-expected-arg={} ignored-not-convertible={} ignored-table-open={} ignored-table-uninformative={} ignored-table-not-runtime-usable={} ignored-bounds-unusable={} ignored-unusable={} ignored-no-push={}",
365            lowerer.expected_arg_evidence_profile.present,
366            lowerer.expected_arg_evidence_profile.converted,
367            lowerer.expected_arg_evidence_profile.usable_by_table,
368            lowerer.expected_arg_evidence_profile.usable_by_bounds,
369            lowerer.expected_arg_evidence_profile.used_as_arg_type_hint,
370            lowerer
371                .expected_arg_evidence_profile
372                .used_as_lowering_expected,
373            lowerer
374                .expected_arg_evidence_profile
375                .ignored_no_expected_arg,
376            lowerer
377                .expected_arg_evidence_profile
378                .ignored_not_convertible,
379            lowerer.expected_arg_evidence_profile.ignored_table_open,
380            lowerer
381                .expected_arg_evidence_profile
382                .ignored_table_uninformative,
383            lowerer
384                .expected_arg_evidence_profile
385                .ignored_table_not_runtime_usable,
386            lowerer
387                .expected_arg_evidence_profile
388                .ignored_bounds_unusable,
389            lowerer.expected_arg_evidence_profile.ignored_unusable,
390            lowerer.expected_arg_evidence_profile.ignored_no_push,
391        );
392    }
393    if std::env::var_os("YULANG_DEBUG_RUNTIME_ADAPTERS").is_some() {
394        eprintln!(
395            "runtime adapters: value-to-thunk={} thunk-to-value={} bind-here={} apply-evidence-value-to-thunk={} apply-evidence-thunk-to-value={} apply-evidence-bind-here={} reused-thunk={} forced-effect-thunk={}",
396            lowerer.runtime_adapter_profile.value_to_thunk,
397            lowerer.runtime_adapter_profile.thunk_to_value,
398            lowerer.runtime_adapter_profile.bind_here,
399            lowerer
400                .runtime_adapter_profile
401                .apply_evidence_value_to_thunk,
402            lowerer
403                .runtime_adapter_profile
404                .apply_evidence_thunk_to_value,
405            lowerer.runtime_adapter_profile.apply_evidence_bind_here,
406            lowerer.runtime_adapter_profile.reused_thunk,
407            lowerer.runtime_adapter_profile.forced_effect_thunk,
408        );
409    }
410    let roots = module
411        .roots
412        .into_iter()
413        .map(|root| match root {
414            typed_ir::PrincipalRoot::Binding(path) => Root::Binding(path),
415            typed_ir::PrincipalRoot::Expr(index) => Root::Expr(index),
416        })
417        .collect();
418    let module = Module {
419        path,
420        bindings,
421        root_exprs,
422        roots,
423        role_impls: graph.role_impls.clone(),
424    };
425    check_runtime_invariants(&module, RuntimeStage::Lowered)?;
426    validate_module(&module)?;
427    let mut runtime_adapters = lowerer.runtime_adapter_profile;
428    if collect_profile {
429        profile_runtime_adapter_expected_matches(&mut runtime_adapters, evidence);
430        profile_runtime_adapter_derived_parent_matches(&mut runtime_adapters, evidence);
431        collect_observed_adapter_evidence(&mut runtime_adapters, evidence);
432    }
433    Ok(RuntimeLowerOutput {
434        module,
435        profile: RuntimeLowerProfile {
436            core_shape,
437            expected_arg_evidence: lowerer.expected_arg_evidence_profile,
438            expected_adapter_evidence: collect_profile
439                .then(|| expected_adapter_evidence_profile(evidence))
440                .unwrap_or_default(),
441            derived_expected_evidence: collect_profile
442                .then(|| derived_expected_evidence_profile(evidence))
443                .unwrap_or_default(),
444            runtime_adapters,
445        },
446    })
447}
448
449fn is_constructor_variant_expr(expr: &typed_ir::Expr) -> bool {
450    matches!(
451        expr,
452        typed_ir::Expr::Variant {
453            source: typed_ir::VariantExprSource::Constructor,
454            ..
455        }
456    )
457}
458
459fn collect_observed_adapter_evidence(
460    profile: &mut RuntimeAdapterProfile,
461    evidence: &typed_ir::PrincipalEvidence,
462) {
463    profile.observed_adapter_evidence = profile
464        .events
465        .iter()
466        .filter_map(observed_adapter_evidence_from_event)
467        .collect();
468    profile_observed_adapter_source_coverage(profile, evidence);
469}
470
471fn profile_observed_adapter_source_coverage(
472    profile: &mut RuntimeAdapterProfile,
473    evidence: &typed_ir::PrincipalEvidence,
474) {
475    let observed = profile.observed_adapter_evidence.clone();
476    for observed in observed {
477        let Some(source_expected_edge) = observed.source_expected_edge else {
478            profile.observed_adapter_without_source_expected_edge += 1;
479            continue;
480        };
481        profile.observed_adapter_with_source_expected_edge += 1;
482        match evidence
483            .expected_edge(source_expected_edge)
484            .map(|edge| edge.kind)
485        {
486            Some(typed_ir::ExpectedEdgeKind::ApplicationCallee) => {
487                profile.observed_adapter_source_application_callee += 1;
488            }
489            Some(typed_ir::ExpectedEdgeKind::ApplicationArgument) => {
490                profile.observed_adapter_source_application_argument += 1;
491            }
492            Some(_) | None => {
493                profile.observed_adapter_source_other_expected_edge += 1;
494            }
495        }
496        if evidence
497            .derived_expected_edges_for_parent(source_expected_edge)
498            .next()
499            .is_some()
500        {
501            profile.observed_adapter_source_with_derived_parent += 1;
502        } else {
503            profile.observed_adapter_source_without_derived_parent += 1;
504        }
505    }
506}
507
508fn observed_adapter_evidence_from_event(
509    event: &RuntimeAdapterEvent,
510) -> Option<ObservedAdapterEvidence> {
511    let kind = match event.kind {
512        RuntimeAdapterEventKind::ValueToThunk => ObservedAdapterEvidenceKind::ValueToThunk,
513        RuntimeAdapterEventKind::ThunkToValue => ObservedAdapterEvidenceKind::ForceThunkToValue,
514        RuntimeAdapterEventKind::BindHere => return None,
515    };
516    Some(ObservedAdapterEvidence {
517        kind,
518        phase: event.phase,
519        owner: event.owner.clone(),
520        apply_target: event.apply_target.clone(),
521        source_expected_edge: runtime_adapter_event_source_edge(event),
522        actual: event.actual.clone(),
523        expected: event.expected.clone(),
524    })
525}
526
527fn profile_runtime_adapter_expected_matches(
528    profile: &mut RuntimeAdapterProfile,
529    evidence: &typed_ir::PrincipalEvidence,
530) {
531    for event in &profile.events {
532        if expected_adapter_event_match(evidence, event) {
533            profile.matched_expected_adapter += 1;
534        } else {
535            profile.unmatched_expected_adapter += 1;
536            match event.kind {
537                RuntimeAdapterEventKind::ValueToThunk => {
538                    profile.unmatched_value_to_thunk += 1;
539                }
540                RuntimeAdapterEventKind::ThunkToValue => {
541                    profile.unmatched_thunk_to_value += 1;
542                }
543                RuntimeAdapterEventKind::BindHere => {
544                    profile.unmatched_bind_here += 1;
545                }
546            }
547        }
548    }
549}
550
551fn profile_runtime_adapter_derived_parent_matches(
552    profile: &mut RuntimeAdapterProfile,
553    evidence: &typed_ir::PrincipalEvidence,
554) {
555    for event in &profile.events {
556        let Some(source_edge) = runtime_adapter_event_source_edge(event) else {
557            continue;
558        };
559        if evidence
560            .derived_expected_edges_for_parent(source_edge)
561            .next()
562            .is_some()
563        {
564            profile.matched_derived_expected_edge_parent += 1;
565        } else {
566            profile.unmatched_derived_expected_edge_parent += 1;
567        }
568    }
569}
570
571fn expected_adapter_event_match(
572    evidence: &typed_ir::PrincipalEvidence,
573    event: &RuntimeAdapterEvent,
574) -> bool {
575    evidence
576        .expected_adapter_edges
577        .iter()
578        .any(|edge| expected_adapter_edge_matches_event(edge, event))
579}
580
581fn expected_adapter_edge_matches_event(
582    edge: &typed_ir::ExpectedAdapterEdgeEvidence,
583    event: &RuntimeAdapterEvent,
584) -> bool {
585    if expected_adapter_event_kind(edge.kind) != Some(event.kind) {
586        return false;
587    }
588    if let Some(source_edge) = runtime_adapter_event_source_edge(event)
589        && edge.source_expected_edge != Some(source_edge)
590    {
591        return false;
592    }
593    true
594}
595
596fn runtime_adapter_event_source_edge(event: &RuntimeAdapterEvent) -> Option<u32> {
597    match event.phase {
598        RuntimeApplyAdapterPhase::LowerCallee => event.callee_source_edge,
599        RuntimeApplyAdapterPhase::LowerArgument
600        | RuntimeApplyAdapterPhase::PrepareFinalArgument
601        | RuntimeApplyAdapterPhase::PrepareEffectOperationArgument => event.arg_source_edge,
602    }
603}
604
605fn expected_adapter_event_kind(
606    kind: typed_ir::ExpectedAdapterEdgeKind,
607) -> Option<RuntimeAdapterEventKind> {
608    match kind {
609        typed_ir::ExpectedAdapterEdgeKind::ValueToThunk => {
610            Some(RuntimeAdapterEventKind::ValueToThunk)
611        }
612        typed_ir::ExpectedAdapterEdgeKind::ThunkToValue => {
613            Some(RuntimeAdapterEventKind::ThunkToValue)
614        }
615        typed_ir::ExpectedAdapterEdgeKind::BindHere => Some(RuntimeAdapterEventKind::BindHere),
616        typed_ir::ExpectedAdapterEdgeKind::EffectOperationArgument
617        | typed_ir::ExpectedAdapterEdgeKind::HandlerResidual
618        | typed_ir::ExpectedAdapterEdgeKind::HandlerReturn
619        | typed_ir::ExpectedAdapterEdgeKind::ResumeArgument => None,
620    }
621}
622
623fn expected_adapter_evidence_profile(
624    evidence: &typed_ir::PrincipalEvidence,
625) -> ExpectedAdapterEvidenceProfile {
626    let mut profile = ExpectedAdapterEvidenceProfile::default();
627    for edge in &evidence.expected_adapter_edges {
628        profile.total += 1;
629        if edge.runtime_usable {
630            profile.runtime_usable += 1;
631        }
632        if edge.closed {
633            profile.closed += 1;
634        }
635        if edge.informative {
636            profile.informative += 1;
637        }
638        match edge.kind {
639            typed_ir::ExpectedAdapterEdgeKind::EffectOperationArgument => {
640                profile.effect_operation_argument += 1;
641            }
642            typed_ir::ExpectedAdapterEdgeKind::ValueToThunk => {
643                profile.value_to_thunk += 1;
644            }
645            typed_ir::ExpectedAdapterEdgeKind::ThunkToValue => {
646                profile.thunk_to_value += 1;
647            }
648            typed_ir::ExpectedAdapterEdgeKind::BindHere => {
649                profile.bind_here += 1;
650            }
651            typed_ir::ExpectedAdapterEdgeKind::HandlerResidual => {
652                profile.handler_residual += 1;
653            }
654            typed_ir::ExpectedAdapterEdgeKind::HandlerReturn => {
655                profile.handler_return += 1;
656            }
657            typed_ir::ExpectedAdapterEdgeKind::ResumeArgument => {
658                profile.resume_argument += 1;
659            }
660        }
661    }
662    profile
663}
664
665fn derived_expected_evidence_profile(
666    evidence: &typed_ir::PrincipalEvidence,
667) -> DerivedExpectedEvidenceProfile {
668    let mut profile = DerivedExpectedEvidenceProfile::default();
669    for edge in &evidence.derived_expected_edges {
670        profile.total += 1;
671        match edge.kind {
672            typed_ir::DerivedExpectedEdgeKind::RecordField => profile.record_field += 1,
673            typed_ir::DerivedExpectedEdgeKind::TupleItem => profile.tuple_item += 1,
674            typed_ir::DerivedExpectedEdgeKind::VariantPayload => profile.variant_payload += 1,
675            typed_ir::DerivedExpectedEdgeKind::FunctionParam => profile.function_param += 1,
676            typed_ir::DerivedExpectedEdgeKind::FunctionReturn => profile.function_return += 1,
677        }
678        match edge.polarity {
679            typed_ir::EdgePolarity::Covariant => profile.covariant += 1,
680            typed_ir::EdgePolarity::Contravariant => profile.contravariant += 1,
681            typed_ir::EdgePolarity::Invariant => profile.invariant += 1,
682        }
683    }
684    profile
685}
686
687fn normalize_initial_alias_types(
688    bindings: &[typed_ir::PrincipalBinding],
689    env: &mut HashMap<typed_ir::Path, RuntimeType>,
690    binding_infos: &mut HashMap<typed_ir::Path, BindingInfo>,
691) {
692    for _ in 0..bindings.len() {
693        let mut changed = false;
694        for binding in bindings {
695            let typed_ir::Expr::Var(target) = &binding.body else {
696                continue;
697            };
698            let Some(current_ty) = env.get(&binding.name).cloned() else {
699                continue;
700            };
701            let Some(target_ty) = env.get(target).cloned() else {
702                continue;
703            };
704            if !prefer_alias_target_runtime_type(&current_ty, &target_ty) {
705                continue;
706            }
707            if current_ty == target_ty {
708                continue;
709            }
710            env.insert(binding.name.clone(), target_ty.clone());
711            if let Some(info) = binding_infos.get_mut(&binding.name) {
712                info.ty = target_ty.clone();
713                info.type_params = principal_hir_type_params(&target_ty);
714            }
715            changed = true;
716        }
717        if !changed {
718            break;
719        }
720    }
721}
722
723fn direct_aliases(
724    bindings: &[typed_ir::PrincipalBinding],
725) -> HashMap<typed_ir::Path, typed_ir::Path> {
726    bindings
727        .iter()
728        .filter_map(|binding| match &binding.body {
729            typed_ir::Expr::Var(target) if target != &binding.name => {
730                Some((binding.name.clone(), target.clone()))
731            }
732            _ => None,
733        })
734        .collect()
735}
736
737struct Lowerer<'a> {
738    env: HashMap<typed_ir::Path, RuntimeType>,
739    binding_infos: HashMap<typed_ir::Path, BindingInfo>,
740    aliases: HashMap<typed_ir::Path, typed_ir::Path>,
741    graph: &'a typed_ir::CoreGraphView,
742    runtime_symbols: HashMap<typed_ir::Path, typed_ir::RuntimeSymbolKind>,
743    primitive_paths: RuntimePrimitivePathTable,
744    principal_vars: BTreeSet<typed_ir::TypeVar>,
745    expected_edges_by_id: HashMap<u32, &'a typed_ir::ExpectedEdgeEvidence>,
746    use_expected_arg_evidence: bool,
747    use_principal_elaboration: bool,
748    expected_arg_evidence_profile: ExpectedArgEvidenceProfile,
749    runtime_adapter_profile: RuntimeAdapterProfile,
750    local_param_boundaries: HashMap<typed_ir::Path, LocalParamBoundary>,
751    handler_body_depth: usize,
752    current_function_boundary: Option<EffectIdVar>,
753    current_binding: Option<typed_ir::Path>,
754    current_runtime_adapter_source: Option<RuntimeAdapterSource>,
755    next_synthetic_type_var: usize,
756    next_effect_id_var: usize,
757}
758
759#[derive(Clone)]
760struct BindingInfo {
761    ty: RuntimeType,
762    type_params: Vec<typed_ir::TypeVar>,
763    requirements: Vec<typed_ir::RoleRequirement>,
764}
765
766#[derive(Debug, Clone)]
767struct LocalParamBoundary {
768    id: EffectIdVar,
769    effect: typed_ir::Type,
770    applies_to_thunk_var: bool,
771    applies_to_call_outside_handler: bool,
772}
773
774#[cfg(test)]
775mod tests;