add a bunch of hints

This commit is contained in:
Folkert 2020-04-08 21:49:05 +02:00
parent 5e226ee5f4
commit 85e2cf4465
4 changed files with 267 additions and 19 deletions

View file

@ -1,4 +1,5 @@
use crate::report::ReportText::{BinOp, Concat, Module, Region, Value};
use bumpalo::Bump;
use roc_module::ident::TagName;
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
@ -527,11 +528,12 @@ impl ReportText {
src_lines: &[&str],
interns: &Interns,
) {
let arena = Bump::new();
let alloc = BoxAllocator;
let err_msg = "<buffer is not a utf-8 encoded string>";
self.pretty::<_>(&alloc, subs, home, src_lines, interns)
self.pretty::<_>(&alloc, &arena, subs, home, src_lines, interns)
.1
.render_raw(70, &mut CiWrite::new(buf))
.expect(err_msg);
@ -547,11 +549,12 @@ impl ReportText {
interns: &Interns,
palette: &Palette,
) {
let arena = Bump::new();
let alloc = BoxAllocator;
let err_msg = "<buffer is not a utf-8 encoded string>";
self.pretty::<_>(&alloc, subs, home, src_lines, interns)
self.pretty::<_>(&alloc, &arena, subs, home, src_lines, interns)
.1
.render_raw(70, &mut ColorWrite::new(palette, buf))
.expect(err_msg);
@ -562,6 +565,7 @@ impl ReportText {
pub fn pretty<'b, D>(
self,
alloc: &'b D,
arena: &'b Bump,
subs: &mut Subs,
home: ModuleId,
src_lines: &'b [&'b str],
@ -656,14 +660,14 @@ impl ReportText {
.append(alloc.hardline())
.append(
error_type
.pretty(alloc, subs, home, src_lines, interns)
.pretty(alloc, arena, subs, home, src_lines, interns)
.indent(4)
.annotate(Annotation::TypeBlock),
)
.append(alloc.hardline()),
Indent(n, nested) => {
let rest = nested.pretty(alloc, subs, home, src_lines, interns);
let rest = nested.pretty(alloc, arena, subs, home, src_lines, interns);
alloc.nil().append(rest).indent(n)
}
Docs(_) => {
@ -672,22 +676,22 @@ impl ReportText {
Concat(report_texts) => alloc.concat(
report_texts
.into_iter()
.map(|rep| rep.pretty(alloc, subs, home, src_lines, interns)),
.map(|rep| rep.pretty(alloc, arena, subs, home, src_lines, interns)),
),
Stack(report_texts) => alloc
.intersperse(
report_texts
.into_iter()
.map(|rep| (rep.pretty(alloc, subs, home, src_lines, interns))),
.map(|rep| (rep.pretty(alloc, arena, subs, home, src_lines, interns))),
alloc.hardline(),
)
.append(alloc.hardline()),
Intersperse { separator, items } => alloc.intersperse(
items
.into_iter()
.map(|rep| (rep.pretty(alloc, subs, home, src_lines, interns)))
.map(|rep| (rep.pretty(alloc, arena, subs, home, src_lines, interns)))
.collect::<Vec<_>>(),
separator.pretty(alloc, subs, home, src_lines, interns),
separator.pretty(alloc, arena, subs, home, src_lines, interns),
),
BinOp(bin_op) => alloc.text(bin_op.to_string()).annotate(Annotation::BinOp),
Region(region) => {
@ -780,12 +784,15 @@ impl ReportText {
Name(ident) => alloc
.text(format!("{}", ident.as_inline_str()))
.annotate(Annotation::Symbol),
TypeProblem(problem) => Self::type_problem_to_pretty(alloc, home, interns, problem),
TypeProblem(problem) => {
Self::type_problem_to_pretty(alloc, arena, home, interns, problem)
}
}
}
fn type_problem_to_pretty<'b, D>(
alloc: &'b D,
arena: &'b Bump,
home: ModuleId,
interns: &Interns,
problem: crate::type_error::Problem,
@ -827,6 +834,26 @@ impl ReportText {
}
}
}
FieldsMissing(missing) => match missing.split_last() {
None => alloc.nil(),
Some((f1, [])) => Self::hint(alloc)
.append(alloc.reflow("Looks like the "))
.append(f1.as_str().to_owned())
.append(alloc.reflow(" field is missing.")),
Some((last, init)) => {
let separator = alloc.reflow(", ");
Self::hint(alloc)
.append(alloc.reflow("Looks like the "))
.append(
alloc
.intersperse(init.iter().map(|v| v.as_str().to_owned()), separator),
)
.append(alloc.reflow(" and "))
.append(alloc.text(last.as_str().to_owned()))
.append(alloc.reflow(" fields are missing."))
}
},
TagTypo(typo, possibilities_tn) => {
let possibilities = possibilities_tn
.into_iter()
@ -860,6 +887,59 @@ impl ReportText {
}
}
}
ArityMismatch(found, expected) => {
let line = if found < expected {
format!(
"It looks like it takes too few arguments. I was expecting {} more.",
expected - found
)
} else {
format!(
"It looks like it takes too many arguments. I'm seeing {} extra.",
found - expected
)
};
Self::hint(alloc).append(line)
}
BadRigidVar(x, tipe) => {
use ErrorType::*;
let bad_rigid_var = |name: &str, a_thing| {
let text = format!(
r#"The type annotation uses the type variable `{}` to say that this definition can produce any type of value. But in the body I see that it will only produce {} of a single specific type. Maybe change the type annotation to be more specific? Maybe change the code to be more general?"#,
name, a_thing
);
Self::hint(alloc).append(alloc.reflow(arena.alloc(text)))
};
let bad_double_rigid = |a, b| {
let text = format!(
r#"Your type annotation uses {} and {} as separate type variables. Your code seems to be saying they are the same though. Maybe they should be the same your type annotation? Maybe your code uses them in a weird way?"#,
a, b
);
Self::hint(alloc).append(alloc.reflow(arena.alloc(text)))
};
match tipe {
Infinite | Error | FlexVar(_) => alloc.nil(),
RigidVar(y) => bad_double_rigid(x.as_str(), y.as_str()),
Function(_, _) => bad_rigid_var(x.as_str(), "a function value"),
Record(_, _) => bad_rigid_var(x.as_str(), "a record value"),
TagUnion(_, _) | RecursiveTagUnion(_, _, _) => {
bad_rigid_var(x.as_str(), "a tag value")
}
Alias(symbol, _, _) | Type(symbol, _) => bad_rigid_var(
x.as_str(),
&format!("a {} value", symbol.ident_string(interns)),
),
Boolean(_) => bad_rigid_var(x.as_str(), "a uniqueness attribute value"),
}
}
_ => todo!(),
}
}

