refactor and clean up

This commit is contained in:
Folkert 2020-04-03 01:12:17 +02:00
parent 604dbf7215
commit 1981a7e467
7 changed files with 355 additions and 241 deletions

View File

@ -16,7 +16,7 @@ use roc_region::all::{Located, Region};
use roc_types::subs::Variable;
use roc_types::types::AnnotationSource::{self, *};
use roc_types::types::Type::{self, *};
use roc_types::types::{Alias, Category, PReason, PatternCategory, Reason};
use roc_types::types::{Alias, Category, PReason, Reason};
/// This is for constraining Defs
#[derive(Default, Debug)]

View File

@ -14,7 +14,7 @@ use roc_types::boolean_algebra::{Atom, Bool};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::AnnotationSource::{self, *};
use roc_types::types::Type::{self, *};
use roc_types::types::{Alias, Category, PReason, PatternCategory, Reason};
use roc_types::types::{Alias, Category, PReason, Reason};
use roc_uniq::builtins::{attr_type, empty_list_type, list_type, str_type};
use roc_uniq::sharing::{self, Container, FieldAccess, Mark, Usage, VarUsage};

View File

@ -1,12 +1,10 @@
use crate::report::ReportText::{Batch, BinOp, Module, Region, Value};
use roc_can::expected::{Expected, PExpected};
use crate::report::ReportText::{BinOp, Concat, Module, Region, Value};
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
use roc_problem::can::Problem;
use roc_solve::solve;
use roc_types::pretty_print::content_to_string;
use roc_types::subs::{Content, Subs, Variable};
use roc_types::types::{write_error_type, Category, ErrorType, PReason, PatternCategory, Reason};
use roc_types::subs::{Content, Subs};
use roc_types::types::{write_error_type, ErrorType};
use std::path::PathBuf;
/// A textual report.
@ -72,208 +70,6 @@ impl Color {
}
}
pub fn type_problem(filename: PathBuf, problem: solve::TypeError) -> Report {
use solve::TypeError::*;
match problem {
BadExpr(region, category, found, expected) => {
to_expr_report(filename, region, category, found, expected)
}
BadPattern(region, category, found, expected) => {
to_pattern_report(filename, region, category, found, expected)
}
CircularType(region, symbol, overall_type) => {
to_circular_report(filename, region, symbol, overall_type)
}
}
}
fn type_in_focus(typ: ErrorType) -> ReportText {
ReportText::Batch(vec![
newline(),
newline(),
plain_text(" "),
ReportText::ErrorType(typ),
newline(),
newline(),
])
}
fn int_to_ordinal(number: usize) -> String {
// NOTE: one-based
let remainder10 = number % 10;
let remainder100 = number % 100;
let ending = match remainder100 {
11..=13 => "th",
_ => match remainder10 {
1 => "st",
2 => "nd",
3 => "rd",
_ => "th",
},
};
format!("{}{}", number, ending)
}
#[allow(too_many_arguments)]
fn report_mismatch(
filename: PathBuf,
category: &Category,
found: ErrorType,
expected_type: ErrorType,
region: roc_region::all::Region,
opt_highlight: Option<roc_region::all::Region>,
problem: &str,
this_is: &str,
instead_of: &str,
further_details: ReportText,
) -> Report {
use ReportText::*;
let lines = vec![
plain_text(problem),
Region(region),
add_category(this_is, category),
type_in_focus(found),
plain_text(instead_of),
type_in_focus(expected_type),
further_details,
];
Report {
filename,
text: Batch(lines),
}
}
#[allow(too_many_arguments)]
fn report_bad_type(
filename: PathBuf,
category: &Category,
found: ErrorType,
expected_type: ErrorType,
region: roc_region::all::Region,
opt_highlight: Option<roc_region::all::Region>,
problem: &str,
this_is: &str,
further_details: ReportText,
) -> Report {
use ReportText::*;
let lines = vec![
plain_text(problem),
Region(region),
add_category(this_is, &category),
type_in_focus(found),
further_details,
];
Report {
filename,
text: Batch(lines),
}
}
fn to_expr_report(
filename: PathBuf,
expr_region: roc_region::all::Region,
category: Category,
found: ErrorType,
expected: Expected<ErrorType>,
) -> Report {
use ReportText::*;
match expected {
Expected::NoExpectation(expected_type) => todo!(),
Expected::FromAnnotation(name, arity, sub_context, expected_type) => todo!(),
Expected::ForReason(reason, expected_type, region) => {
match reason {
Reason::IfCondition => report_bad_type(
filename,
&category,
found,
expected_type,
region,
Some(expr_region),
"This `if` condition does not evaluate to a boolean value, True or False.",
"It is",
Batch(vec![
plain_text("But I need this `if` condition to be a "),
ReportText::Type(Content::Alias(Symbol::BOOL_BOOL, vec![], Variable::BOOL)),
plain_text(" value."),
newline(),
]),
),
Reason::IfBranch { index } => {
let ith = int_to_ordinal(index);
report_mismatch(
filename,
&category,
found,
expected_type,
region,
Some(expr_region),
&format!(
"The {} branch of this `if` does not match all the previous branches:",
ith
),
&format!("The {} branch is", ith),
"But all the previous branches result in",
Batch(vec![ /* TODO add hint */ ]),
)
}
_ => todo!(),
}
}
}
}
fn add_category(this_is: &str, category: &Category) -> ReportText {
use Category::*;
let result = match category {
Str => format!("{} a string of type:", this_is),
_ => todo!(),
};
plain_text(&*result)
}
fn to_pattern_report(
filename: PathBuf,
expr_region: roc_region::all::Region,
category: PatternCategory,
found: ErrorType,
expected: PExpected<ErrorType>,
) -> Report {
use ReportText::*;
todo!()
}
fn to_circular_report(
filename: PathBuf,
region: roc_region::all::Region,
symbol: Symbol,
overall_type: ErrorType,
) -> Report {
use ReportText::*;
let lines = vec![
plain_text("I'm inferring a weird self-referential type for "),
Value(symbol),
plain_text(":"),
Region(region),
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),
/* TODO hint */
];
Report {
filename,
text: Batch(lines),
}
}
pub fn can_problem(filename: PathBuf, problem: Problem) -> Report {
let mut texts = Vec::new();
@ -352,7 +148,7 @@ pub fn can_problem(filename: PathBuf, problem: Problem) -> Report {
Report {
filename,
text: Batch(texts),
text: Concat(texts),
}
}
@ -387,7 +183,12 @@ pub enum ReportText {
BinOp(roc_parse::operator::BinOp),
/// Many ReportText that should be concatenated together.
Batch(Vec<ReportText>),
Concat(Vec<ReportText>),
/// Many ReportText that each get separate lines
Stack(Vec<ReportText>),
Indent(usize, Box<ReportText>),
}
pub fn plain_text(str: &str) -> ReportText {
@ -408,9 +209,8 @@ pub fn url(str: &str) -> ReportText {
Url(Box::from(str))
}
#[allow(dead_code)]
fn newline() -> ReportText {
plain_text("\n")
pub fn with_indent(n: usize, report_text: ReportText) -> ReportText {
ReportText::Indent(n, Box::new(report_text))
}
pub const RED_CODE: &str = "\u{001b}[31m";
@ -473,6 +273,12 @@ fn white(str: &str) -> String {
pub const RESET_CODE: &str = "\u{001b}[0m";
struct CiEnv<'a> {
home: ModuleId,
src_lines: &'a [&'a str],
interns: &'a Interns,
}
impl ReportText {
/// Render to CI console output, where no colors are available.
pub fn render_ci(
@ -483,6 +289,16 @@ impl ReportText {
src_lines: &[&str],
interns: &Interns,
) {
let env = CiEnv {
home,
src_lines,
interns,
};
self.render_ci_help(&env, buf, subs, 0);
}
fn render_ci_help(self, env: &CiEnv, buf: &mut String, subs: &mut Subs, indent: usize) {
use ReportText::*;
match self {
@ -499,25 +315,31 @@ impl ReportText {
buf.push('>');
}
Value(symbol) => {
if symbol.module_id() == home {
if symbol.module_id() == env.home {
// Render it unqualified if it's in the current module.
buf.push_str(symbol.ident_string(interns));
buf.push_str(symbol.ident_string(env.interns));
} else {
buf.push_str(symbol.module_string(interns));
buf.push_str(symbol.module_string(env.interns));
buf.push('.');
buf.push_str(symbol.ident_string(interns));
buf.push_str(symbol.ident_string(env.interns));
}
}
Module(module_id) => {
buf.push_str(&interns.module_name(module_id));
buf.push_str(&env.interns.module_name(module_id));
}
Type(content) => {
buf.push_str(content_to_string(content, subs, env.home, env.interns).as_str())
}
ErrorType(error_type) => {
buf.push('\n');
buf.push_str(" ".repeat(indent).as_str());
buf.push_str(&write_error_type(env.home, env.interns, error_type));
buf.push('\n');
}
Type(content) => buf.push_str(content_to_string(content, subs, home, interns).as_str()),
ErrorType(error_type) => buf.push_str(&write_error_type(home, interns, error_type)),
Region(region) => {
buf.push('\n');
buf.push('\n');
dbg!(region);
// widest displayed line number
let max_line_number_length = (region.end_line + 1).to_string().len();
@ -535,11 +357,11 @@ impl ReportText {
buf.push_str(line_number);
buf.push_str("");
let line = src_lines[i as usize];
let line = env.src_lines[i as usize];
if !line.trim().is_empty() {
buf.push_str(" ");
buf.push_str(src_lines[i as usize]);
buf.push_str(env.src_lines[i as usize]);
}
buf.push('\n');
@ -566,11 +388,11 @@ impl ReportText {
buf.push_str(line_number);
buf.push_str(" ┆>");
let line = src_lines[i as usize];
let line = env.src_lines[i as usize];
if !line.trim().is_empty() {
buf.push_str(" ");
buf.push_str(src_lines[i as usize]);
buf.push_str(env.src_lines[i as usize]);
}
if i != region.end_line {
@ -582,12 +404,27 @@ impl ReportText {
buf.push('\n');
buf.push('\n');
}
Indent(n, nested) => {
nested.render_ci_help(env, buf, subs, indent + n);
}
Docs(_) => {
panic!("TODO implment docs");
}
Batch(report_texts) => {
Concat(report_texts) => {
for report_text in report_texts {
report_text.render_ci(buf, subs, home, src_lines, interns);
report_text.render_ci_help(env, buf, subs, indent);
}
}
Stack(report_texts) => {
let mut it = report_texts.into_iter().peekable();
while let Some(report_text) = it.next() {
report_text.render_ci_help(env, buf, subs, indent);
buf.push('\n');
if it.peek().is_some() {
buf.push_str(" ".repeat(indent).as_str());
}
}
}
BinOp(bin_op) => {
@ -729,7 +566,11 @@ impl ReportText {
buf.push('\n');
buf.push('\n');
}
Batch(report_texts) => {
Indent(n, nested) => {
buf.push_str(" ".repeat(n).as_str());
nested.render_color_terminal(buf, subs, home, src_lines, interns, palette);
}
Concat(report_texts) => {
for report_text in report_texts {
report_text.render_color_terminal(buf, subs, home, src_lines, interns, palette);
}

View File

@ -0,0 +1,264 @@
use crate::report::{plain_text, with_indent, Report, ReportText};
use roc_can::expected::{Expected, PExpected};
use roc_module::symbol::Symbol;
use roc_solve::solve;
use roc_types::subs::{Content, Variable};
use roc_types::types::{Category, ErrorType, PatternCategory, Reason};
use std::path::PathBuf;
pub fn type_problem(filename: PathBuf, problem: solve::TypeError) -> Report {
use solve::TypeError::*;
match problem {
BadExpr(region, category, found, expected) => {
to_expr_report(filename, region, category, found, expected)
}
BadPattern(region, category, found, expected) => {
to_pattern_report(filename, region, category, found, expected)
}
CircularType(region, symbol, overall_type) => {
to_circular_report(filename, region, symbol, overall_type)
}
}
}
fn type_in_focus(typ: ErrorType) -> ReportText {
ReportText::ErrorType(typ)
}
fn int_to_ordinal(number: usize) -> String {
// NOTE: one-based
let remainder10 = number % 10;
let remainder100 = number % 100;
let ending = match remainder100 {
11..=13 => "th",
_ => match remainder10 {
1 => "st",
2 => "nd",
3 => "rd",
_ => "th",
},
};
format!("{}{}", number, ending)
}
#[allow(clippy::too_many_arguments)]
fn report_mismatch(
filename: PathBuf,
category: &Category,
found: ErrorType,
expected_type: ErrorType,
region: roc_region::all::Region,
_opt_highlight: Option<roc_region::all::Region>,
problem: &str,
this_is: &str,
instead_of: &str,
further_details: ReportText,
) -> Report {
use ReportText::*;
let lines = vec![
plain_text(problem),
Region(region),
type_comparison(
found,
expected_type,
add_category(this_is, category),
instead_of,
further_details,
),
];
Report {
filename,
text: Concat(lines),
}
}
#[allow(clippy::too_many_arguments)]
fn report_bad_type(
filename: PathBuf,
category: &Category,
found: ErrorType,
expected_type: ErrorType,
region: roc_region::all::Region,
_opt_highlight: Option<roc_region::all::Region>,
problem: &str,
this_is: &str,
further_details: ReportText,
) -> Report {
use ReportText::*;
let lines = vec![
plain_text(problem),
Region(region),
lone_type(
found,
expected_type,
add_category(this_is, &category),
further_details,
),
];
Report {
filename,
text: Concat(lines),
}
}
fn to_expr_report(
filename: PathBuf,
expr_region: roc_region::all::Region,
category: Category,
found: ErrorType,
expected: Expected<ErrorType>,
) -> Report {
use ReportText::*;
match expected {
Expected::NoExpectation(_expected_type) => todo!(),
Expected::FromAnnotation(_name, _arity, _sub_context, _expected_type) => todo!(),
Expected::ForReason(reason, expected_type, region) => {
match reason {
Reason::IfCondition => report_bad_type(
filename,
&category,
found,
expected_type,
region,
Some(expr_region),
"This `if` condition does not evaluate to a boolean value, True or False.",
"It is",
Concat(vec![
plain_text("But I need this `if` condition to be a "),
ReportText::Type(Content::Alias(Symbol::BOOL_BOOL, vec![], Variable::BOOL)),
plain_text(" value."),
]),
),
Reason::IfBranch { index } => {
let ith = int_to_ordinal(index);
report_mismatch(
filename,
&category,
found,
expected_type,
region,
Some(expr_region),
&format!(
"The {} branch of this `if` does not match all the previous branches:",
ith
),
&format!("The {} branch is", ith),
"But all the previous branches result in",
Concat(vec![ /* TODO add hint */ ]),
)
}
_ => todo!(),
}
}
}
}
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,
i_am_seeing: ReportText,
instead_of: &str,
context_hints: ReportText,
) -> ReportText {
let comparison = to_comparison(actual, expected);
ReportText::Stack(vec![
i_am_seeing,
with_indent(4, comparison.actual),
plain_text(instead_of),
with_indent(4, comparison.expected),
context_hints,
problems_to_hint(comparison.problems),
])
}
fn lone_type(
actual: ErrorType,
expected: ErrorType,
i_am_seeing: ReportText,
further_details: ReportText,
) -> ReportText {
let comparison = to_comparison(actual, expected);
ReportText::Stack(vec![
i_am_seeing,
with_indent(4, comparison.actual),
further_details,
problems_to_hint(comparison.problems),
])
}
fn add_category(this_is: &str, category: &Category) -> ReportText {
use Category::*;
let result = match category {
Str => format!("{} a string of type:", this_is),
_ => todo!(),
};
plain_text(&*result)
}
fn to_pattern_report(
_filename: PathBuf,
_expr_region: roc_region::all::Region,
_category: PatternCategory,
_found: ErrorType,
_expected: PExpected<ErrorType>,
) -> Report {
todo!()
}
fn to_circular_report(
filename: PathBuf,
region: roc_region::all::Region,
symbol: Symbol,
overall_type: ErrorType,
) -> Report {
use ReportText::*;
let lines = vec![
plain_text("I'm inferring a weird self-referential type for "),
Value(symbol),
plain_text(":"),
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."),
with_indent(4, type_in_focus(overall_type)),
/* TODO hint */
]),
];
Report {
filename,
text: Concat(lines),
}
}

View File

@ -12,16 +12,17 @@ mod test_reporting {
use crate::helpers::test_home;
use roc_module::symbol::{Interns, ModuleId};
use roc_reporting::report::{
can_problem, em_text, plain_text, type_problem, url, Report, ReportText, BLUE_CODE,
BOLD_CODE, CYAN_CODE, GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, TEST_PALETTE,
UNDERLINE_CODE, WHITE_CODE, YELLOW_CODE,
can_problem, em_text, plain_text, url, Report, ReportText, BLUE_CODE, BOLD_CODE, CYAN_CODE,
GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, TEST_PALETTE, UNDERLINE_CODE, WHITE_CODE,
YELLOW_CODE,
};
use roc_reporting::type_error::type_problem;
use roc_types::pretty_print::name_all_type_vars;
use roc_types::subs::Subs;
use std::path::PathBuf;
// use roc_region::all;
use crate::helpers::{can_expr, infer_expr, CanExprOut};
use roc_reporting::report::ReportText::{Batch, Module, Region, Type, Value};
use roc_reporting::report::ReportText::{Concat, Module, Region, Type, Value};
use roc_solve::solve;
use roc_types::subs::Content::{FlexVar, RigidVar, Structure};
use roc_types::subs::FlatType::EmptyRecord;
@ -276,7 +277,7 @@ mod test_reporting {
report_texts.push(em_text("y"));
report_renders_as(
to_simple_report(Batch(report_texts)),
to_simple_report(Concat(report_texts)),
"Wait a second. There is a problem here. -> *y*",
);
}
@ -566,7 +567,7 @@ mod test_reporting {
report_texts.push(Type(Structure(EmptyRecord)));
report_renders_in_color(
to_simple_report(Batch(report_texts)),
to_simple_report(Concat(report_texts)),
"<yellow>List<reset><white> <reset><green>{}<reset>",
);
}
@ -775,6 +776,7 @@ mod test_reporting {
Str
But I need this `if` condition to be a Bool value.
"#
),
)
@ -803,6 +805,8 @@ mod test_reporting {
Num a
"#
),
)
@ -839,7 +843,7 @@ mod test_reporting {
report_problem_as(
indoc!(
r#"
f = \x -> f [x]
f = \x -> f [x]
f
"#
@ -848,7 +852,7 @@ mod test_reporting {
r#"
I'm inferring a weird self-referential type for f:
1 f = \x -> f [x]
1 f = \x -> f [x]
^
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.

View File

@ -217,6 +217,8 @@ fn solve(
}
Failure(vars, actual_type, expected_type) => {
introduce(subs, rank, pools, &vars);
let problem = TypeError::BadExpr(
*region,
Category::Lookup(*symbol),

View File

@ -817,14 +817,17 @@ fn write_error_type_help(
}
Record(fields, ext) => {
buf.push('{');
for (label, content) in fields {
buf.push_str(label.as_str());
buf.push_str(": ");
write_error_type_help(home, interns, content, buf, Parens::Unnecessary);
}
buf.push('}');
write_type_ext(ext, buf);
}
Infinite => {
buf.push_str("");
}
other => todo!("cannot format {:?} yet", other),
}
}