ronantakizawa/codereview-bench huggingface dataset:
Repository: HigherOrderCO/Bend
File: src/lib.rs (Rust)

Code before:

impl Opts {
  /// All optimizations enabled.
  pub fn heavy() -> Self {
    Self {
      eta: true,
      ref_to_ref: true,
      prune: true,
      pre_reduce: true,
      supercombinators: true,
      simplify_main: true,
      pre_reduce_refs: true,
      merge_definitions: true,
    }
  }

  /// All optimizations disabled, except detach supercombinators.
  pub fn light() -> Self {
    Self { supercombinators: true, ..Self::default() }
  }
}

impl Opts {
  pub fn check(&self) {
    if !self.supercombinators {
      println!("Warning: Running in strict mode can lead some functions expand infinitely.");
    }
  }
}

#[derive(Default, Clone, Copy)]
pub struct WarningOpts {
  pub match_only_vars: WarnState,
  pub unused_defs: WarnState,
}

#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum WarnState {
  #[default]
  Warn,
  Allow,
  Deny,
}

impl WarningOpts {
  pub fn allow_all() -> Self {
    Self { match_only_vars: WarnState::Allow, unused_defs: WarnState::Allow }
  }

  pub fn deny_all() -> Self {
    Self { match_only_vars: WarnState::Deny, unused_defs: WarnState::Deny }

Review comment (suggestion):
```suggestion
      println!("Warning: Running in strict mode without enabling the supercombinators pass can lead to some functions expanding infinitely.");
```

Code after:

impl Opts {
  /// All optimizations enabled.
  pub fn heavy() -> Self {
    Self {
      eta: true,
      ref_to_ref: true,
      prune: true,
      pre_reduce: true,
      supercombinators: true,
      simplify_main: true,
      pre_reduce_refs: true,
      merge_definitions: true,
    }
  }

  /// All optimizations disabled, except detach supercombinators.
  pub fn light() -> Self {
    Self { supercombinators: true, ..Self::default() }
  }
}

impl Opts {
  pub fn check(&self) {
    if !self.supercombinators {
      println!("Warning: Running in strict mode without enabling the supercombinators pass can lead to some functions expanding infinitely.");
    }
  }
}

#[derive(Default, Clone, Copy)]
pub struct WarningOpts {
  pub match_only_vars: WarnState,
  pub unused_defs: WarnState,
}

#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum WarnState {
  #[default]
  Warn,
  Allow,
  Deny,
}

impl WarningOpts {
  pub fn allow_all() -> Self {
    Self { match_only_vars: WarnState::Allow, unused_defs: WarnState::Allow }
  }

  pub fn deny_all() -> Self {
    Self { match_only_vars: WarnState::Deny, unused_defs: WarnState::Deny }
Repository: HigherOrderCO/Bend
File: src/term/check/ctrs_arities.rs (Rust)

Code before:
    Ok(())
  }

  /// Returns a hashmap of the constructor name to its arity.
  pub fn ctr_arities(&self) -> HashMap<Name, usize> {
    let mut arities = HashMap::new();

    for adt in self.adts.values() {
      for (ctr_name, fields) in adt.ctrs.iter() {
        arities.insert(ctr_name.clone(), fields.len());
      }
    }

    arities
  }
}

impl Pattern {
  fn check(&self, arities: &HashMap<Name, usize>) -> Result<(), String> {
    match self {
      Pattern::Ctr(name, args) => {
        let arity = arities.get(name).unwrap();
        let args = args.len();
        if *arity != args {
          return Err(format!(
            "Arity error. Constructor '{}' expects {} fields but was found {}.",
            name, arity, args
          ));
        } else {
          Ok(())
        }
      }
      Pattern::Tup(fst, snd) => {
        fst.check(arities)?;
        snd.check(arities)
      }
      Pattern::List(els) => {
        for el in els {
          el.check(arities)?;
        }
        Ok(())
      }
      Pattern::Var(..) | Pattern::Num(..) => Ok(()),
    }
  }
}


Review comment (suggestion):
```suggestion
            "Arity error. Constructor '{}' expects {} fields, found {}.",
```

Code after:
    Ok(())
  }

  /// Returns a hashmap of the constructor name to its arity.
  pub fn ctr_arities(&self) -> HashMap<Name, usize> {
    let mut arities = HashMap::new();

    for adt in self.adts.values() {
      for (ctr_name, fields) in adt.ctrs.iter() {
        arities.insert(ctr_name.clone(), fields.len());
      }
    }

    arities
  }
}

