Skip to main content

voltaic_driver/
annotate.rs

1use voltaic_ast::ClassMember;
2
3use voltaic_ast::FnDecl;
4
5use voltaic_ast::Item;
6
7use voltaic_ast::Program;
8
9use voltaic_ast::TypeExpr;
10
11use voltaic_lattice::Declarations;
12
13use voltaic_lattice::Mode;
14
15use voltaic_lattice::Obligation;
16
17use voltaic_lattice::Outcome;
18
19use voltaic_lattice::Store;
20
21use voltaic_lattice::Ty;
22
23use voltaic_ast::Span;
24
25use voltaic_diag::Code;
26
27use voltaic_diag::Diagnostic;
28
29use voltaic_diag::DiagnosticCode;
30
31use voltaic_diag::Severity;
32
33use std::path::Path;
34
35struct Filled {
36  name: String,
37  ty: Ty,
38  span: voltaic_ast::Span,
39  modes: Vec<(String, Mode)>,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct Unresolved {
44  pub name: String,
45  pub shown: String,
46  pub span: voltaic_ast::Span,
47}
48
49#[derive(Debug, Clone)]
50pub struct Analysis {
51  pub open: Vec<Unresolved>,
52  pub methods: Vec<Span>,
53}
54
55pub fn annotate(program: &mut Program) -> Vec<Unresolved> {
56  analyse_at(program, Path::new("")).open
57}
58
59pub fn analyse(program: &mut Program) -> Analysis {
60  analyse_at(program, Path::new(""))
61}
62
63pub fn analyse_at(program: &mut Program, from: &Path) -> Analysis {
64  let mut found = voltaic_lattice::walk(program);
65  let published = crate::resolve::upstream(program, from);
66  for m in published.members.iter() {
67    found.known.members.push(m.clone());
68  }
69  for t in published.types.iter() {
70    found.known.types.push(t.clone());
71  }
72  let outcome = voltaic_lattice::solve_with(&found.store, &found.known);
73  let methods = method_spans(&found.store, &outcome, &found.known);
74  let mut filled = Vec::new();
75  for definition in found.definitions.iter() {
76    if definition.written.is_none() {
77      filled.push(Filled {
78        name: definition.name.clone(),
79        ty: definition.judged(&outcome),
80        span: definition.span,
81        modes: definition.modes.clone(),
82      });
83    }
84  }
85  if filled.is_empty() {
86    return Analysis {
87      open: Vec::new(),
88      methods,
89    };
90  }
91
92  let mut open = Vec::new();
93  for item in program.items.iter_mut() {
94    for f in methods_of(item) {
95      if f.sig.ret.is_none() {
96        if let Some(whole) = written_for(&f.name.text, f.span, filled.as_slice()) {
97          let inferred_modes = modes_for(&f.name.text, f.span, filled.as_slice());
98          if !fill(f, &whole, &inferred_modes) {
99            open.push(Unresolved {
100              name: f.name.text.clone(),
101              shown: whole.to_string(),
102              span: f.span,
103            });
104          }
105        } else {
106          open.push(Unresolved {
107            name: f.name.text.clone(),
108            shown: "?".to_string(),
109            span: f.span,
110          });
111        }
112      }
113    }
114  }
115  Analysis { open, methods }
116}
117
118fn method_spans(store: &Store, outcome: &Outcome, known: &Declarations) -> Vec<Span> {
119  let mut found = Vec::new();
120  for c in store.all() {
121    let at = c.provenance.span;
122    if let Obligation::Member { name, .. } = &c.obligation
123      && let Some(owner) = voltaic_lattice::receiver_owner(store, outcome, at)
124      && known.is_method(&owner, name)
125    {
126      found.push(at);
127    }
128  }
129  found
130}
131
132fn methods_of(item: &mut Item) -> Vec<&mut FnDecl> {
133  let mut found = Vec::new();
134  match item {
135    Item::Fn(f) => found.push(f),
136    Item::Instance(block) => {
137      for member in block.members.iter_mut() {
138        if let ClassMember::Method(f) = member {
139          found.push(f);
140        }
141      }
142    }
143    _ => (),
144  };
145  found
146}
147
148fn written_for(name: &str, span: voltaic_ast::Span, filled: &[Filled]) -> Option<Ty> {
149  for row in filled.iter() {
150    if row.name == name && row.span == span {
151      return Some(row.ty.clone());
152    }
153  }
154  None
155}
156
157fn modes_for(name: &str, span: voltaic_ast::Span, filled: &[Filled]) -> Vec<(String, Mode)> {
158  for row in filled.iter() {
159    if row.name == name && row.span == span {
160      return row.modes.clone();
161    }
162  }
163  Vec::new()
164}
165
166fn receiver_mode(modes: &[(String, Mode)]) -> Mode {
167  for row in modes.iter() {
168    if row.0 == "self" {
169      return row.1;
170    }
171  }
172  Mode::Owned
173}
174
175fn fill(f: &mut FnDecl, whole: &Ty, modes: &[(String, Mode)]) -> bool {
176  let parts = spine_of(whole, f.sig.params.len());
177  if let Some(rows) = parts {
178    for pair in f.sig.params.iter_mut().zip(rows.0.iter()) {
179      if let Some(_written) = Mode::of_receiver(&pair.0.name.text) {
180        let prefix = receiver_mode(modes).emits();
181        pair.0.name.text = format!("{}self", prefix);
182      } else {
183        pair.0.ty = Some(TypeExpr {
184          text: pair.1.clone(),
185          span: pair.0.span,
186        });
187      }
188    }
189    f.sig.ret = Some(TypeExpr {
190      text: rows.1.clone(),
191      span: f.sig.span,
192    });
193    return true;
194  }
195  false
196}
197
198fn spine_of(whole: &Ty, arity: usize) -> Option<(Vec<String>, String)> {
199  let mut cursor = whole.clone();
200  let mut params = Vec::new();
201  let mut taken = 0;
202  while taken < arity {
203    let next = arrow_parts(&cursor)?;
204    params.push(voltaic_lattice::write_type(&next.0)?);
205    cursor = next.1;
206    taken += 1;
207  }
208  let ret = voltaic_lattice::write_type(&cursor)?;
209  Some((params, ret))
210}
211
212fn arrow_parts(held: &Ty) -> Option<(Ty, Ty)> {
213  match held {
214    Ty::Con { name, args, .. } => {
215      if name == voltaic_lattice::ARROW && args.len() == 2 {
216        return Some((args[0].clone(), args[1].clone()));
217      }
218      None
219    }
220    _ => None,
221  }
222}
223
224pub fn refusals(path: &Path, source: &str, open: &[Unresolved]) -> Vec<Diagnostic> {
225  let shown = path.display().to_string();
226  let mut diagnostics = Vec::new();
227  for one in open.iter() {
228    diagnostics.push(Diagnostic {
229            code: DiagnosticCode::from(Code::SignatureNotInferred),
230            severity: Severity::Error,
231            message: format!("cannot infer a signature for `{}`", one.name),
232            span: one.span,
233            related: Vec::new(),
234            help: Some(format!(
235                "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.",
236                one.shown,
237                one.name
238            )),
239            path: shown.clone(),
240            source: source.to_string(),
241        });
242  }
243  diagnostics
244}
245
246#[cfg(test)]
247mod __vlt_tests {
248  use super::*;
249
250  #[test]
251  fn the_solver_resolves_a_method_and_a_field_through_an_annotated_receiver() {
252    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";
253    let program = voltaic_parser::parse_source(source).expect("should parse");
254    let found = voltaic_lattice::walk(&program);
255    let outcome = voltaic_lattice::solve_with(&found.store, &found.known);
256    assert!(found.known.is_method("Cursor", "depth"));
257    assert!(found.known.is_field("Cursor", "pos"));
258    let mut named: Vec<String> = Vec::new();
259    for c in found.store.all() {
260      if let Obligation::Member { name, .. } = &c.obligation
261        && let Some(owner) =
262          voltaic_lattice::receiver_owner(&found.store, &outcome, c.provenance.span)
263      {
264        named.push(format!("{}.{}", owner, name));
265      }
266    }
267    assert!(named.contains(&"Cursor.depth".to_string()));
268    assert!(named.contains(&"Cursor.pos".to_string()));
269  }
270
271  #[test]
272  fn a_self_receiver_is_not_resolved_so_a_method_call_on_self_stays_a_field_read() {
273    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";
274    let mut program = voltaic_parser::parse_source(source).expect("should parse");
275    let analysis = analyse(&mut program);
276    assert!(analysis.methods.is_empty());
277  }
278
279  #[test]
280  fn analyse_reports_the_method_call_s_span_which_is_what_the_emitter_asks_about() {
281    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";
282    let mut program = voltaic_parser::parse_source(source).expect("should parse");
283    let analysis = analyse(&mut program);
284    assert!(analysis.open.is_empty());
285    assert_eq!(analysis.methods.len(), 1);
286  }
287
288  #[test]
289  fn a_program_with_no_methods_reports_no_spans_so_the_default_stays_field() {
290    let source =
291      "data Cursor = Cursor { pos : usize }\n\nreach : &Cursor -> usize\nreach c = c.pos\n";
292    let mut program = voltaic_parser::parse_source(source).expect("should parse");
293    let analysis = analyse(&mut program);
294    assert!(analysis.methods.is_empty());
295  }
296
297  #[test]
298  fn a_program_whose_definitions_are_all_signed_is_left_alone() {
299    let mut program =
300      voltaic_parser::parse_source("twice : u64 -> u64\ntwice n = n + n\n").expect("should parse");
301    assert!(annotate(&mut program).is_empty());
302    if let Item::Fn(f) = &program.items[0] {
303      assert_eq!(f.sig.ret.as_ref().expect("a written return").text, "u64");
304    }
305  }
306
307  #[test]
308  fn an_unsigned_definition_gains_the_inferred_types() {
309    let mut program = voltaic_parser::parse_source(
310      "scaled : u64 -> u64\nscaled n = n * 2\n\ndoubled n = scaled n\n",
311    )
312    .expect("should parse");
313    assert!(annotate(&mut program).is_empty());
314    if let Item::Fn(f) = &program.items[1] {
315      assert_eq!(f.sig.ret.as_ref().expect("a return").text, "u64");
316      assert_eq!(f.sig.params[0].ty.as_ref().expect("a param").text, "u64");
317    }
318  }
319
320  #[test]
321  fn an_unsigned_nullary_definition_gains_only_a_return_type() {
322    let mut program = voltaic_parser::parse_source("ready =\n  true\n").expect("should parse");
323    assert!(annotate(&mut program).is_empty());
324    if let Item::Fn(f) = &program.items[0] {
325      assert!(f.sig.params.is_empty());
326      assert_eq!(f.sig.ret.as_ref().expect("a return").text, "bool");
327    }
328  }
329
330  #[test]
331  fn a_definition_inference_cannot_reach_is_refused_not_guessed() {
332    let mut program =
333      voltaic_parser::parse_source("data Point = Point { x : f64 }\nacross p = p.x\n")
334        .expect("should parse");
335    let open = annotate(&mut program);
336    assert_eq!(open.len(), 1);
337    assert_eq!(open[0].name, "across");
338  }
339
340  #[test]
341  fn a_refusal_is_an_error_naming_the_type_it_got_to_and_the_command_that_explains_it() {
342    let mut program =
343      voltaic_parser::parse_source("data Point = Point { x : f64 }\nacross p = p.x\n")
344        .expect("should parse");
345    let open = annotate(&mut program);
346    let shown = refusals(Path::new("probe.vlt"), "", open.as_slice());
347    assert_eq!(shown.len(), 1);
348    assert_eq!(
349      shown[0].severity,
350      Severity::Error,
351      "an unemittable program must not build"
352    );
353    let help = shown[0].help.clone().expect("a help line");
354    assert!(help.contains("sig --why across"), "{}", help);
355  }
356
357  #[test]
358  fn a_chain_of_unsigned_definitions_resolves_through_the_globals_fixed_point() {
359    let mut program = voltaic_parser::parse_source(
360      "scaled : u64 -> u64\nscaled n = n * 2\n\ndoubled n = scaled n\n\nquadrupled n = doubled n\n",
361    )
362    .expect("should parse");
363    assert!(annotate(&mut program).is_empty());
364    if let Item::Fn(f) = &program.items[2] {
365      assert_eq!(f.sig.ret.as_ref().expect("a return").text, "u64");
366    }
367  }
368
369  #[test]
370  fn annotating_twice_is_the_same_as_annotating_once() {
371    let source = "scaled : u64 -> u64\nscaled n = n * 2\n\ndoubled n = scaled n\n";
372    let mut once = voltaic_parser::parse_source(source).expect("should parse");
373    let _ = annotate(&mut once);
374    let mut twice = voltaic_parser::parse_source(source).expect("should parse");
375    let _ = annotate(&mut twice);
376    let _ = annotate(&mut twice);
377    assert_eq!(
378      once, twice,
379      "a second pass sees signed definitions and must not re-decide"
380    );
381  }
382}