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::subs::Variable;
use roc_types::types::AnnotationSource::{self, *}; use roc_types::types::AnnotationSource::{self, *};
use roc_types::types::Type::{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 /// This is for constraining Defs
#[derive(Default, Debug)] #[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::subs::{VarStore, Variable};
use roc_types::types::AnnotationSource::{self, *}; use roc_types::types::AnnotationSource::{self, *};
use roc_types::types::Type::{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::builtins::{attr_type, empty_list_type, list_type, str_type};
use roc_uniq::sharing::{self, Container, FieldAccess, Mark, Usage, VarUsage}; 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 crate::report::ReportText::{BinOp, Concat, Module, Region, Value};
use roc_can::expected::{Expected, PExpected};
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_problem::can::PrecedenceProblem::BothNonAssociative; use roc_problem::can::PrecedenceProblem::BothNonAssociative;
use roc_problem::can::Problem; use roc_problem::can::Problem;
use roc_solve::solve;
use roc_types::pretty_print::content_to_string; use roc_types::pretty_print::content_to_string;
use roc_types::subs::{Content, Subs, Variable}; use roc_types::subs::{Content, Subs};
use roc_types::types::{write_error_type, Category, ErrorType, PReason, PatternCategory, Reason}; use roc_types::types::{write_error_type, ErrorType};
use std::path::PathBuf; use std::path::PathBuf;
/// A textual report. /// 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 { pub fn can_problem(filename: PathBuf, problem: Problem) -> Report {
let mut texts = Vec::new(); let mut texts = Vec::new();
@ -352,7 +148,7 @@ pub fn can_problem(filename: PathBuf, problem: Problem) -> Report {
Report { Report {
filename, filename,
text: Batch(texts), text: Concat(texts),
} }
} }
@ -387,7 +183,12 @@ pub enum ReportText {
BinOp(roc_parse::operator::BinOp), BinOp(roc_parse::operator::BinOp),
/// Many ReportText that should be concatenated together. /// 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 { pub fn plain_text(str: &str) -> ReportText {
@ -408,9 +209,8 @@ pub fn url(str: &str) -> ReportText {
Url(Box::from(str)) Url(Box::from(str))
} }
#[allow(dead_code)] pub fn with_indent(n: usize, report_text: ReportText) -> ReportText {
fn newline() -> ReportText { ReportText::Indent(n, Box::new(report_text))
plain_text("\n")
} }
pub const RED_CODE: &str = "\u{001b}[31m"; 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"; pub const RESET_CODE: &str = "\u{001b}[0m";
struct CiEnv<'a> {
home: ModuleId,
src_lines: &'a [&'a str],
interns: &'a Interns,
}
impl ReportText { impl ReportText {
/// Render to CI console output, where no colors are available. /// Render to CI console output, where no colors are available.
pub fn render_ci( pub fn render_ci(
@ -483,6 +289,16 @@ impl ReportText {
src_lines: &[&str], src_lines: &[&str],
interns: &Interns, 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::*; use ReportText::*;
match self { match self {
@ -499,25 +315,31 @@ impl ReportText {
buf.push('>'); buf.push('>');
} }
Value(symbol) => { Value(symbol) => {
if symbol.module_id() == home { if symbol.module_id() == env.home {
// Render it unqualified if it's in the current module. // 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 { } else {
buf.push_str(symbol.module_string(interns)); buf.push_str(symbol.module_string(env.interns));
buf.push('.'); buf.push('.');
buf.push_str(symbol.ident_string(interns)); buf.push_str(symbol.ident_string(env.interns));
} }
} }
Module(module_id) => { 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) => { Region(region) => {
buf.push('\n'); buf.push('\n');
buf.push('\n'); buf.push('\n');
dbg!(region);
// widest displayed line number // widest displayed line number
let max_line_number_length = (region.end_line + 1).to_string().len(); 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(line_number);
buf.push_str(""); buf.push_str("");
let line = src_lines[i as usize]; let line = env.src_lines[i as usize];
if !line.trim().is_empty() { if !line.trim().is_empty() {
buf.push_str(" "); buf.push_str(" ");
buf.push_str(src_lines[i as usize]); buf.push_str(env.src_lines[i as usize]);
} }
buf.push('\n'); buf.push('\n');
@ -566,11 +388,11 @@ impl ReportText {
buf.push_str(line_number); buf.push_str(line_number);
buf.push_str(" ┆>"); buf.push_str(" ┆>");
let line = src_lines[i as usize]; let line = env.src_lines[i as usize];
if !line.trim().is_empty() { if !line.trim().is_empty() {
buf.push_str(" "); 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 { if i != region.end_line {
@ -582,12 +404,27 @@ impl ReportText {
buf.push('\n'); buf.push('\n');
buf.push('\n'); buf.push('\n');
} }
Indent(n, nested) => {
nested.render_ci_help(env, buf, subs, indent + n);
}
Docs(_) => { Docs(_) => {
panic!("TODO implment docs"); panic!("TODO implment docs");
} }
Batch(report_texts) => { Concat(report_texts) => {
for report_text in 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) => { BinOp(bin_op) => {
@ -729,7 +566,11 @@ impl ReportText {
buf.push('\n'); buf.push('\n');
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 { for report_text in report_texts {
report_text.render_color_terminal(buf, subs, home, src_lines, interns, palette); 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 crate::helpers::test_home;
use roc_module::symbol::{Interns, ModuleId}; use roc_module::symbol::{Interns, ModuleId};
use roc_reporting::report::{ use roc_reporting::report::{
can_problem, em_text, plain_text, type_problem, url, Report, ReportText, BLUE_CODE, can_problem, em_text, plain_text, url, Report, ReportText, BLUE_CODE, BOLD_CODE, CYAN_CODE,
BOLD_CODE, CYAN_CODE, GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, TEST_PALETTE, GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, TEST_PALETTE, UNDERLINE_CODE, WHITE_CODE,
UNDERLINE_CODE, WHITE_CODE, YELLOW_CODE, YELLOW_CODE,
}; };
use roc_reporting::type_error::type_problem;
use roc_types::pretty_print::name_all_type_vars; use roc_types::pretty_print::name_all_type_vars;
use roc_types::subs::Subs; use roc_types::subs::Subs;
use std::path::PathBuf; use std::path::PathBuf;
// use roc_region::all; // use roc_region::all;
use crate::helpers::{can_expr, infer_expr, CanExprOut}; 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_solve::solve;
use roc_types::subs::Content::{FlexVar, RigidVar, Structure}; use roc_types::subs::Content::{FlexVar, RigidVar, Structure};
use roc_types::subs::FlatType::EmptyRecord; use roc_types::subs::FlatType::EmptyRecord;
@ -276,7 +277,7 @@ mod test_reporting {
report_texts.push(em_text("y")); report_texts.push(em_text("y"));
report_renders_as( report_renders_as(
to_simple_report(Batch(report_texts)), to_simple_report(Concat(report_texts)),
"Wait a second. There is a problem here. -> *y*", "Wait a second. There is a problem here. -> *y*",
); );
} }
@ -566,7 +567,7 @@ mod test_reporting {
report_texts.push(Type(Structure(EmptyRecord))); report_texts.push(Type(Structure(EmptyRecord)));
report_renders_in_color( report_renders_in_color(
to_simple_report(Batch(report_texts)), to_simple_report(Concat(report_texts)),
"<yellow>List<reset><white> <reset><green>{}<reset>", "<yellow>List<reset><white> <reset><green>{}<reset>",
); );
} }
@ -775,6 +776,7 @@ mod test_reporting {
Str Str
But I need this `if` condition to be a Bool value. But I need this `if` condition to be a Bool value.
"# "#
), ),
) )
@ -803,6 +805,8 @@ mod test_reporting {
Num a Num a
"# "#
), ),
) )
@ -839,7 +843,7 @@ mod test_reporting {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
f = \x -> f [x] f = \x -> f [x]
f f
"# "#
@ -848,7 +852,7 @@ mod test_reporting {
r#" r#"
I'm inferring a weird self-referential type for f: 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. 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) => { Failure(vars, actual_type, expected_type) => {
introduce(subs, rank, pools, &vars);
let problem = TypeError::BadExpr( let problem = TypeError::BadExpr(
*region, *region,
Category::Lookup(*symbol), Category::Lookup(*symbol),

View file

@ -817,14 +817,17 @@ fn write_error_type_help(
} }
Record(fields, ext) => { Record(fields, ext) => {
buf.push('{'); 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('}'); buf.push('}');
write_type_ext(ext, buf); write_type_ext(ext, buf);
} }
Infinite => {
buf.push_str("");
}
other => todo!("cannot format {:?} yet", other), other => todo!("cannot format {:?} yet", other),
} }
} }