impl Pattern {
  fn check(&self, arities: &HashMap<Name, usize>) -> Result<(), String> {
    match self {
      Pattern::Ctr(name, args) => {
        let arity = arities.get(name).unwrap();
        let args = args.len();
        if *arity != args {
          return Err(format!(
            "Arity error. Constructor '{}' expects {} fields, found {}.",
            name, arity, args
          ));
        } else {
          Ok(())
        }
      }
      Pattern::Tup(fst, snd) => {
        fst.check(arities)?;
        snd.check(arities)
      }
      Pattern::List(els) => {
        for el in els {
          el.check(arities)?;
        }
        Ok(())
      }
      Pattern::Var(..) | Pattern::Num(..) => Ok(()),
    }
  }
}
Repository: HigherOrderCO/Bend
File: src/term/mod.rs (Rust)

Code before:
    self.contains('$') || self.contains('%')
  }
}

impl From<String> for Name {
  fn from(value: String) -> Self {
    Name(Arc::new(value))
  }
}

impl From<Val> for Name {
  fn from(value: Val) -> Self {
    num_to_name(value).into()
  }
}

impl Deref for Name {
  type Target = String;

  fn deref(&self) -> &Self::Target {
    self.0.deref()
  }
}

impl Book {
  pub fn entrypoint(&self) -> String {
    if let Some(nam) = &self.entrypoint { nam.to_string() } else { ENTRY_POINT.to_string() }
  }
}


Review comment (suggestion):
This is the hvmc entrypoint right? Also it's a bit confusing to call it the same name as one of the fields of `Book`, so let's change it to `Book::hvmc_entrypoint`.

Code after:
    self.contains('$') || self.contains('%')
  }
}

impl From<String> for Name {
  fn from(value: String) -> Self {
    Name(Arc::new(value))
  }
}

impl From<Val> for Name {
  fn from(value: Val) -> Self {
    num_to_name(value).into()
  }
}

impl Deref for Name {
  type Target = String;

  fn deref(&self) -> &Self::Target {
    self.0.deref()
  }
}

impl Book {
  pub fn hvmc_entrypoint(&self) -> String {
    if let Some(nam) = &self.entrypoint { nam.to_string() } else { ENTRY_POINT.to_string() }
  }
}
Repository: HigherOrderCO/Bend
File: src/term/parser/lexer.rs (Rust)

Code before:
pub enum Token {
  #[regex("[_.a-zA-Z][_.a-zA-Z0-9-]*", |lex| lex.slice().parse().ok().map(|s: String| STRINGS.get(s)))]
  Name(GlobalString),

  #[regex("@|λ")]
  Lambda,

  #[token("$")]
  Dollar,

  #[token("let")]
  Let,

  #[token("match")]
  Match,

  #[token("=")]
  Equals,

  #[regex("0[bB][0-9a-zA-Z_]+", |lex| from_radix(2, lex))]
  #[regex("0[xX][0-9a-zA-Z_]+", |lex| from_radix(16, lex))]
  #[regex("[0-9][0-9a-zA-Z_]*", |lex| from_radix(10, lex))]
  Num(u64),

  #[regex(r#""([^"\\]|\\[0tunr'"\\])*""#, |lex| normalized_string(lex).ok())]
  #[regex(r#"`([^`\\]|\\[0tunr`"\\])*`"#, |lex| normalized_string(lex).ok())]
  Str(GlobalString),

  #[regex(r#"'\\U[0-9a-fA-F]{1,8}'"#, normalized_char, priority = 2)]
  // Since '.' is just covering any ascii char, we need to make the
  // regex match any possible character of the unicode general category
  #[regex(
    r#"'(\p{L}|\p{M}|\p{N}|\p{P}|\p{S}|\p{Z}|\p{C}|\p{Emoji}|\\u[0-9a-fA-F]{1,4}|\\[0tunr'"\\])'"#,
    normalized_char
  )]
  Char(u64),

  #[token("#")]
  Hash,

  #[token("+")]
  Add,

  #[token("-")]
  Sub,

  #[token("*")]
  Asterisk,

  #[token("/")]
  Div,

