Repository: HigherOrderCO/Bend
File: src/term/net_to_term.rs (Rust)

Code before:
    for child in term.children_mut() {
      switch_lam_to_chn(child, unscoped_vars);
    }
  }

  switch_lam_to_chn(&mut term, &reader.unscoped_vars);

  reader.report_errors(diagnostics);
  term
}

// BTreeSet for consistent readback of dups
type Scope = BTreeSet<NodeId>;

pub struct Reader<'a> {
  pub book: &'a Book,
  pub namegen: NameGen,
  net: &'a INet,
  labels: &'a Labels,
  dup_paths: Option<HashMap<u32, Vec<SlotId>>>,
  /// Store for floating/unscoped terms, like dups and let tups.
  scope: Scope,
  seen_fans: Scope,
  seen: HashSet<Port>,
  errors: Vec<ReadbackError>,
  vars: HashSet<Name>,
  unscoped_vars: HashSet<Name>,
}

impl Reader<'_> {
  fn read_term(&mut self, next: Port) -> Term {
    maybe_grow(|| {
      if self.dup_paths.is_none() && !self.seen.insert(next) {
        self.error(ReadbackError::Cyclic);
        return Term::Var { nam: Name::new("...") };
      }

      let node = next.node();

      let term = match &self.net.node(node).kind {
        Era => {
          // Only the main port actually exists in an ERA, the aux ports are just an artifact of this representation.
          debug_assert!(next.slot() == 0);
          Term::Era
        }
        // If we're visiting a con node...
        Con { lab } => match next.slot() {
          // If we're visiting a port 0, then it is a lambda.
          0 => {
            let nam = self.namegen.decl_name(self.net, Port(node, 1));
            if let Some(nam) = &nam {

Review comment (performance):
I think it would be better to move the gathering of unscopeds to a separate funcion to not increase the complexity of the Reader, which is already quite complex.

We could in addition make the reader always return unscoped lambdas only, to make it's result correct even before checking the scope of variables

Code after:
}

// BTreeSet for consistent readback of dups
type Scope = BTreeSet<NodeId>;

pub struct Reader<'a> {
  pub book: &'a Book,
  pub namegen: NameGen,
  net: &'a INet,
  labels: &'a Labels,
  dup_paths: Option<HashMap<u32, Vec<SlotId>>>,
  /// Store for floating/unscoped terms, like dups and let tups.
  scope: Scope,
  seen_fans: Scope,
  seen: HashSet<Port>,
  errors: Vec<ReadbackError>,
}

impl Reader<'_> {
  fn read_term(&mut self, next: Port) -> Term {
    maybe_grow(|| {
      if self.dup_paths.is_none() && !self.seen.insert(next) {
        self.error(ReadbackError::Cyclic);
        return Term::Var { nam: Name::new("...") };
      }

      let node = next.node();

      let term = match &self.net.node(node).kind {
        Era => {
          // Only the main port actually exists in an ERA, the aux ports are just an artifact of this representation.
          debug_assert!(next.slot() == 0);
          Term::Era
        }
        // If we're visiting a con node...
        Con { lab } => match next.slot() {
          // If we're visiting a port 0, then it is a lambda.
          0 => {
            let nam = self.namegen.decl_name(self.net, Port(node, 1));
            let bod = self.read_term(self.net.enter_port(Port(node, 2)));
            Term::Lam { tag: self.labels.con.to_tag(*lab), nam, bod: Box::new(bod) }
          }
          // If we're visiting a port 1, then it is a variable.
          1 => Term::Var { nam: self.namegen.var_name(next) },
          // If we're visiting a port 2, then it is an application.
          2 => {
            let fun = self.read_term(self.net.enter_port(Port(node, 0)));
            let arg = self.read_term(self.net.enter_port(Port(node, 1)));
            Term::App { tag: self.labels.con.to_tag(*lab), fun: Box::new(fun), arg: Box::new(arg) }
          }
          _ => unreachable!(),