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}