Review comment (security):
Here you define the \` escape sequence, but you don't process it in the function below and don't test it

Code after:
pub enum Token {
  #[regex("[_.a-zA-Z][_.a-zA-Z0-9-]*", |lex| lex.slice().parse().ok().map(|s: String| STRINGS.get(s)))]
  Name(GlobalString),

  #[regex("@|λ")]
  Lambda,

  #[token("$")]
  Dollar,

  #[token("let")]
  Let,

  #[token("match")]
  Match,

  #[token("=")]
  Equals,

  #[regex("0[bB][0-9a-zA-Z_]+", |lex| from_radix(2, lex))]
  #[regex("0[xX][0-9a-zA-Z_]+", |lex| from_radix(16, lex))]
  #[regex("[0-9][0-9a-zA-Z_]*", |lex| from_radix(10, lex))]
  Num(u64),

  #[regex(r#""([^"\\]|\\[0tunr'"\\])*""#, |lex| normalized_string(lex).ok())]
  #[regex(r#"`([^`\\]|\\[0tunr`'"\\])*`"#, |lex| normalized_string(lex).ok())]
  Str(GlobalString),

  #[regex(r#"'\\U[0-9a-fA-F]{1,8}'"#, normalized_char, priority = 2)]
  // Since '.' is just covering any ascii char, we need to make the
  // regex match any possible character of the unicode general category
  #[regex(
    r#"'(\p{L}|\p{M}|\p{N}|\p{P}|\p{S}|\p{Z}|\p{C}|\p{Emoji}|\\u[0-9a-fA-F]{1,4}|\\[0tunr`'"\\])'"#,
    normalized_char
  )]
  Char(u64),

  #[token("#")]
  Hash,

  #[token("+")]
  Add,

  #[token("-")]
  Sub,

  #[token("*")]
  Asterisk,

  #[token("/")]
  Div,
Repository: HigherOrderCO/Bend
File: src/lib.rs (Rust)

Code before:
        let resugar_errs = term.resugar_adts(&book, compile_opts.adt_encoding);
        term.resugar_builtins();

        readback_errors.extend(resugar_errs);
        if let Term::Str { val } = term {
          println!("{val}");
        }
      }
    }))),
  );
  let book = ast::Book::from_str("@HVM.black_box = (x x)").unwrap();
  host.lock().unwrap().insert_book(&book);

  host
}

pub fn check_book(book: &mut Book) -> Result<(), Info> {
  // TODO: Do the checks without having to do full compilation
  // TODO: Shouldn't the check mode show warnings?
  compile_book(book, None, CompileOpts::light())?;
  Ok(())
}

pub fn compile_book(
  book: &mut Book,
  args: Option<Vec<Term>>,
  opts: CompileOpts,
) -> Result<CompileResult, Info> {
  let warns = desugar_book(book, args, opts)?;
  let (nets, labels) = book_to_nets(book);

  let mut core_book = nets_to_hvmc(nets)?;
  if opts.pre_reduce {
    pre_reduce_book(&mut core_book, book.hvmc_entrypoint())?;
  }
  if opts.prune {
    prune_defs(&mut core_book, book.hvmc_entrypoint().to_string());
  }
  Ok(CompileResult { core_book, labels, warns })
}

pub fn desugar_book(
  book: &mut Book,
  args: Option<Vec<Term>>,
  opts: CompileOpts,
) -> Result<Vec<Warning>, Info> {
  let mut ctx = Ctx::new(book);

  ctx.check_shared_names();
  ctx.set_entrypoint(if let Some(args) = &args { args.len() } else { 0 });


Review comment (suggestion):
For compiling programs that receive arguments, we want to compile down to an hvm-core program that receives arguments instead of passing the arguments as compile-time constants like this.

For running programs with hvm-lang the current approach is good

Code after:
        let resugar_errs = term.resugar_adts(&book, compile_opts.adt_encoding);
        term.resugar_builtins();

        readback_errors.extend(resugar_errs);
        if let Term::Str { val } = term {
          println!("{val}");
        }
      }
    }))),
  );
  let book = ast::Book::from_str("@HVM.black_box = (x x)").unwrap();
  host.lock().unwrap().insert_book(&book);

  host
}

