report first type error

This commit is contained in:
Folkert 2020-04-01 23:37:05 +02:00
parent 106a3646bf
commit 8642cfeae0
5 changed files with 124 additions and 15 deletions

View file

@ -355,16 +355,19 @@ pub fn constrain_expr(
branches, branches,
final_else, final_else,
} => { } => {
let expect_bool = |region| {
let bool_type = Type::Variable(Variable::BOOL); let bool_type = Type::Variable(Variable::BOOL);
let expect_bool = Expected::ForReason(Reason::IfCondition, bool_type, region); Expected::ForReason(Reason::IfCondition, bool_type, region)
};
let mut branch_cons = Vec::with_capacity(2 * branches.len() + 3); let mut branch_cons = Vec::with_capacity(2 * branches.len() + 3);
// TODO why does this cond var exist? is it for error messages? // TODO why does this cond var exist? is it for error messages?
let first_cond_region = branches[0].0.region;
let cond_var_is_bool_con = Eq( let cond_var_is_bool_con = Eq(
Type::Variable(*cond_var), Type::Variable(*cond_var),
expect_bool.clone(), expect_bool(first_cond_region),
Category::If, Category::If,
Region::zero(), first_cond_region,
); );
branch_cons.push(cond_var_is_bool_con); branch_cons.push(cond_var_is_bool_con);
@ -376,7 +379,7 @@ pub fn constrain_expr(
env, env,
loc_cond.region, loc_cond.region,
&loc_cond.value, &loc_cond.value,
expect_bool.clone(), expect_bool(loc_cond.region),
); );
let then_con = constrain_expr( let then_con = constrain_expr(
@ -424,7 +427,7 @@ pub fn constrain_expr(
env, env,
loc_cond.region, loc_cond.region,
&loc_cond.value, &loc_cond.value,
expect_bool.clone(), expect_bool(loc_cond.region),
); );
let then_con = constrain_expr( let then_con = constrain_expr(

View file

@ -1,10 +1,12 @@
use crate::report::ReportText::{Batch, Module, Region, Value}; use crate::report::ReportText::{Batch, 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_solve::solve;
use roc_types::pretty_print::content_to_string; use roc_types::pretty_print::content_to_string;
use roc_types::subs::{Content, Subs}; use roc_types::subs::{Content, Subs, Variable};
use roc_types::types::{write_error_type, Category, ErrorType, PReason, PatternCategory, Reason};
use std::path::PathBuf; use std::path::PathBuf;
/// A textual report. /// A textual report.
@ -85,8 +87,7 @@ pub fn type_problem(filename: PathBuf, problem: solve::TypeError) -> Report {
match problem { match problem {
BadExpr(region, category, found, expected) => { BadExpr(region, category, found, expected) => {
dbg!(region, category, found, expected); return to_expr_report(filename, region, category, found, expected);
todo!()
} }
BadPattern(region, category, found, expected) => todo!(), BadPattern(region, category, found, expected) => todo!(),
CircularType(region, symbol, circ_type) => todo!(), CircularType(region, symbol, circ_type) => todo!(),
@ -98,6 +99,67 @@ pub fn type_problem(filename: PathBuf, problem: solve::TypeError) -> Report {
} }
} }
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) => {
let bad_type = |op_highlight, problem, this_is, further_details| {
let mut lines = vec![
plain_text(problem),
Region(region),
add_category(this_is, category),
newline(),
newline(),
plain_text(" "),
ErrorType(found),
newline(),
newline(),
further_details,
];
Report {
filename,
text: Batch(lines),
}
};
match reason {
Reason::IfCondition => bad_type(
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(),
]),
),
_ => 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)
}
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();
@ -180,6 +242,7 @@ pub enum ReportText {
/// A type. Render it using roc_types::pretty_print for now, but maybe /// A type. Render it using roc_types::pretty_print for now, but maybe
/// do something fancier later. /// do something fancier later.
Type(Content), Type(Content),
ErrorType(ErrorType),
/// Plain text /// Plain text
Plain(Box<str>), Plain(Box<str>),
@ -322,10 +385,12 @@ impl ReportText {
buf.push_str(&interns.module_name(module_id)); buf.push_str(&interns.module_name(module_id));
} }
Type(content) => buf.push_str(content_to_string(content, subs, home, interns).as_str()), 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();
@ -460,6 +525,7 @@ impl ReportText {
), ),
Content::Error => {} Content::Error => {}
}, },
ErrorType(error_type) => buf.push_str(&write_error_type(home, interns, error_type)),
Region(region) => { Region(region) => {
// newline before snippet // newline before snippet
buf.push('\n'); buf.push('\n');

View file

@ -761,16 +761,21 @@ mod test_reporting {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
if 1 then 2 else 3 if "foo" then 2 else 3
"# "#
), ),
indoc!( indoc!(
r#" r#"
You cannot mix (!=) and (==) without parentheses This `if` condition does not evaluate to a boolean value, True or False.
3 if selectedId != thisId == adminsId then 1 if "foo" then 2 else 3
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^
It is a string of type:
Str
But I need this `if` condition to be a Bool value.
"# "#
), ),
) )

View file

@ -24,7 +24,7 @@ static EMPTY_TAG_UNION: &str = "[]";
/// ///
/// Otherwise, parens are unnecessary. /// Otherwise, parens are unnecessary.
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
enum Parens { pub enum Parens {
InFn, InFn,
InTypeParam, InTypeParam,
Unnecessary, Unnecessary,

View file

@ -1,9 +1,10 @@
use crate::boolean_algebra; use crate::boolean_algebra;
use crate::pretty_print::Parens;
use crate::subs::{Subs, VarStore, Variable}; use crate::subs::{Subs, VarStore, Variable};
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
use roc_collections::all::{union, ImMap, ImSet, MutMap, MutSet, SendMap}; use roc_collections::all::{union, ImMap, ImSet, MutMap, MutSet, SendMap};
use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_parse::operator::{ArgSide, BinOp}; use roc_parse::operator::{ArgSide, BinOp};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use std::fmt; use std::fmt;
@ -724,6 +725,40 @@ impl ErrorType {
} }
} }
pub fn write_error_type(home: ModuleId, interns: &Interns, error_type: ErrorType) -> String {
let mut buf = String::new();
write_error_type_help(home, interns, error_type, &mut buf, Parens::Unnecessary);
buf
}
fn write_error_type_help(
home: ModuleId,
interns: &Interns,
error_type: ErrorType,
buf: &mut String,
parens: Parens,
) {
use ErrorType::*;
match error_type {
Infinite => buf.push_str(""),
Error => buf.push_str("?"),
FlexVar(name) => buf.push_str(name.as_str()),
RigidVar(name) => buf.push_str(name.as_str()),
Type(symbol, arguments) => {
buf.push_str(symbol.ident_string(interns));
for arg in arguments {
buf.push(' ');
write_error_type_help(home, interns, arg, buf, Parens::InTypeParam);
}
}
_ => todo!(),
}
}
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone)]
pub enum TypeExt { pub enum TypeExt {
Closed, Closed,