voltaic-driver 0.2.0

Incremental compilation driver for the Voltaic compiler.
Documentation
use voltaic_ast::ClassMember;

use voltaic_ast::FnDecl;

use voltaic_ast::Item;

use voltaic_ast::Program;

use voltaic_ast::TypeExpr;

use voltaic_lattice::Declarations;

use voltaic_lattice::Mode;

use voltaic_lattice::Obligation;

use voltaic_lattice::Outcome;

use voltaic_lattice::Store;

use voltaic_lattice::Ty;

use voltaic_ast::Span;

use voltaic_diag::Code;

use voltaic_diag::Diagnostic;

use voltaic_diag::DiagnosticCode;

use voltaic_diag::Severity;

use std::path::Path;

struct Filled {
  name: String,
  ty: Ty,
  span: voltaic_ast::Span,
  modes: Vec<(String, Mode)>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Unresolved {
  pub name: String,
  pub shown: String,
  pub span: voltaic_ast::Span,
}

#[derive(Debug, Clone)]
pub struct Analysis {
  pub open: Vec<Unresolved>,
  pub methods: Vec<Span>,
}

pub fn annotate(program: &mut Program) -> Vec<Unresolved> {
  analyse_at(program, Path::new("")).open
}

pub fn analyse(program: &mut Program) -> Analysis {
  analyse_at(program, Path::new(""))
}

pub fn analyse_at(program: &mut Program, from: &Path) -> Analysis {
  let mut found = voltaic_lattice::walk(program);
  let published = crate::resolve::upstream(program, from);
  for m in published.members.iter() {
    found.known.members.push(m.clone());
  }
  for t in published.types.iter() {
    found.known.types.push(t.clone());
  }
  let outcome = voltaic_lattice::solve_with(&found.store, &found.known);
  let methods = method_spans(&found.store, &outcome, &found.known);
  let mut filled = Vec::new();
  for definition in found.definitions.iter() {
    if definition.written.is_none() {
      filled.push(Filled {
        name: definition.name.clone(),
        ty: definition.judged(&outcome),
        span: definition.span,
        modes: definition.modes.clone(),
      });
    }
  }
  if filled.is_empty() {
    return Analysis {
      open: Vec::new(),
      methods,
    };
  }

  let mut open = Vec::new();
  for item in program.items.iter_mut() {
    for f in methods_of(item) {
      if f.sig.ret.is_none() {
        if let Some(whole) = written_for(&f.name.text, f.span, filled.as_slice()) {
          let inferred_modes = modes_for(&f.name.text, f.span, filled.as_slice());
          if !fill(f, &whole, &inferred_modes) {
            open.push(Unresolved {
              name: f.name.text.clone(),
              shown: whole.to_string(),
              span: f.span,
            });
          }
        } else {
          open.push(Unresolved {
            name: f.name.text.clone(),
            shown: "?".to_string(),
            span: f.span,
          });
        }
      }
    }
  }
  Analysis { open, methods }
}

fn method_spans(store: &Store, outcome: &Outcome, known: &Declarations) -> Vec<Span> {
  let mut found = Vec::new();
  for c in store.all() {
    let at = c.provenance.span;
    if let Obligation::Member { name, .. } = &c.obligation
      && let Some(owner) = voltaic_lattice::receiver_owner(store, outcome, at)
      && known.is_method(&owner, name)
    {
      found.push(at);
    }
  }
  found
}

fn methods_of(item: &mut Item) -> Vec<&mut FnDecl> {
  let mut found = Vec::new();
  match item {
    Item::Fn(f) => found.push(f),
    Item::Instance(block) => {
      for member in block.members.iter_mut() {
        if let ClassMember::Method(f) = member {
          found.push(f);
        }
      }
    }
    _ => (),
  };
  found
}

fn written_for(name: &str, span: voltaic_ast::Span, filled: &[Filled]) -> Option<Ty> {
  for row in filled.iter() {
    if row.name == name && row.span == span {
      return Some(row.ty.clone());
    }
  }
  None
}

fn modes_for(name: &str, span: voltaic_ast::Span, filled: &[Filled]) -> Vec<(String, Mode)> {
  for row in filled.iter() {
    if row.name == name && row.span == span {
      return row.modes.clone();
    }
  }
  Vec::new()
}

fn receiver_mode(modes: &[(String, Mode)]) -> Mode {
  for row in modes.iter() {
    if row.0 == "self" {
      return row.1;
    }
  }
  Mode::Owned
}

fn fill(f: &mut FnDecl, whole: &Ty, modes: &[(String, Mode)]) -> bool {
  let parts = spine_of(whole, f.sig.params.len());
  if let Some(rows) = parts {
    for pair in f.sig.params.iter_mut().zip(rows.0.iter()) {
      if let Some(_written) = Mode::of_receiver(&pair.0.name.text) {
        let prefix = receiver_mode(modes).emits();
        pair.0.name.text = format!("{}self", prefix);
      } else {
        pair.0.ty = Some(TypeExpr {
          text: pair.1.clone(),
          span: pair.0.span,
        });
      }
    }
    f.sig.ret = Some(TypeExpr {
      text: rows.1.clone(),
      span: f.sig.span,
    });
    return true;
  }
  false
}

fn spine_of(whole: &Ty, arity: usize) -> Option<(Vec<String>, String)> {
  let mut cursor = whole.clone();
  let mut params = Vec::new();
  let mut taken = 0;
  while taken < arity {
    let next = arrow_parts(&cursor)?;
    params.push(voltaic_lattice::write_type(&next.0)?);
    cursor = next.1;
    taken += 1;
  }
  let ret = voltaic_lattice::write_type(&cursor)?;
  Some((params, ret))
}

fn arrow_parts(held: &Ty) -> Option<(Ty, Ty)> {
  match held {
    Ty::Con { name, args, .. } => {
      if name == voltaic_lattice::ARROW && args.len() == 2 {
        return Some((args[0].clone(), args[1].clone()));
      }
      None
    }
    _ => None,
  }
}

pub fn refusals(path: &Path, source: &str, open: &[Unresolved]) -> Vec<Diagnostic> {
  let shown = path.display().to_string();
  let mut diagnostics = Vec::new();
  for one in open.iter() {
    diagnostics.push(Diagnostic {
            code: DiagnosticCode::from(Code::SignatureNotInferred),
            severity: Severity::Error,
            message: format!("cannot infer a signature for `{}`", one.name),
            span: one.span,
            related: Vec::new(),
            help: Some(format!(
                "Lattice reached `{}`, which has no Rust spelling. Write the signature, or annotate a parameter so inference can reach one. `voltaic sig --why {}` shows the chain.",
                one.shown,
                one.name
            )),
            path: shown.clone(),
            source: source.to_string(),
        });
  }
  diagnostics
}

#[cfg(test)]
mod __vlt_tests {
  use super::*;