View file

@ -1143,15 +1143,22 @@ fn to_diff(parens: Parens, type1: &ErrorType, type2: &ErrorType) -> Diff<ReportT
}
}
_ => {
pair => {
// We hit none of the specific cases where we give more detailed information
let left = to_doc(Parens::Unnecessary, type1);
let right = to_doc(Parens::Unnecessary, type2);
let problems = match pair {
(RigidVar(x), other) | (other, RigidVar(x)) => {
vec![Problem::BadRigidVar(x.clone(), other.clone())]
}
_ => vec![],
};
Diff {
left,
right,
status: Status::Similar,
status: Status::Different(problems),
}
}
}
@ -1200,8 +1207,8 @@ fn diff_record(
let diff = to_diff(Parens::Unnecessary, t1, t2);
Diff {
left: (plain_text(field.as_str()), diff.left),
right: (plain_text(field.as_str()), diff.right),
left: (field.clone(), plain_text(field.as_str()), diff.left),
right: (field.clone(), plain_text(field.as_str()), diff.right),
status: diff.status,
}
};
@ -1262,7 +1269,7 @@ fn diff_record(
let ext_diff = ext_to_diff(ext1, ext2);
let mut fields_diff: Diff<Vec<(ReportText, ReportText)>> = Diff {
let mut fields_diff: Diff<Vec<(Lowercase, ReportText, ReportText)>> = Diff {
left: vec![],
right: vec![],
status: Status::Similar,
@ -1275,13 +1282,31 @@ fn diff_record(
}
if !all_fields_shared {
fields_diff.left.extend(left.map(|(_, x, y)| (x, y)));
fields_diff.right.extend(right.map(|(_, x, y)| (x, y)));
fields_diff.left.extend(left);
fields_diff.right.extend(right);
fields_diff.status.merge(Status::Different(vec![]));
}
let doc1 = report_text::record(fields_diff.left, ext_diff.left);
let doc2 = report_text::record(fields_diff.right, ext_diff.right);
// sort fields for display
fields_diff.left.sort_by(|a, b| a.0.cmp(&b.0));
fields_diff.right.sort_by(|a, b| a.0.cmp(&b.0));
let doc1 = report_text::record(
fields_diff
.left
.into_iter()
.map(|(_, b, c)| (b, c))
.collect(),
ext_diff.left,
);
let doc2 = report_text::record(
fields_diff
.right
.into_iter()
.map(|(_, b, c)| (b, c))
.collect(),
ext_diff.right,
);
fields_diff.status.merge(status);