mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 14:54:47 +00:00
report first type error
This commit is contained in:
parent
106a3646bf
commit
8642cfeae0
5 changed files with 124 additions and 15 deletions
|
@ -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(
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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.
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue