convert ErrorType to ReportText

This commit is contained in:
Folkert 2020-04-06 14:18:10 +02:00
parent 854ffdae5e
commit 7818e84316
4 changed files with 512 additions and 47 deletions

View File

@ -1,4 +1,5 @@
use crate::report::ReportText::{BinOp, Concat, Module, Region, Value};
use roc_module::ident::TagName;
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
use roc_problem::can::{Problem, RuntimeError};
@ -162,7 +163,9 @@ pub enum ReportText {
/// A type. Render it using roc_types::pretty_print for now, but maybe
/// do something fancier later.
Type(Content),
ErrorType(ErrorType),
ErrorTypeInline(ErrorType),
ErrorTypeBlock(Box<ReportText>),
/// Plain text
Plain(Box<str>),
@ -199,6 +202,11 @@ pub enum ReportText {
/// Many ReportText that each get separate lines
Stack(Vec<ReportText>),
Intersperse {
separator: Box<ReportText>,
items: Vec<ReportText>,
},
Indent(usize, Box<ReportText>),
}
@ -214,6 +222,13 @@ pub fn em_text(str: &str) -> ReportText {
ReportText::EmText(Box::from(str))
}
pub fn tag_name_text(tag_name: TagName) -> ReportText {
match tag_name {
TagName::Private(symbol) => ReportText::PrivateTag(symbol),
TagName::Global(uppercase) => global_tag_text(uppercase.as_str()),
}
}
pub fn private_tag_text(symbol: Symbol) -> ReportText {
ReportText::PrivateTag(symbol)
}
@ -230,10 +245,34 @@ pub fn keyword_text(str: &str) -> ReportText {
ReportText::Keyword(Box::from(str))
}
pub fn error_type_inline(err: ErrorType) -> ReportText {
ReportText::ErrorTypeInline(err)
}
pub fn error_type_block(err: ReportText) -> ReportText {
ReportText::ErrorTypeBlock(Box::new(err))
}
pub fn url(str: &str) -> ReportText {
ReportText::Url(Box::from(str))
}
pub fn concat(values: Vec<ReportText>) -> ReportText {
ReportText::Concat(values)
}
pub fn separate(values: Vec<ReportText>) -> ReportText {
// TODO I think this should be a possibly-breaking space
intersperse(plain_text(" "), values)
}
pub fn intersperse(separator: ReportText, items: Vec<ReportText>) -> ReportText {
ReportText::Intersperse {
separator: Box::new(separator),
items,
}
}
pub const RED_CODE: &str = "\u{001b}[31m";
pub const WHITE_CODE: &str = "\u{001b}[37m";
pub const BLUE_CODE: &str = "\u{001b}[34m";
@ -266,12 +305,14 @@ pub enum Annotation {
LineNumber,
PlainText,
CodeBlock,
TypeBlock,
Module,
}
/// Render with minimal formatting
pub struct CiWrite<W> {
style_stack: Vec<Annotation>,
in_type_block: bool,
upstream: W,
}
@ -279,6 +320,7 @@ impl<W> CiWrite<W> {
pub fn new(upstream: W) -> CiWrite<W> {
CiWrite {
style_stack: vec![],
in_type_block: false,
upstream,
}
}
@ -323,17 +365,20 @@ where
fn push_annotation(&mut self, annotation: &Annotation) -> Result<(), Self::Error> {
use Annotation::*;
match annotation {
TypeBlock => {
self.in_type_block = true;
}
Emphasized => {
self.write_str("*")?;
}
Url => {
self.write_str("<")?;
}
GlobalTag | PrivateTag | Keyword | RecordField | Symbol => {
GlobalTag | PrivateTag | Keyword | RecordField | Symbol if !self.in_type_block => {
self.write_str("`")?;
}
CodeBlock | PlainText | LineNumber | Error | GutterBar | TypeVariable | Alias
| Module | Structure | BinOp => {}
_ => {}
}
self.style_stack.push(*annotation);
Ok(())
@ -345,17 +390,20 @@ where
match self.style_stack.pop() {
None => {}
Some(annotation) => match annotation {
TypeBlock => {
self.in_type_block = false;
}
Emphasized => {
self.write_str("*")?;
}
Url => {
self.write_str(">")?;
}
GlobalTag | PrivateTag | Keyword | RecordField | Symbol => {
GlobalTag | PrivateTag | Keyword | RecordField | Symbol if !self.in_type_block => {
self.write_str("`")?;
}
CodeBlock | PlainText | LineNumber | Error | GutterBar | TypeVariable | Alias
| Module | Structure | BinOp => {}
_ => {}
},
}
Ok(())
@ -423,7 +471,7 @@ where
Module => {
self.write_str(self.palette.module_name)?;
}
GlobalTag | PrivateTag | RecordField | Keyword => { /* nothing yet */ }
TypeBlock | GlobalTag | PrivateTag | RecordField | Keyword => { /* nothing yet */ }
}
self.style_stack.push(*annotation);
Ok(())
@ -440,7 +488,7 @@ where
self.write_str(RESET_CODE)?;
}
GlobalTag | PrivateTag | RecordField | Keyword => { /* nothing yet */ }
TypeBlock | GlobalTag | PrivateTag | RecordField | Keyword => { /* nothing yet */ }
},
}
Ok(())
@ -537,8 +585,8 @@ impl ReportText {
}
}
Value(symbol) => {
if symbol.module_id() == home {
// Render it unqualified if it's in the current module.
if symbol.module_id() == home || symbol.module_id().is_builtin() {
// Render it unqualified if it's in the current module or a builtin
alloc
.text(format!("{}", symbol.ident_string(interns)))
.annotate(Annotation::Symbol)
@ -571,7 +619,7 @@ impl ReportText {
Content::Error => alloc.text(content_to_string(content, subs, home, interns)),
},
ErrorType(error_type) => alloc
ErrorTypeInline(error_type) => alloc
.nil()
.append(alloc.hardline())
.append(
@ -581,6 +629,17 @@ impl ReportText {
)
.append(alloc.hardline()),
ErrorTypeBlock(error_type) => alloc
.nil()
.append(alloc.hardline())
.append(
error_type
.pretty(alloc, 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);
alloc.nil().append(rest).indent(n)
@ -599,6 +658,13 @@ impl ReportText {
.map(|rep| (rep.pretty(alloc, subs, home, src_lines, interns))),
alloc.hardline(),
),
Intersperse { separator, items } => alloc.intersperse(
items
.into_iter()
.map(|rep| (rep.pretty(alloc, subs, home, src_lines, interns)))
.collect::<Vec<_>>(),
separator.pretty(alloc, subs, home, src_lines, interns),
),
BinOp(bin_op) => alloc.text(bin_op.to_string()).annotate(Annotation::BinOp),
Region(region) => {
let max_line_number_length = (region.end_line + 1).to_string().len();

View File

@ -1,12 +1,14 @@
use crate::report::{
global_tag_text, keyword_text, plain_text, private_tag_text, record_field_text, Report,
ReportText,
error_type_block, error_type_inline, global_tag_text, keyword_text, plain_text,
private_tag_text, record_field_text, tag_name_text, Report, ReportText,
};
use roc_can::expected::{Expected, PExpected};
use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_solve::solve;
use roc_types::pretty_print::Parens;
use roc_types::subs::{Content, Variable};
use roc_types::types::{Category, ErrorType, PatternCategory, Reason};
use roc_types::types::{Category, ErrorType, PatternCategory, Reason, TypeExt};
use std::path::PathBuf;
pub fn type_problem(filename: PathBuf, problem: solve::TypeError) -> Report {
@ -25,10 +27,6 @@ pub fn type_problem(filename: PathBuf, problem: solve::TypeError) -> Report {
}
}
fn type_in_focus(typ: ErrorType) -> ReportText {
ReportText::ErrorType(typ)
}
fn int_to_ordinal(number: usize) -> String {
// NOTE: one-based
let remainder10 = number % 10;
@ -337,28 +335,6 @@ fn to_expr_report(
}
}
pub enum Problem {}
pub struct Comparison {
actual: ReportText,
expected: ReportText,
problems: Vec<Problem>,
}
fn problems_to_hint(_problems: Vec<Problem>) -> ReportText {
// TODO
ReportText::Concat(vec![])
}
fn to_comparison(actual: ErrorType, expected: ErrorType) -> Comparison {
// TODO make this do actual comparison
Comparison {
actual: type_in_focus(actual),
expected: type_in_focus(expected),
problems: vec![],
}
}
fn type_comparison(
actual: ErrorType,
expected: ErrorType,
@ -489,7 +465,7 @@ fn to_circular_report(
Region(region),
Stack(vec![
plain_text("Here is my best effort at writing down the type. You will see ∞ for parts of the type that repeat something already printed out infinitely."),
type_in_focus(overall_type),
error_type_block(to_doc(Parens::Unnecessary, &overall_type)),
/* TODO hint */
]),
];
@ -500,3 +476,423 @@ fn to_circular_report(
text: Concat(lines),
}
}
#[derive(Clone)]
pub enum Problem {
IntFloat,
ArityMismatch(usize, usize),
FieldTypo(Vec<Lowercase>),
}
fn problems_to_hint(_problems: Vec<Problem>) -> ReportText {
// TODO
ReportText::Concat(vec![])
}
pub struct Comparison {
actual: ReportText,
expected: ReportText,
problems: Vec<Problem>,
}
fn to_comparison(actual: ErrorType, expected: ErrorType) -> Comparison {
let diff = to_diff(Parens::Unnecessary, &actual, &expected);
Comparison {
actual: error_type_block(diff.left),
expected: error_type_block(diff.right),
problems: match diff.status {
Status::Similar => vec![],
Status::Different(problems) => problems,
},
}
}
pub enum Status {
Similar, // the structure is the same or e.g. record fields are different
Different(Vec<Problem>), // e.g. found Bool, expected Int
}
impl Status {
pub fn merge(&mut self, other: Self) {
use Status::*;
match self {
Similar => {
*self = other;
}
Different(problems1) => match other {
Similar => { /* nothing */ }
Different(problems2) => {
// TODO pick a data structure that makes this merge cheaper
let mut problems = Vec::with_capacity(problems1.len() + problems2.len());
problems.extend(problems1.iter().cloned());
problems.extend(problems2);
*self = Different(problems);
}
},
}
}
}
pub struct Diff<T> {
left: T,
right: T,
status: Status,
}
pub fn to_doc(parens: Parens, tipe: &ErrorType) -> ReportText {
use ErrorType::*;
match tipe {
Function(args, ret) => report_text::function(
parens,
args.iter().map(|arg| to_doc(Parens::InFn, arg)).collect(),
to_doc(Parens::InFn, ret),
),
Infinite => plain_text(""),
Error => plain_text("?"),
FlexVar(lowercase) => plain_text(lowercase.as_str()),
RigidVar(lowercase) => plain_text(lowercase.as_str()),
Type(symbol, args) => report_text::apply(
parens,
ReportText::Value(*symbol),
args.iter()
.map(|arg| to_doc(Parens::InTypeParam, arg))
.collect(),
),
Alias(symbol, args, _) => report_text::apply(
parens,
ReportText::Value(*symbol),
args.iter()
.map(|(_, arg)| to_doc(Parens::InTypeParam, arg))
.collect(),
),
Record(fields_map, ext) => {
let mut fields = fields_map.into_iter().collect::<Vec<_>>();
fields.sort_by(|(a, _), (b, _)| a.cmp(&b));
report_text::record(
fields
.into_iter()
.map(|(k, v)| (plain_text(k.as_str()), to_doc(Parens::Unnecessary, v)))
.collect(),
ext_to_doc(ext),
)
}
TagUnion(tags_map, ext) => {
let mut tags = tags_map
.into_iter()
.map(|(name, args)| {
(
name,
args.iter()
.map(|arg| to_doc(Parens::InTypeParam, arg))
.collect::<Vec<_>>(),
)
})
.collect::<Vec<_>>();
tags.sort_by(|(a, _), (b, _)| a.cmp(&b));
report_text::tag_union(
tags.into_iter()
.map(|(k, v)| (tag_name_text(k.clone()), v))
.collect(),
ext_to_doc(ext),
)
}
RecursiveTagUnion(rec_var, tags_map, ext) => {
let mut tags = tags_map
.into_iter()
.map(|(name, args)| {
(
name,
args.iter()
.map(|arg| to_doc(Parens::InTypeParam, arg))
.collect::<Vec<_>>(),
)
})
.collect::<Vec<_>>();
tags.sort_by(|(a, _), (b, _)| a.cmp(&b));
report_text::recursive_tag_union(
to_doc(Parens::Unnecessary, rec_var),
tags.into_iter()
.map(|(k, v)| (tag_name_text(k.clone()), v))
.collect(),
ext_to_doc(ext),
)
}
Boolean(b) => plain_text(&format!("{:?}", b)),
}
}
fn ext_to_doc(ext: &TypeExt) -> Option<ReportText> {
use TypeExt::*;
match ext {
Closed => None,
FlexOpen(lowercase) | RigidOpen(lowercase) => Some(plain_text(lowercase.as_str())),
}
}
fn same(parens: Parens, tipe: &ErrorType) -> Diff<ReportText> {
let doc = to_doc(parens, tipe);
Diff {
left: doc.clone(),
right: doc,
status: Status::Similar,
}
}
fn to_diff(parens: Parens, type1: &ErrorType, type2: &ErrorType) -> Diff<ReportText> {
use ErrorType::*;
match (type1, type2) {
(Error, Error) | (Infinite, Infinite) => same(parens, type1),
(FlexVar(x), FlexVar(y)) if x == y => same(parens, type1),
(RigidVar(x), RigidVar(y)) if x == y => same(parens, type1),
(Function(args1, ret1), Function(args2, ret2)) => {
if args1.len() == args2.len() {
let mut status = Status::Similar;
let arg_diff = traverse(Parens::InFn, args1, args2);
let ret_diff = to_diff(Parens::InFn, ret1, ret2);
status.merge(arg_diff.status);
status.merge(ret_diff.status);
let left = report_text::function(parens, arg_diff.left, ret_diff.left);
let right = report_text::function(parens, arg_diff.right, ret_diff.right);
Diff {
left,
right,
status,
}
} else {
let left = to_doc(Parens::InFn, type1);
let right = to_doc(Parens::InFn, type2);
Diff {
left,
right,
status: Status::Different(vec![Problem::ArityMismatch(
args1.len(),
args2.len(),
)]),
}
}
}
(Type(symbol1, args1), Type(symbol2, args2)) if symbol1 == symbol2 => {
let args_diff = traverse(Parens::InTypeParam, args1, args2);
let left = report_text::apply(parens, ReportText::Value(*symbol1), args_diff.left);
let right = report_text::apply(parens, ReportText::Value(*symbol2), args_diff.right);
Diff {
left,
right,
status: args_diff.status,
}
}
(Alias(symbol1, args1, _), Alias(symbol2, args2, _)) if symbol1 == symbol2 => {
// TODO remove collects
let a1 = args1.iter().map(|(_, v)| v).collect::<Vec<_>>();
let a2 = args2.iter().map(|(_, v)| v).collect::<Vec<_>>();
let args_diff = traverse(Parens::InTypeParam, a1, a2);
let left = report_text::apply(parens, ReportText::Value(*symbol1), args_diff.left);
let right = report_text::apply(parens, ReportText::Value(*symbol2), args_diff.right);
Diff {
left,
right,
status: args_diff.status,
}
}
_ => {
// TODO actually diff
// (Record(fields1, ext1), Record(fields2, ext2)) => {
let left = to_doc(Parens::Unnecessary, type1);
let right = to_doc(Parens::Unnecessary, type2);
Diff {
left,
right,
status: Status::Similar,
}
}
}
}
fn traverse<'a, I>(parens: Parens, args1: I, args2: I) -> Diff<Vec<ReportText>>
where
I: IntoIterator<Item = &'a ErrorType>,
{
let mut status = Status::Similar;
// TODO use ExactSizeIterator to pre-allocate here
let mut left = Vec::new();
let mut right = Vec::new();
for (arg1, arg2) in args1.into_iter().zip(args2.into_iter()) {
let diff = to_diff(parens, arg1, arg2);
left.push(diff.left);
right.push(diff.right);
status.merge(diff.status);
}
Diff {
left,
right,
status,
}
}
mod report_text {
use super::ReportText;
use crate::report::{concat, intersperse, plain_text, separate};
use roc_types::pretty_print::Parens;
fn with_parens(text: ReportText) -> ReportText {
ReportText::Concat(vec![plain_text("("), text, plain_text(")")])
}
pub fn function(parens: Parens, args: Vec<ReportText>, ret: ReportText) -> ReportText {
let function_text = concat(vec![
intersperse(plain_text(", "), args),
plain_text(" -> "),
ret,
]);
match parens {
Parens::Unnecessary => function_text,
_ => with_parens(function_text),
}
}
pub fn apply(parens: Parens, name: ReportText, args: Vec<ReportText>) -> ReportText {
if args.is_empty() {
name
} else {
let apply_text = concat(vec![
name,
plain_text(" "),
intersperse(plain_text(" "), args),
]);
match parens {
Parens::Unnecessary | Parens::InFn => apply_text,
Parens::InTypeParam => with_parens(apply_text),
}
}
}
pub fn record(
entries: Vec<(ReportText, ReportText)>,
opt_ext: Option<ReportText>,
) -> ReportText {
let ext_text = if let Some(t) = opt_ext {
t
} else {
plain_text("")
};
if entries.is_empty() {
concat(vec![plain_text("{}"), ext_text])
} else {
let entry_to_text = |(field_name, field_type)| {
separate(vec![concat(vec![field_name, plain_text(" :")]), field_type])
};
let starts = std::iter::once(plain_text("{")).chain(std::iter::repeat(plain_text(",")));
let mut lines: Vec<_> = entries
.into_iter()
.zip(starts)
.map(|(entry, start)| concat(vec![start, entry_to_text(entry)]))
.collect();
lines.push(plain_text("}"));
lines.push(ext_text);
concat(lines)
}
}
pub fn tag_union(
entries: Vec<(ReportText, Vec<ReportText>)>,
opt_ext: Option<ReportText>,
) -> ReportText {
let ext_text = if let Some(t) = opt_ext {
t
} else {
plain_text("")
};
if entries.is_empty() {
concat(vec![plain_text("[]"), ext_text])
} else {
let entry_to_text = |(tag_name, arguments)| concat(vec![tag_name, separate(arguments)]);
let starts = std::iter::once(plain_text("[")).chain(std::iter::repeat(plain_text(",")));
let mut lines: Vec<_> = entries
.into_iter()
.zip(starts)
.map(|(entry, start)| concat(vec![start, entry_to_text(entry)]))
.collect();
lines.push(plain_text("]"));
lines.push(ext_text);
concat(lines)
}
}
pub fn recursive_tag_union(
rec_var: ReportText,
entries: Vec<(ReportText, Vec<ReportText>)>,
opt_ext: Option<ReportText>,
) -> ReportText {
let ext_text = if let Some(t) = opt_ext {
t
} else {
plain_text("")
};
if entries.is_empty() {
concat(vec![plain_text("[]"), ext_text])
} else {
let entry_to_text = |(tag_name, arguments)| concat(vec![tag_name, separate(arguments)]);
let starts = std::iter::once(plain_text("[")).chain(std::iter::repeat(plain_text(",")));
let mut lines: Vec<_> = entries
.into_iter()
.zip(starts)
.map(|(entry, start)| concat(vec![start, entry_to_text(entry)]))
.collect();
lines.push(plain_text("]"));
lines.push(ext_text);
lines.push(plain_text(" as "));
lines.push(rec_var);
concat(lines)
}
}
}

View File

@ -1265,21 +1265,24 @@ fn flat_type_to_err_type(subs: &mut Subs, state: &mut NameState, flat_type: Flat
err_tags.insert(tag, err_vars);
}
let rec_error_type = Box::new(var_to_err_type(subs, state, rec_var));
match var_to_err_type(subs, state, ext_var).unwrap_alias() {
ErrorType::RecursiveTagUnion(rec_var, sub_tags, sub_ext) => {
ErrorType::RecursiveTagUnion(rec_var, sub_tags.union(err_tags), sub_ext)
debug_assert!(rec_var == rec_error_type);
ErrorType::RecursiveTagUnion(rec_error_type, sub_tags.union(err_tags), sub_ext)
}
ErrorType::TagUnion(sub_tags, sub_ext) => {
ErrorType::RecursiveTagUnion(rec_var, sub_tags.union(err_tags), sub_ext)
ErrorType::RecursiveTagUnion(rec_error_type, sub_tags.union(err_tags), sub_ext)
}
ErrorType::FlexVar(var) => {
ErrorType::RecursiveTagUnion(rec_var, err_tags, TypeExt::FlexOpen(var))
ErrorType::RecursiveTagUnion(rec_error_type, err_tags, TypeExt::FlexOpen(var))
}
ErrorType::RigidVar(var) => {
ErrorType::RecursiveTagUnion(rec_var, err_tags, TypeExt::RigidOpen(var))
ErrorType::RecursiveTagUnion(rec_error_type, err_tags, TypeExt::RigidOpen(var))
}
other =>

View File

@ -709,7 +709,7 @@ pub enum ErrorType {
RigidVar(Lowercase),
Record(SendMap<Lowercase, ErrorType>, TypeExt),
TagUnion(SendMap<TagName, Vec<ErrorType>>, TypeExt),
RecursiveTagUnion(Variable, SendMap<TagName, Vec<ErrorType>>, TypeExt),
RecursiveTagUnion(Box<ErrorType>, SendMap<TagName, Vec<ErrorType>>, TypeExt),
Function(Vec<ErrorType>, Box<ErrorType>),
Alias(Symbol, Vec<(Lowercase, ErrorType)>, Box<ErrorType>),
Boolean(boolean_algebra::Bool),