  #[test]
  fn the_solver_resolves_a_method_and_a_field_through_an_annotated_receiver() {
    let source = "data Cursor = Cursor { pos : usize }\n\ninstance Cursor\n  export depth : &self -> usize\n  depth self = 1\n\nreach : &Cursor -> usize\nreach c = c.depth\n\nread : &Cursor -> usize\nread c = c.pos\n";
    let program = voltaic_parser::parse_source(source).expect("should parse");
    let found = voltaic_lattice::walk(&program);
    let outcome = voltaic_lattice::solve_with(&found.store, &found.known);
    assert!(found.known.is_method("Cursor", "depth"));
    assert!(found.known.is_field("Cursor", "pos"));
    let mut named: Vec<String> = Vec::new();
    for c in found.store.all() {
      if let Obligation::Member { name, .. } = &c.obligation
        && let Some(owner) =
          voltaic_lattice::receiver_owner(&found.store, &outcome, c.provenance.span)
      {
        named.push(format!("{}.{}", owner, name));
      }
    }
    assert!(named.contains(&"Cursor.depth".to_string()));
    assert!(named.contains(&"Cursor.pos".to_string()));
  }

  #[test]
  fn a_self_receiver_is_not_resolved_so_a_method_call_on_self_stays_a_field_read() {
    let source = "data Cursor = Cursor { pos : usize }\n\ninstance Cursor\n  export depth : &self -> usize\n  depth self = self.pos\n\n  export twice : &self -> usize\n  twice self = self.depth\n";
    let mut program = voltaic_parser::parse_source(source).expect("should parse");
    let analysis = analyse(&mut program);
    assert!(analysis.methods.is_empty());
  }