pub fn check_book(book: &mut Book) -> Result<(), Info> {
  // TODO: Do the checks without having to do full compilation
  // TODO: Shouldn't the check mode show warnings?
  compile_book(book, CompileOpts::light(), None)?;
  Ok(())
}

pub fn compile_book(
  book: &mut Book,
  opts: CompileOpts,
  args: Option<Vec<Term>>,
) -> Result<CompileResult, Info> {
  let warns = desugar_book(book, opts, args)?;
  let (nets, labels) = book_to_nets(book);

  let mut core_book = nets_to_hvmc(nets)?;
  if opts.pre_reduce {
    pre_reduce_book(&mut core_book, book.hvmc_entrypoint())?;
  }
  if opts.prune {
    prune_defs(&mut core_book, book.hvmc_entrypoint().to_string());
  }
  Ok(CompileResult { core_book, labels, warns })
}

pub fn desugar_book(
  book: &mut Book,
  opts: CompileOpts,
  args: Option<Vec<Term>>,
) -> Result<Vec<Warning>, Info> {
  let mut ctx = Ctx::new(book);

  ctx.check_shared_names();
  ctx.set_entrypoint();
  ctx.apply_args(args)?;
Repository: HigherOrderCO/Bend
File: src/term/transform/apply_args.rs (Rust)

Code before:
use crate::term::{Book, Pattern, Term};

impl Book {
  pub fn apply_args(&mut self, args: Option<Vec<Term>>) -> Result<(), String> {
    if let Some(entrypoint) = &self.entrypoint
      && let Some(args) = args
    {
      let main_def = &mut self.defs[entrypoint];
      let expected = main_def.rules[0].pats.len();
      let got = args.len();

      if !main_def.rules[0].pats.iter().all(|pat| matches!(pat, Pattern::Var(Some(..)))) {
        return Err("Definition '{entrypoint}' should contain only var patterns.".into());
      }

      if expected != got {
        return Err(format!("Expected {expected} arguments, got {got}."));
      }

      main_def.convert_match_def_to_term();
      let main_body = &mut self.defs[entrypoint].rule_mut().body;

      *main_body = Term::call(main_body.clone(), args);
    }
    Ok(())
  }
}


Review comment (suggestion):
We don't want to return a string, but rather an Info on errors. That way we don't lose previous errors.

Check how other passes emit fatal errors.

Code after:
use std::fmt::Display;

use crate::{
  diagnostics::Info,
  term::{Ctx, Pattern, Term},
};

#[derive(Clone, Debug)]
pub enum ArgError {
  PatternArgError,
  ArityArgError { expected: usize, got: usize },
}

impl Display for ArgError {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    match self {
      ArgError::PatternArgError => write!(f, ""),
      ArgError::ArityArgError { expected, got } => write!(f, "Expected {expected} arguments, got {got}."),
    }
  }
}

