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

Code before:
impl Book {
  /// Checks that there are no unbound variables in all definitions.
  pub fn check_unbound_vars(&self) -> Result<(), String> {
    for def in self.defs.values() {
      def.assert_no_pattern_matching_rules();
      def.rules[0]
        .body
        .check_unbound_vars()
        .map_err(|e| format!("In definition '{}': {}", self.def_names.name(&def.def_id).unwrap(), e))?;
    }
    Ok(())
  }
}

impl Term {
  /// Checks that all variables are bound.
  /// Precondition: References have been resolved.
  pub fn check_unbound_vars(&self) -> Result<(), String> {
    let mut globals = HashMap::new();
    check_uses(self, &mut HashMap::new(), &mut globals)?;

    // Check global vars
    for (nam, (declared, used)) in globals.into_iter() {
      match (used, declared) {
        (1, 1) => {}
        (0, _) => return Err(format!("Missing unscoped variable use for 'λ${nam}'")),
        (_, 0) => return Err(format!("Unbound unscoped variable '${nam}'")),
        (_, _) => return Err(format!("Unscoped variable '${nam}' used more than once")),
      }
    }
    Ok(())
  }
}

/// Scope has the number of times a name was declared in the current scope
/// Globals has how many times a global var name was declared and used.
pub fn check_uses<'a>(
  term: &'a Term,
  scope: &mut HashMap<&'a Name, Val>,
  globals: &mut HashMap<&'a Name, (usize, usize)>,
) -> Result<(), String> {
  // TODO: Don't stop at the first error
  match term {
    Term::Lam { nam, bod, .. } => {
      push_scope(nam.as_ref(), scope);
      check_uses(bod, scope, globals)?;
      pop_scope(nam.as_ref(), scope);
    }
    Term::Var { nam } => {
      if !scope.contains_key(nam) {
        return Err(format!("Unbound variable '{nam}'"));

Review comment (suggestion):
```suggestion
        (0, _) => return Err(format!("Unscoped variable from lambda 'λ${nam}' is never used")),
```

Code after:
impl Book {
  /// Checks that there are no unbound variables in all definitions.
  pub fn check_unbound_vars(&self) -> Result<(), String> {
    for def in self.defs.values() {
      def.assert_no_pattern_matching_rules();
      def.rules[0]
        .body
        .check_unbound_vars()
        .map_err(|e| format!("In definition '{}': {}", self.def_names.name(&def.def_id).unwrap(), e))?;
    }
    Ok(())
  }
}

impl Term {
  /// Checks that all variables are bound.
  /// Precondition: References have been resolved.
  pub fn check_unbound_vars(&self) -> Result<(), String> {
    let mut globals = HashMap::new();
    check_uses(self, &mut HashMap::new(), &mut globals)?;

    // Check global vars
    for (nam, (declared, used)) in globals.into_iter() {
      match (declared, used) {
        (1, 1) => {}
        (0, _) => return Err(format!("Unbound unscoped variable '${nam}'")),
        (_, 0) => return Err(format!("Unscoped variable from lambda 'λ${nam}' is never used")),
        (1, _) => return Err(format!("Unscoped variable '${nam}' used more than once")),
        (_, 1) => return Err(format!("Unscoped lambda 'λ${nam}' declared more than once")),
        (_, _) => return Err(format!("Unscoped lambda 'λ${nam}' and unscoped variable '${nam}' used more than once")),
      }
    }
    Ok(())
  }
}

/// Scope has the number of times a name was declared in the current scope
/// Globals has how many times a global var name was declared and used.
pub fn check_uses<'a>(
  term: &'a Term,
  scope: &mut HashMap<&'a Name, Val>,
  globals: &mut HashMap<&'a Name, (usize, usize)>,
) -> Result<(), String> {
  // TODO: Don't stop at the first error
  match term {
    Term::Lam { nam, bod, .. } => {
      push_scope(nam.as_ref(), scope);
      check_uses(bod, scope, globals)?;
      pop_scope(nam.as_ref(), scope);
    }
    Term::Var { nam } => {