  #[test]
  fn analyse_reports_the_method_call_s_span_which_is_what_the_emitter_asks_about() {
    let source = "data Cursor = Cursor { pos : usize }\n\ninstance Cursor\n  export depth : &self -> usize\n  depth self = self.pos\n\nreach : &Cursor -> usize\nreach c = c.depth\n";
    let mut program = voltaic_parser::parse_source(source).expect("should parse");
    let analysis = analyse(&mut program);
    assert!(analysis.open.is_empty());
    assert_eq!(analysis.methods.len(), 1);
  }

  #[test]
  fn a_program_with_no_methods_reports_no_spans_so_the_default_stays_field() {
    let source =
      "data Cursor = Cursor { pos : usize }\n\nreach : &Cursor -> usize\nreach c = c.pos\n";
    let mut program = voltaic_parser::parse_source(source).expect("should parse");
    let analysis = analyse(&mut program);
    assert!(analysis.methods.is_empty());
  }

  #[test]
  fn a_program_whose_definitions_are_all_signed_is_left_alone() {
    let mut program =
      voltaic_parser::parse_source("twice : u64 -> u64\ntwice n = n + n\n").expect("should parse");
    assert!(annotate(&mut program).is_empty());
    if let Item::Fn(f) = &program.items[0] {
      assert_eq!(f.sig.ret.as_ref().expect("a written return").text, "u64");
    }
  }

  #[test]
  fn an_unsigned_definition_gains_the_inferred_types() {
    let mut program = voltaic_parser::parse_source(
      "scaled : u64 -> u64\nscaled n = n * 2\n\ndoubled n = scaled n\n",
    )
    .expect("should parse");
    assert!(annotate(&mut program).is_empty());
    if let Item::Fn(f) = &program.items[1] {
      assert_eq!(f.sig.ret.as_ref().expect("a return").text, "u64");
      assert_eq!(f.sig.params[0].ty.as_ref().expect("a param").text, "u64");
    }
  }

  #[test]
  fn an_unsigned_nullary_definition_gains_only_a_return_type() {
    let mut program = voltaic_parser::parse_source("ready =\n  true\n").expect("should parse");
    assert!(annotate(&mut program).is_empty());
    if let Item::Fn(f) = &program.items[0] {
      assert!(f.sig.params.is_empty());
      assert_eq!(f.sig.ret.as_ref().expect("a return").text, "bool");
    }
  }

  #[test]
  fn a_definition_inference_cannot_reach_is_refused_not_guessed() {
    let mut program =
      voltaic_parser::parse_source("data Point = Point { x : f64 }\nacross p = p.x\n")
        .expect("should parse");
    let open = annotate(&mut program);
    assert_eq!(open.len(), 1);
    assert_eq!(open[0].name, "across");
  }

  #[test]
  fn a_refusal_is_an_error_naming_the_type_it_got_to_and_the_command_that_explains_it() {
    let mut program =
      voltaic_parser::parse_source("data Point = Point { x : f64 }\nacross p = p.x\n")
        .expect("should parse");
    let open = annotate(&mut program);
    let shown = refusals(Path::new("probe.vlt"), "", open.as_slice());
    assert_eq!(shown.len(), 1);
    assert_eq!(
      shown[0].severity,
      Severity::Error,
      "an unemittable program must not build"
    );
    let help = shown[0].help.clone().expect("a help line");
    assert!(help.contains("sig --why across"), "{}", help);
  }

  #[test]
  fn a_chain_of_unsigned_definitions_resolves_through_the_globals_fixed_point() {
    let mut program = voltaic_parser::parse_source(
      "scaled : u64 -> u64\nscaled n = n * 2\n\ndoubled n = scaled n\n\nquadrupled n = doubled n\n",
    )
    .expect("should parse");
    assert!(annotate(&mut program).is_empty());
    if let Item::Fn(f) = &program.items[2] {
      assert_eq!(f.sig.ret.as_ref().expect("a return").text, "u64");
    }
  }

  #[test]
  fn annotating_twice_is_the_same_as_annotating_once() {
    let source = "scaled : u64 -> u64\nscaled n = n * 2\n\ndoubled n = scaled n\n";
    let mut once = voltaic_parser::parse_source(source).expect("should parse");
    let _ = annotate(&mut once);
    let mut twice = voltaic_parser::parse_source(source).expect("should parse");
    let _ = annotate(&mut twice);
    let _ = annotate(&mut twice);
    assert_eq!(
      once, twice,
      "a second pass sees signed definitions and must not re-decide"
    );
  }
}