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"
);
}
}