1use std::collections::{HashMap, HashSet};
14
15use serde::Serialize;
16
17use crate::ast::{DecisionBlock, FnDef, TopLevel, TypeDef, VerifyBlock};
18use crate::call_graph::{direct_calls, recursive_callsite_counts, recursive_scc_ids};
19use crate::checker::expr_to_str;
20use crate::verify_law::canonical_spec_ref;
21
22#[derive(Clone)]
25pub struct FileContext {
26 pub source_file: String,
27 pub module_name: Option<String>,
28 pub intent: Option<String>,
29 pub depends: Vec<String>,
30 pub exposes: Vec<String>,
31 pub exposes_opaque: Vec<String>,
32 pub api_effects: Vec<String>,
33 pub module_effects: Vec<String>,
34 pub main_effects: Option<Vec<String>>,
35 pub fn_defs: Vec<FnDef>,
37 pub all_fn_defs: Vec<FnDef>,
39 pub fn_auto_tco: HashSet<String>,
40 pub fn_recursive_callsites: HashMap<String, usize>,
41 pub fn_recursive_scc_id: HashMap<String, usize>,
42 pub fn_specs: HashMap<String, Vec<String>>,
43 pub fn_direct_calls: HashMap<String, Vec<String>>,
44 pub type_defs: Vec<TypeDef>,
45 pub verify_blocks: Vec<VerifyBlock>,
46 pub verify_counts: HashMap<String, usize>,
47 pub verify_samples: HashMap<String, Vec<String>>,
48 pub decisions: Vec<DecisionBlock>,
49}
50
51impl FileContext {
52 fn empty(source_file: impl Into<String>) -> Self {
53 Self {
54 source_file: source_file.into(),
55 module_name: None,
56 intent: None,
57 depends: vec![],
58 exposes: vec![],
59 exposes_opaque: vec![],
60 api_effects: vec![],
61 module_effects: vec![],
62 main_effects: None,
63 fn_defs: vec![],
64 all_fn_defs: vec![],
65 fn_auto_tco: HashSet::new(),
66 fn_recursive_callsites: HashMap::new(),
67 fn_recursive_scc_id: HashMap::new(),
68 fn_specs: HashMap::new(),
69 fn_direct_calls: HashMap::new(),
70 type_defs: vec![],
71 verify_blocks: vec![],
72 verify_counts: HashMap::new(),
73 verify_samples: HashMap::new(),
74 decisions: vec![],
75 }
76 }
77}
78
79pub fn build_context_for_items(
88 items: &[TopLevel],
89 _source: &str,
90 file_label: impl Into<String>,
91 module_root: Option<&str>,
92) -> FileContext {
93 let mut ctx = FileContext::empty(file_label);
94
95 let mut declared_module_effects: Option<Vec<String>> = None;
96 for item in items {
97 match item {
98 TopLevel::Module(m) => {
99 ctx.module_name = Some(m.name.clone());
100 ctx.intent = if m.intent.is_empty() {
101 None
102 } else {
103 Some(m.intent.clone())
104 };
105 ctx.depends = m.depends.clone();
106 ctx.exposes = m.exposes.clone();
107 ctx.exposes_opaque = m.exposes_opaque.clone();
108 declared_module_effects = m.effects.clone();
109 }
110 TopLevel::FnDef(fd) => {
111 ctx.fn_defs.push(fd.clone());
112 ctx.all_fn_defs.push(fd.clone());
113 }
114 TopLevel::TypeDef(td) => ctx.type_defs.push(td.clone()),
115 TopLevel::Verify(vb) => ctx.verify_blocks.push(vb.clone()),
116 TopLevel::Decision(db) => ctx.decisions.push(db.clone()),
117 _ => {}
118 }
119 }
120
121 let flags = compute_context_fn_flags(items, module_root);
122 let ContextFnFlags {
123 auto_tco,
124 recursive_callsites,
125 recursive_scc_id,
126 fn_sigs,
127 } = flags;
128 ctx.fn_auto_tco = auto_tco;
129 ctx.fn_recursive_callsites = recursive_callsites;
130 ctx.fn_recursive_scc_id = recursive_scc_id;
131 ctx.fn_direct_calls = direct_calls(items);
132
133 for vb in &ctx.verify_blocks {
134 let crate::ast::VerifyKind::Law(law) = &vb.kind else {
135 continue;
136 };
137 let Some(spec_ref) = canonical_spec_ref(&vb.fn_name, law, &fn_sigs) else {
138 continue;
139 };
140 ctx.fn_specs
141 .entry(vb.fn_name.clone())
142 .or_default()
143 .push(spec_ref.spec_fn_name);
144 }
145 for specs in ctx.fn_specs.values_mut() {
146 specs.sort();
147 specs.dedup();
148 }
149
150 let (verify_counts, verify_samples) = build_verify_summaries(&ctx.verify_blocks, &fn_sigs);
151 ctx.verify_counts = verify_counts;
152 ctx.verify_samples = verify_samples;
153
154 ctx.module_effects = if let Some(declared) = declared_module_effects {
161 unique_sorted_effects(declared.iter())
162 } else {
163 unique_sorted_effects(
164 ctx.fn_defs
165 .iter()
166 .flat_map(|fd| fd.effects.iter().map(|e| &e.node)),
167 )
168 };
169 ctx.api_effects = unique_sorted_effects(
170 ctx.fn_defs
171 .iter()
172 .filter(|fd| ctx.exposes.contains(&fd.name))
173 .flat_map(|fd| fd.effects.iter().map(|e| &e.node)),
174 );
175 ctx.main_effects = ctx
176 .fn_defs
177 .iter()
178 .find(|fd| fd.name == "main")
179 .map(|fd| unique_sorted_effects(fd.effects.iter().map(|e| &e.node)));
180
181 if !ctx.exposes.is_empty() {
182 let exposes = ctx.exposes.clone();
183 ctx.fn_defs.retain(|fd| exposes.contains(&fd.name));
184 }
185
186 ctx
187}
188
189#[derive(Clone, Debug, Serialize)]
192pub struct ContextSummary {
193 pub file_label: String,
194 pub module_name: Option<String>,
195 pub intent: Option<String>,
196 pub depends: Vec<String>,
197 pub exposes: Vec<String>,
198 pub exposes_opaque: Vec<String>,
199 pub api_effects: Vec<String>,
200 pub module_effects: Vec<String>,
201 #[serde(skip_serializing_if = "Option::is_none")]
202 pub main_effects: Option<Vec<String>>,
203 pub functions: Vec<ContextFnSummary>,
204 pub types: Vec<ContextTypeSummary>,
205 pub decisions: Vec<ContextDecisionSummary>,
206}
207
208#[derive(Clone, Debug, Serialize)]
209pub struct ContextFnSummary {
210 pub name: String,
211 pub signature: String,
212 #[serde(skip_serializing_if = "Option::is_none")]
213 pub description: Option<String>,
214 pub effects: Vec<String>,
215 pub auto_tco: bool,
216 pub recursive_callsites: usize,
217 pub verify_count: usize,
218 pub verify_samples: Vec<String>,
219 pub is_exposed: bool,
220 pub specs: Vec<String>,
221 pub direct_calls: Vec<String>,
222}
223
224#[derive(Clone, Debug, Serialize)]
225pub struct ContextTypeSummary {
226 pub name: String,
227 pub kind: &'static str,
228 pub fields_or_variants: Vec<String>,
229}
230
231#[derive(Clone, Debug, Serialize)]
232pub struct ContextDecisionSummary {
233 pub name: String,
234 pub date: String,
235 pub reason_prefix: String,
236 pub impacts: Vec<String>,
237}
238
239pub fn summarize(ctx: &FileContext) -> ContextSummary {
245 let functions = ctx
246 .all_fn_defs
247 .iter()
248 .map(|fd| {
249 let effects: Vec<String> = fd.effects.iter().map(|e| e.node.clone()).collect();
250 let description = fd.desc.clone();
251 let signature = render_signature(fd);
252 let is_exposed = ctx.exposes.is_empty() || ctx.exposes.contains(&fd.name);
253 let specs = ctx.fn_specs.get(&fd.name).cloned().unwrap_or_default();
254 let direct = ctx
255 .fn_direct_calls
256 .get(&fd.name)
257 .cloned()
258 .unwrap_or_default();
259 ContextFnSummary {
260 name: fd.name.clone(),
261 signature,
262 description,
263 effects,
264 auto_tco: ctx.fn_auto_tco.contains(&fd.name),
265 recursive_callsites: ctx
266 .fn_recursive_callsites
267 .get(&fd.name)
268 .copied()
269 .unwrap_or(0),
270 verify_count: ctx.verify_counts.get(&fd.name).copied().unwrap_or(0),
271 verify_samples: ctx
272 .verify_samples
273 .get(&fd.name)
274 .cloned()
275 .unwrap_or_default(),
276 is_exposed,
277 specs,
278 direct_calls: direct,
279 }
280 })
281 .collect();
282
283 let types = ctx
284 .type_defs
285 .iter()
286 .map(|td| match td {
287 TypeDef::Sum { name, variants, .. } => ContextTypeSummary {
288 name: name.clone(),
289 kind: "sum",
290 fields_or_variants: variants.iter().map(|v| v.name.clone()).collect(),
291 },
292 TypeDef::Product { name, fields, .. } => ContextTypeSummary {
293 name: name.clone(),
294 kind: "product",
295 fields_or_variants: fields.iter().map(|(n, _)| n.clone()).collect(),
296 },
297 })
298 .collect();
299
300 let decisions = ctx
301 .decisions
302 .iter()
303 .map(|d| {
304 let reason_prefix: String = d.reason.chars().take(60).collect();
305 let reason_prefix = if d.reason.len() > 60 {
306 format!("{}...", reason_prefix.trim_end())
307 } else {
308 reason_prefix
309 };
310 ContextDecisionSummary {
311 name: d.name.clone(),
312 date: d.date.clone(),
313 reason_prefix,
314 impacts: d
315 .impacts
316 .iter()
317 .map(|i| i.node.text().to_string())
318 .collect(),
319 }
320 })
321 .collect();
322
323 ContextSummary {
324 file_label: ctx.source_file.clone(),
325 module_name: ctx.module_name.clone(),
326 intent: ctx.intent.clone(),
327 depends: ctx.depends.clone(),
328 exposes: ctx.exposes.clone(),
329 exposes_opaque: ctx.exposes_opaque.clone(),
330 api_effects: ctx.api_effects.clone(),
331 module_effects: ctx.module_effects.clone(),
332 main_effects: ctx.main_effects.clone(),
333 functions,
334 types,
335 decisions,
336 }
337}
338
339pub fn render_context_md(summary: &ContextSummary) -> String {
344 let mut out = String::new();
345 out.push_str(&format!("# Aver Context — {}\n\n", summary.file_label));
346 out.push_str("_Generated by `aver context`_\n\n");
347
348 out.push_str("---\n\n");
349 if let Some(name) = &summary.module_name {
350 out.push_str(&format!("## Module: {}\n\n", name));
351 } else {
352 out.push_str(&format!("## {}\n\n", summary.file_label));
353 }
354
355 if let Some(intent) = &summary.intent {
356 out.push_str(&format!("> {}\n\n", intent));
357 }
358
359 if !summary.depends.is_empty() {
360 out.push_str(&format!("depends: `[{}]` \n", summary.depends.join(", ")));
361 }
362 if !summary.exposes.is_empty() {
363 out.push_str(&format!("exposes: `[{}]` \n", summary.exposes.join(", ")));
364 }
365 if !summary.exposes_opaque.is_empty() {
366 out.push_str(&format!(
367 "exposes opaque: `[{}]` \n",
368 summary.exposes_opaque.join(", ")
369 ));
370 }
371
372 if effects_equal(&summary.api_effects, &summary.module_effects) {
374 if !summary.module_effects.is_empty() {
375 out.push_str(&format!(
376 "effects: `[{}]`\n",
377 summary.module_effects.join(", ")
378 ));
379 }
380 } else {
381 out.push_str(&format!(
382 "api_effects: `[{}]` \nmodule_effects: `[{}]`\n",
383 summary.api_effects.join(", "),
384 summary.module_effects.join(", ")
385 ));
386 }
387 if let Some(main_effects) = &summary.main_effects {
388 out.push_str(&format!("main_effects: `[{}]`\n", main_effects.join(", ")));
389 }
390 out.push('\n');
391
392 for ty in &summary.types {
393 let header = match ty.kind {
394 "record" => format!("### record {}\n", ty.name),
395 _ => format!("### type {}\n", ty.name),
396 };
397 out.push_str(&header);
398 if !ty.fields_or_variants.is_empty() {
399 out.push_str(&format!("`{}`\n\n", ty.fields_or_variants.join("` | `")));
400 } else {
401 out.push('\n');
402 }
403 }
404
405 for fd in &summary.functions {
406 if fd.name == "main" {
407 continue;
408 }
409 out.push_str(&format!("### `{}`\n", fd.signature));
410 if !fd.effects.is_empty() {
411 out.push_str(&format!("effects: `[{}]` \n", fd.effects.join(", ")));
412 }
413 if fd.auto_tco {
414 out.push_str("tco: `true` \n");
415 }
416 if !fd.specs.is_empty() {
417 let label = if fd.specs.len() == 1 { "spec" } else { "specs" };
418 out.push_str(&format!("{}: `[{}]` \n", label, fd.specs.join(", ")));
419 }
420 if !fd.is_exposed {
421 out.push_str("visibility: `private` \n");
422 }
423 if let Some(desc) = &fd.description {
424 out.push_str(&format!("> {}\n", desc));
425 }
426 if !fd.verify_samples.is_empty() {
427 let samples: Vec<String> = fd
428 .verify_samples
429 .iter()
430 .map(|s| format!("`{}`", s))
431 .collect();
432 out.push_str(&format!("verify: {}\n", samples.join(", ")));
433 }
434 out.push('\n');
435 }
436
437 if !summary.decisions.is_empty() {
438 out.push_str("---\n\n## Decisions\n\n");
439 for dec in &summary.decisions {
440 out.push_str(&format!("### {} ({})\n", dec.name, dec.date));
441 if !dec.reason_prefix.is_empty() {
442 out.push_str(&format!("> {}\n", dec.reason_prefix));
443 }
444 if !dec.impacts.is_empty() {
445 out.push_str(&format!("impacts: `{}`\n", dec.impacts.join("`, `")));
446 }
447 out.push('\n');
448 }
449 }
450
451 out
452}
453
454fn effects_equal(a: &[String], b: &[String]) -> bool {
455 if a.len() != b.len() {
456 return false;
457 }
458 let mut a = a.to_vec();
459 let mut b = b.to_vec();
460 a.sort();
461 b.sort();
462 a == b
463}
464
465fn render_signature(fd: &FnDef) -> String {
466 let params = fd
470 .params
471 .iter()
472 .map(|(name, type_str)| format!("{}: {}", name, type_str))
473 .collect::<Vec<_>>()
474 .join(", ");
475 format!("fn {}({}) -> {}", fd.name, params, fd.return_type)
476}
477
478const VERIFY_SAMPLE_LIMIT: usize = 3;
481const VERIFY_CASE_MAX_LEN: usize = 150;
482
483fn unique_sorted_effects<'a, I>(effects: I) -> Vec<String>
484where
485 I: Iterator<Item = &'a String>,
486{
487 let mut uniq = effects
488 .cloned()
489 .collect::<HashSet<_>>()
490 .into_iter()
491 .collect::<Vec<_>>();
492 uniq.sort();
493 uniq
494}
495
496fn classify_verify_case(lhs: &str, rhs: &str, ret_category: Option<&str>) -> Vec<String> {
497 let combined = format!("{lhs} -> {rhs}");
498 let mut categories = Vec::new();
499
500 match ret_category {
501 Some("result") => {
502 if rhs.contains("Result.Ok(") || rhs.contains("Ok(") {
503 categories.push("ok".to_string());
504 }
505 if rhs.contains("Result.Err(") || rhs.contains("Err(") {
506 categories.push("err".to_string());
507 }
508 }
509 Some("option") => {
510 if rhs.contains("Option.Some(") || rhs.contains("Some(") {
511 categories.push("some".to_string());
512 }
513 if rhs.contains("Option.None") || rhs == "None" {
514 categories.push("none".to_string());
515 }
516 }
517 Some("bool") => {
518 if rhs == "true" {
519 categories.push("true".to_string());
520 }
521 if rhs == "false" {
522 categories.push("false".to_string());
523 }
524 }
525 _ => {}
526 }
527
528 if combined.contains("[]") || combined.contains("{}") {
529 categories.push("empty".to_string());
530 }
531 if combined.contains("-1") || combined.contains("(0 - ") {
532 categories.push("negative".to_string());
533 }
534 if combined.contains("(0)") || rhs == "0" {
535 categories.push("zero".to_string());
536 }
537 if combined.contains("\"\"") {
538 categories.push("empty-string".to_string());
539 }
540
541 if ret_category == Some("named")
542 && let Some(dot_pos) = rhs.find('.')
543 {
544 let after_dot = &rhs[dot_pos + 1..];
545 let ctor = after_dot.split('(').next().unwrap_or(after_dot);
546 categories.push(format!("ctor:{ctor}"));
547 }
548
549 categories.sort();
550 categories.dedup();
551 categories
552}
553
554fn base_verify_case_score(lhs: &str, rhs: &str) -> i32 {
555 let combined_len = lhs.len() + rhs.len();
556 let mut score = 400 - combined_len as i32;
557 let combined = format!("{lhs} -> {rhs}");
558 if rhs.contains("Result.Err(")
559 || rhs.contains("ParseResult.Err(")
560 || rhs.contains("Option.None")
561 {
562 score += 120;
563 }
564 if combined.contains("[]") || combined.contains("{}") {
565 score += 60;
566 }
567 if combined.contains("\"\"") {
568 score += 45;
569 }
570 if combined.contains("-1") || combined.contains("(0 - ") {
571 score += 45;
572 }
573 if combined.contains(", 0") || combined.contains("(0)") || rhs == "0" {
574 score += 30;
575 }
576 if rhs == "true" || rhs == "false" {
577 score += 20;
578 }
579 score
580}
581
582fn scored_verify_samples(cases: &[(String, String)], ret_category: Option<&str>) -> Vec<String> {
583 #[derive(Clone)]
584 struct ScoredVerifyCase {
585 rendered: String,
586 base_score: i32,
587 categories: Vec<String>,
588 original_index: usize,
589 }
590
591 let mut scored = cases
592 .iter()
593 .enumerate()
594 .filter_map(|(original_index, (lhs_text, rhs_text))| {
595 if lhs_text.len() + rhs_text.len() > VERIFY_CASE_MAX_LEN {
596 return None;
597 }
598 Some(ScoredVerifyCase {
599 rendered: format!("{lhs_text} => {rhs_text}"),
600 base_score: base_verify_case_score(lhs_text, rhs_text),
601 categories: classify_verify_case(lhs_text, rhs_text, ret_category),
602 original_index,
603 })
604 })
605 .collect::<Vec<_>>();
606
607 let mut selected = Vec::new();
608 let mut seen_categories: HashSet<String> = HashSet::new();
609 while selected.len() < VERIFY_SAMPLE_LIMIT && !scored.is_empty() {
610 let best_idx = scored
611 .iter()
612 .enumerate()
613 .max_by_key(|(_, case)| {
614 let novelty = case
615 .categories
616 .iter()
617 .filter(|cat| !seen_categories.contains(cat.as_str()))
618 .count() as i32;
619 (
620 case.base_score + novelty * 35,
621 case.base_score,
622 -(case.original_index as i32),
623 )
624 })
625 .map(|(idx, _)| idx)
626 .expect("verify samples should be non-empty");
627 let chosen = scored.swap_remove(best_idx);
628 for category in &chosen.categories {
629 seen_categories.insert(category.clone());
630 }
631 selected.push(chosen.rendered);
632 }
633 selected
634}
635
636fn return_type_category(
637 fn_name: &str,
638 fn_sigs: &HashMap<String, (Vec<crate::types::Type>, crate::types::Type, Vec<String>)>,
639) -> Option<&'static str> {
640 let (_, ret, _) = fn_sigs.get(fn_name)?;
641 match ret {
642 crate::types::Type::Result(_, _) => Some("result"),
643 crate::types::Type::Option(_) => Some("option"),
644 crate::types::Type::Bool => Some("bool"),
645 crate::types::Type::List(_) => Some("list"),
646 crate::types::Type::Named { .. } => Some("named"),
647 _ => None,
648 }
649}
650
651fn build_verify_summaries(
652 verify_blocks: &[VerifyBlock],
653 fn_sigs: &HashMap<String, (Vec<crate::types::Type>, crate::types::Type, Vec<String>)>,
654) -> (HashMap<String, usize>, HashMap<String, Vec<String>>) {
655 let mut cases_by_fn: HashMap<String, Vec<(String, String)>> = HashMap::new();
656 for block in verify_blocks {
657 let entry = cases_by_fn.entry(block.fn_name.clone()).or_default();
658 for (lhs, rhs) in &block.cases {
659 entry.push((expr_to_str(lhs), expr_to_str(rhs)));
660 }
661 }
662
663 let verify_counts = cases_by_fn
664 .iter()
665 .map(|(fn_name, cases)| (fn_name.clone(), cases.len()))
666 .collect::<HashMap<_, _>>();
667 let verify_samples = cases_by_fn
668 .into_iter()
669 .map(|(fn_name, cases)| {
670 let ret_cat = return_type_category(&fn_name, fn_sigs);
671 (fn_name, scored_verify_samples(&cases, ret_cat))
672 })
673 .collect::<HashMap<_, _>>();
674
675 (verify_counts, verify_samples)
676}
677
678struct ContextFnFlags {
679 auto_tco: HashSet<String>,
680 recursive_callsites: HashMap<String, usize>,
681 recursive_scc_id: HashMap<String, usize>,
682 fn_sigs: HashMap<String, (Vec<crate::types::Type>, crate::types::Type, Vec<String>)>,
683}
684
685fn expr_has_tail_call(expr: &crate::ast::Spanned<crate::ast::Expr>) -> bool {
686 use crate::ast::Expr;
687 match &expr.node {
688 Expr::TailCall(_) => true,
689 Expr::Literal(_) | Expr::Ident(_) | Expr::Resolved { .. } => false,
690 Expr::Attr(obj, _) => expr_has_tail_call(obj),
691 Expr::FnCall(f, args) => expr_has_tail_call(f) || args.iter().any(expr_has_tail_call),
692 Expr::BinOp(_, l, r) => expr_has_tail_call(l) || expr_has_tail_call(r),
693 Expr::Neg(inner) => expr_has_tail_call(inner),
694 Expr::Match { subject, arms, .. } => {
695 expr_has_tail_call(subject) || arms.iter().any(|arm| expr_has_tail_call(&arm.body))
696 }
697 Expr::Constructor(_, arg) => arg.as_ref().is_some_and(|a| expr_has_tail_call(a)),
698 Expr::ErrorProp(inner) => expr_has_tail_call(inner),
699 Expr::InterpolatedStr(parts) => parts.iter().any(|part| match part {
700 crate::ast::StrPart::Literal(_) => false,
701 crate::ast::StrPart::Parsed(e) => expr_has_tail_call(e),
702 }),
703 Expr::List(items) | Expr::Tuple(items) | Expr::IndependentProduct(items, _) => {
704 items.iter().any(expr_has_tail_call)
705 }
706 Expr::MapLiteral(entries) => entries
707 .iter()
708 .any(|(k, v)| expr_has_tail_call(k) || expr_has_tail_call(v)),
709 Expr::RecordCreate { fields, .. } => fields.iter().any(|(_, e)| expr_has_tail_call(e)),
710 Expr::RecordUpdate { base, updates, .. } => {
711 expr_has_tail_call(base) || updates.iter().any(|(_, e)| expr_has_tail_call(e))
712 }
713 }
714}
715
716fn fn_has_tail_call(fd: &FnDef) -> bool {
717 fd.body.stmts().iter().any(|stmt| match stmt {
718 crate::ast::Stmt::Binding(_, _, expr) | crate::ast::Stmt::Expr(expr) => {
719 expr_has_tail_call(expr)
720 }
721 })
722}
723
724fn compute_context_fn_flags(items: &[TopLevel], module_root: Option<&str>) -> ContextFnFlags {
725 let mut transformed = items.to_vec();
726 crate::ir::pipeline::tco(&mut transformed);
727 let tco_fns = transformed
728 .iter()
729 .filter_map(|item| match item {
730 TopLevel::FnDef(fd) if fn_has_tail_call(fd) => Some(fd.name.clone()),
731 _ => None,
732 })
733 .collect::<HashSet<_>>();
734 let recursive_callsites = recursive_callsite_counts(&transformed);
735 let recursive_scc_id = recursive_scc_ids(&transformed);
736
737 let tc_result = crate::ir::pipeline::typecheck(
738 &transformed,
739 &crate::ir::TypecheckMode::Full {
740 base_dir: module_root,
741 },
742 );
743
744 ContextFnFlags {
745 auto_tco: tco_fns,
746 recursive_callsites,
747 recursive_scc_id,
748 fn_sigs: tc_result.fn_sigs,
749 }
750}