impl Ctx<'_> {
  pub fn apply_args(&mut self, args: Option<Vec<Term>>) -> Result<(), Info> {
    self.info.start_pass();

    if let Some(entrypoint) = &self.book.entrypoint {
      let main_def = &mut self.book.defs[entrypoint];
Repository: HigherOrderCO/Bend
File: src/hvmc_net/mutual_recursion.rs (Rust)

Code before:
use hvmc::ast::{Book, Tree};
use indexmap::{IndexMap, IndexSet};
use std::fmt::Debug;

type Ref = String;
type Stack<T> = Vec<T>;
type RefSet = IndexSet<Ref>;

#[derive(Default)]
pub struct Graph(IndexMap<Ref, RefSet>);

pub fn check_cycles(book: &Book) -> Result<(), String> {
  let graph = Graph::from(book);
  let cycles = graph.cycles();

  if cycles.is_empty() { Ok(()) } else { Err(pretty_print_cycles(&cycles)) }
}

fn pretty_print_cycles(cycles: &[Vec<Ref>]) -> String {
  cycles
    .iter()
    .enumerate()
    .map(|(i, cycles)| format!("Cycle {}: {}", 1 + i, cycles.join(" -> ")))
    .collect::<Vec<String>>()
    .join("\n")
}

impl Graph {
  pub fn cycles(&self) -> Vec<Vec<Ref>> {
    let mut cycles = Vec::new();
    let mut stack = Stack::new();
    let mut visited = RefSet::new();

    for r#ref in self.0.keys() {
      if !visited.contains(r#ref) {
        self.find_cycles(r#ref, &mut visited, &mut stack, &mut cycles);
      }
    }

    cycles
  }

  fn find_cycles(
    &self,
    r#ref: &Ref,
    visited: &mut RefSet,
    stack: &mut Stack<Ref>,
    cycles: &mut Vec<Vec<Ref>>,

Review comment (suggestion):
We need to improve this message.
It should be something like `Mutual recursion cycle detected in compiled functions. Cycle 1: A -> B -> ... -> A. This program will expand infinitely in strict evaluation mode. Read <link to docs> for more information.`

Code after:
use hvmc::ast::{Book, Tree};
use indexmap::{IndexMap, IndexSet};
use std::fmt::Debug;

type Ref = String;
type Stack<T> = Vec<T>;
type RefSet = IndexSet<Ref>;

#[derive(Default)]
pub struct Graph(IndexMap<Ref, RefSet>);

pub fn check_cycles(book: &Book) -> Result<(), String> {
  let graph = Graph::from(book);
  let cycles = graph.cycles();

  if cycles.is_empty() {
    Ok(())
  } else {
    Err(format!(
      "{}\n{}\n{}\n{}",
      "Mutual recursion cycle detected in compiled functions:",
      pretty_print_cycles(&cycles),
      "This program will expand infinitely in strict evaluation mode.",
      "Read https://github.com/HigherOrderCO/hvm-lang/blob/main/docs/lazy-definitions.md for more information."
    ))
  }
}

fn pretty_print_cycles(cycles: &[Vec<Ref>]) -> String {
  cycles
    .iter()
    .enumerate()
    .map(|(i, cycle)| {
      let cycle_str = cycle.iter().chain(cycle.first()).cloned().collect::<Vec<_>>().join(" -> ");
      format!("Cycle {}: {}", 1 + i, cycle_str)
    })
    .collect::<Vec<String>>()
    .join("\n")
}

impl Graph {
  pub fn cycles(&self) -> Vec<Vec<Ref>> {
    let mut cycles = Vec::new();
    let mut stack = Stack::new();
    let mut visited = RefSet::new();

    for r#ref in self.0.keys() {
      if !visited.contains(r#ref) {
Repository: HigherOrderCO/Bend
File: src/hvmc_net/mutual_recursion.rs (Rust)

Code before:

pub fn check_cycles(book: &Book, diagnostics: &mut Diagnostics) -> Result<(), Diagnostics> {
  diagnostics.start_pass();

  let graph = Graph::from(book);
  let cycles = graph.cycles();

  if !cycles.is_empty() {
    let msg = format!(include_str!("mutual_recursion.message"), cycles = show_cycles(&cycles));
    diagnostics.add_book_warning(msg.as_str(), WarningType::MutualRecursionCycle);
  }

  diagnostics.fatal(())
}

fn show_cycles(cycles: &[Vec<Ref>]) -> String {
  let tail = &format!("\n{:ERR_INDENT_SIZE$}* ...", "");
  let tail = if cycles.len() > 5 { tail } else { "" };

  let mut cycles = cycles
    .iter()
    .take(5)
    .map(|cycle| {
      let cycle_str = cycle
        .iter()
        .filter(|nam| !nam.contains("$"))
        .chain(cycle.first())
        .cloned()
        .collect::<Vec<_>>()
        .join(" -> ");
      format!("{:ERR_INDENT_SIZE$}* {}", "", cycle_str)
    })
    .collect::<Vec<String>>()
    .join("\n");

  cycles.push_str(tail);

  cycles
}

impl Graph {
  pub fn cycles(&self) -> Vec<Vec<Ref>> {
    let mut cycles = Vec::new();
    let mut stack = Stack::new();
    let mut visited = RefSet::new();

    for r#ref in self.0.keys() {
      if !visited.contains(r#ref) {
        self.find_cycles(r#ref, &mut visited, &mut stack, &mut cycles);
      }
    }

Review comment (bug):
I think this will break if a non-generated definition gets merged by the merge pass.
We could have `Foo_$_Bar -> Baz -> Foo_$_Bar`. And we should display it either as `Foo -> Baz -> Foo` or both cycles separately (i think showing just one is ok).
This is also an extremely rare case, so we could leave it to another task if this is hard to implement correctly 

Code after:

pub fn check_cycles(book: &Book, diagnostics: &mut Diagnostics) -> Result<(), Diagnostics> {
  diagnostics.start_pass();

  let graph = Graph::from(book);
  let cycles = graph.cycles();

  if !cycles.is_empty() {
    let msg = format!(include_str!("mutual_recursion.message"), cycles = show_cycles(&cycles));
    diagnostics.add_book_warning(msg.as_str(), WarningType::MutualRecursionCycle);
  }

  diagnostics.fatal(())
}

fn show_cycles(cycles: &[Vec<Ref>]) -> String {
  let tail = &format!("\n{:ERR_INDENT_SIZE$}* ...", "");
  let tail = if cycles.len() > 5 { tail } else { "" };

  let mut cycles = cycles
    .iter()
    .take(5)
    .map(|cycle| {
      let cycle_str = cycle
        .iter()
        .filter(|nam| !nam.contains("$C"))
        .chain(cycle.first())
        .cloned()
        .collect::<Vec<_>>()
        .join(" -> ");
      format!("{:ERR_INDENT_SIZE$}* {}", "", cycle_str)
    })
    .collect::<Vec<String>>()
    .join("\n");

  cycles.push_str(tail);

  cycles
}

impl Graph {
  pub fn cycles(&self) -> Vec<Vec<Ref>> {
    let mut cycles = Vec::new();
    let mut stack = Stack::new();
    let mut visited = RefSet::new();

    for r#ref in self.0.keys() {
      if !visited.contains(r#ref) {
        self.find_cycles(r#ref, &mut visited, &mut stack, &mut cycles);
      }
    }
Repository: HigherOrderCO/Bend
File: src/term/transform/apply_bnd.rs (Rust)

Code before:
        if def_names.contains(&fun) {
          let mut fvs = nxt.free_vars();
          ask.binds().flatten().for_each(|bind| _ = fvs.remove(bind));
          let fvs = fvs.into_keys().collect::<Vec<_>>();
          let nxt =
            fvs.iter().fold(*nxt.clone(), |nxt, nam| Term::lam(Pattern::Var(Some(nam.clone())), nxt.clone()));
          let nxt = Term::lam(*ask.clone(), nxt);
          let term = Term::call(Term::Ref { nam: fun.clone() }, [*val.clone(), nxt]);
          *self = Term::call(term, fvs.into_iter().map(|nam| Term::Var { nam }));
        } else {
          return Err(MonadicBindError { expected_def: fun, type_name: typ.clone() });
        }
      }

      for children in self.children_mut() {
        children.apply_bnd(def_names)?;
      }

      Ok(())
    })
  }
}

fn make_fun_name(typ: &mut Name) -> Name {
  let name: String = [typ, "/", "bind"].into_iter().collect();
  Name::new(name)
}

impl fmt::Display for MonadicBindError {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    write!(f, "Could not find definition {} for type {}.", self.expected_def, self.type_name)
  }
}


Review comment (suggestion):
```suggestion
  Name::new(format!("{typ}/bind")
```

Code after:
        if def_names.contains(&fun) {
          let mut fvs = nxt.free_vars();
          ask.binds().flatten().for_each(|bind| _ = fvs.remove(bind));
          let fvs = fvs.into_keys().collect::<Vec<_>>();
          let nxt =
            fvs.iter().fold(*nxt.clone(), |nxt, nam| Term::lam(Pattern::Var(Some(nam.clone())), nxt.clone()));
          let nxt = Term::lam(*ask.clone(), nxt);
          let term = Term::call(Term::Ref { nam: fun.clone() }, [*val.clone(), nxt]);
          *self = Term::call(term, fvs.into_iter().map(|nam| Term::Var { nam }));
        } else {
          return Err(MonadicBindError { expected_def: fun, type_name: typ.clone() });
        }
      }

      for children in self.children_mut() {
        children.apply_bnd(def_names)?;
      }

      Ok(())
    })
  }
}

fn make_fun_name(typ: &mut Name) -> Name {
  Name::new(format!("{typ}/bind"))
}

impl fmt::Display for MonadicBindError {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    write!(f, "Could not find definition {} for type {}.", self.expected_def, self.type_name)
  }
}