mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 22:34:45 +00:00
pretty error messages for floats
This commit is contained in:
parent
9d67b11c0d
commit
edb79b8fce
5 changed files with 163 additions and 23 deletions
|
@ -1,13 +1,12 @@
|
||||||
use crate::env::Env;
|
use crate::env::Env;
|
||||||
use crate::expr::Expr;
|
use crate::expr::Expr;
|
||||||
use roc_parse::ast::Base;
|
use roc_parse::ast::Base;
|
||||||
use roc_problem::can::IntErrorKind;
|
|
||||||
use roc_problem::can::Problem;
|
use roc_problem::can::Problem;
|
||||||
use roc_problem::can::RuntimeError::*;
|
use roc_problem::can::RuntimeError::*;
|
||||||
|
use roc_problem::can::{FloatErrorKind, IntErrorKind};
|
||||||
use roc_region::all::Region;
|
use roc_region::all::Region;
|
||||||
use roc_types::subs::VarStore;
|
use roc_types::subs::VarStore;
|
||||||
use std::i64;
|
use std::i64;
|
||||||
use std::num::ParseFloatError;
|
|
||||||
|
|
||||||
// TODO distinguish number parsing failures
|
// TODO distinguish number parsing failures
|
||||||
//
|
//
|
||||||
|
@ -17,7 +16,7 @@ use std::num::ParseFloatError;
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn num_expr_from_result(
|
pub fn num_expr_from_result(
|
||||||
var_store: &mut VarStore,
|
var_store: &mut VarStore,
|
||||||
result: Result<i64, (&str, ParseIntError)>,
|
result: Result<i64, (&str, IntErrorKind)>,
|
||||||
region: Region,
|
region: Region,
|
||||||
env: &mut Env,
|
env: &mut Env,
|
||||||
) -> Expr {
|
) -> Expr {
|
||||||
|
@ -27,7 +26,7 @@ pub fn num_expr_from_result(
|
||||||
// (Num *) compiles to Int if it doesn't
|
// (Num *) compiles to Int if it doesn't
|
||||||
// get specialized to something else first,
|
// get specialized to something else first,
|
||||||
// so use int's overflow bounds here.
|
// so use int's overflow bounds here.
|
||||||
let runtime_error = InvalidInt(error.kind, Base::Decimal, region, raw.into());
|
let runtime_error = InvalidInt(error, Base::Decimal, region, raw.into());
|
||||||
|
|
||||||
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
||||||
|
|
||||||
|
@ -39,7 +38,7 @@ pub fn num_expr_from_result(
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn int_expr_from_result(
|
pub fn int_expr_from_result(
|
||||||
var_store: &mut VarStore,
|
var_store: &mut VarStore,
|
||||||
result: Result<i64, (&str, ParseIntError)>,
|
result: Result<i64, (&str, IntErrorKind)>,
|
||||||
region: Region,
|
region: Region,
|
||||||
base: Base,
|
base: Base,
|
||||||
env: &mut Env,
|
env: &mut Env,
|
||||||
|
@ -48,7 +47,7 @@ pub fn int_expr_from_result(
|
||||||
match result {
|
match result {
|
||||||
Ok(int) => Expr::Int(var_store.fresh(), int),
|
Ok(int) => Expr::Int(var_store.fresh(), int),
|
||||||
Err((raw, error)) => {
|
Err((raw, error)) => {
|
||||||
let runtime_error = InvalidInt(error.kind, base, region, raw.into());
|
let runtime_error = InvalidInt(error, base, region, raw.into());
|
||||||
|
|
||||||
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
||||||
|
|
||||||
|
@ -60,15 +59,15 @@ pub fn int_expr_from_result(
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn float_expr_from_result(
|
pub fn float_expr_from_result(
|
||||||
var_store: &mut VarStore,
|
var_store: &mut VarStore,
|
||||||
result: Result<f64, (&str, ParseFloatError)>,
|
result: Result<f64, (&str, FloatErrorKind)>,
|
||||||
region: Region,
|
region: Region,
|
||||||
env: &mut Env,
|
env: &mut Env,
|
||||||
) -> Expr {
|
) -> Expr {
|
||||||
// Float stores a variable to generate better error messages
|
// Float stores a variable to generate better error messages
|
||||||
match result {
|
match result {
|
||||||
Ok(float) => Expr::Float(var_store.fresh(), float),
|
Ok(float) => Expr::Float(var_store.fresh(), float),
|
||||||
Err((raw, _error)) => {
|
Err((raw, error)) => {
|
||||||
let runtime_error = FloatOutsideRange(raw.into(), region);
|
let runtime_error = InvalidFloat(error, region, raw.into());
|
||||||
|
|
||||||
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
||||||
|
|
||||||
|
@ -78,10 +77,10 @@ pub fn float_expr_from_result(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn finish_parsing_int(raw: &str) -> Result<i64, (&str, ParseIntError)> {
|
pub fn finish_parsing_int(raw: &str) -> Result<i64, (&str, IntErrorKind)> {
|
||||||
// Ignore underscores.
|
// Ignore underscores.
|
||||||
let radix = 10;
|
let radix = 10;
|
||||||
from_str_radix::<i64>(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e))
|
from_str_radix::<i64>(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e.kind))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -89,7 +88,7 @@ pub fn finish_parsing_base(
|
||||||
raw: &str,
|
raw: &str,
|
||||||
base: Base,
|
base: Base,
|
||||||
is_negative: bool,
|
is_negative: bool,
|
||||||
) -> Result<i64, (&str, ParseIntError)> {
|
) -> Result<i64, (&str, IntErrorKind)> {
|
||||||
let radix = match base {
|
let radix = match base {
|
||||||
Base::Hex => 16,
|
Base::Hex => 16,
|
||||||
Base::Decimal => 10,
|
Base::Decimal => 10,
|
||||||
|
@ -103,16 +102,22 @@ pub fn finish_parsing_base(
|
||||||
} else {
|
} else {
|
||||||
from_str_radix::<i64>(raw.replace("_", "").as_str(), radix)
|
from_str_radix::<i64>(raw.replace("_", "").as_str(), radix)
|
||||||
})
|
})
|
||||||
.map_err(|e| (raw, e))
|
.map_err(|e| (raw, e.kind))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn finish_parsing_float(raw: &str) -> Result<f64, (&str, ParseFloatError)> {
|
pub fn finish_parsing_float(raw: &str) -> Result<f64, (&str, FloatErrorKind)> {
|
||||||
// Ignore underscores.
|
// Ignore underscores.
|
||||||
match raw.replace("_", "").parse::<f64>() {
|
match raw.replace("_", "").parse::<f64>() {
|
||||||
Ok(float) if float.is_finite() => Ok(float),
|
Ok(float) if float.is_finite() => Ok(float),
|
||||||
Ok(_float) => panic!("TODO handle infinite float literal"),
|
Ok(float) => {
|
||||||
Err(e) => Err((raw, e)),
|
if float.is_sign_positive() {
|
||||||
|
Err((raw, FloatErrorKind::PositiveInfinity))
|
||||||
|
} else {
|
||||||
|
Err((raw, FloatErrorKind::NegativeInfinity))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => Err((raw, FloatErrorKind::Error)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +155,9 @@ macro_rules! doit {
|
||||||
}
|
}
|
||||||
})*)
|
})*)
|
||||||
}
|
}
|
||||||
doit! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize }
|
// We only need the i64 implementation, but libcore defines
|
||||||
|
// doit! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize }
|
||||||
|
doit! { i64 }
|
||||||
|
|
||||||
fn from_str_radix<T: FromStrRadixHelper>(src: &str, radix: u32) -> Result<T, ParseIntError> {
|
fn from_str_radix<T: FromStrRadixHelper>(src: &str, radix: u32) -> Result<T, ParseIntError> {
|
||||||
use self::IntErrorKind::*;
|
use self::IntErrorKind::*;
|
||||||
|
|
|
@ -16,7 +16,7 @@ mod test_can {
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use roc_can::expr::Expr::{self, *};
|
use roc_can::expr::Expr::{self, *};
|
||||||
use roc_can::expr::Recursive;
|
use roc_can::expr::Recursive;
|
||||||
use roc_problem::can::{IntErrorKind, Problem, RuntimeError};
|
use roc_problem::can::{FloatErrorKind, IntErrorKind, Problem, RuntimeError};
|
||||||
use roc_region::all::Region;
|
use roc_region::all::Region;
|
||||||
use std::{f64, i64};
|
use std::{f64, i64};
|
||||||
|
|
||||||
|
@ -112,7 +112,11 @@ mod test_can {
|
||||||
|
|
||||||
assert_can(
|
assert_can(
|
||||||
&string.clone(),
|
&string.clone(),
|
||||||
RuntimeError(RuntimeError::FloatOutsideRange(string.into(), region)),
|
RuntimeError(RuntimeError::InvalidFloat(
|
||||||
|
FloatErrorKind::PositiveInfinity,
|
||||||
|
region,
|
||||||
|
string.into(),
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +127,11 @@ mod test_can {
|
||||||
|
|
||||||
assert_can(
|
assert_can(
|
||||||
&string.clone(),
|
&string.clone(),
|
||||||
RuntimeError(RuntimeError::FloatOutsideRange(string.into(), region)),
|
RuntimeError(RuntimeError::InvalidFloat(
|
||||||
|
FloatErrorKind::NegativeInfinity,
|
||||||
|
region,
|
||||||
|
string.into(),
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,17 @@ pub enum IntErrorKind {
|
||||||
Underflow,
|
Underflow,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enum to store the various types of errors that can cause parsing a float to fail.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum FloatErrorKind {
|
||||||
|
/// Probably an invalid digit
|
||||||
|
Error,
|
||||||
|
/// the literal is too small for f64
|
||||||
|
NegativeInfinity,
|
||||||
|
/// the literal is too large for f64
|
||||||
|
PositiveInfinity,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum RuntimeError {
|
pub enum RuntimeError {
|
||||||
Shadowing {
|
Shadowing {
|
||||||
|
@ -104,7 +115,7 @@ pub enum RuntimeError {
|
||||||
InvalidPrecedence(PrecedenceProblem, Region),
|
InvalidPrecedence(PrecedenceProblem, Region),
|
||||||
MalformedIdentifier(Box<str>, Region),
|
MalformedIdentifier(Box<str>, Region),
|
||||||
MalformedClosure(Region),
|
MalformedClosure(Region),
|
||||||
FloatOutsideRange(Box<str>, Region),
|
InvalidFloat(FloatErrorKind, Region, Box<str>),
|
||||||
InvalidInt(IntErrorKind, Base, Region, Box<str>),
|
InvalidInt(IntErrorKind, Base, Region, Box<str>),
|
||||||
CircularDef(Vec<Symbol>, Vec<(Region /* pattern */, Region /* expr */)>),
|
CircularDef(Vec<Symbol>, Vec<(Region /* pattern */, Region /* expr */)>),
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use roc_collections::all::MutSet;
|
use roc_collections::all::MutSet;
|
||||||
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
|
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
|
||||||
use roc_problem::can::{IntErrorKind, Problem, RuntimeError};
|
use roc_problem::can::{FloatErrorKind, IntErrorKind, Problem, RuntimeError};
|
||||||
use roc_region::all::Region;
|
use roc_region::all::Region;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
@ -386,7 +386,50 @@ fn pretty_runtime_error<'b>(
|
||||||
todo!("malformed identifier, currently gives a parse error and thus is unreachable")
|
todo!("malformed identifier, currently gives a parse error and thus is unreachable")
|
||||||
}
|
}
|
||||||
RuntimeError::MalformedClosure(_) => todo!(""),
|
RuntimeError::MalformedClosure(_) => todo!(""),
|
||||||
RuntimeError::FloatOutsideRange(_raw_str, _region) => todo!(""),
|
RuntimeError::InvalidFloat(sign @ FloatErrorKind::PositiveInfinity, region, _raw_str)
|
||||||
|
| RuntimeError::InvalidFloat(sign @ FloatErrorKind::NegativeInfinity, region, _raw_str) => {
|
||||||
|
let hint = alloc
|
||||||
|
.hint()
|
||||||
|
.append(alloc.reflow("Learn more about number literals at TODO"));
|
||||||
|
|
||||||
|
let big_or_small = if let FloatErrorKind::PositiveInfinity = sign {
|
||||||
|
"big"
|
||||||
|
} else {
|
||||||
|
"small"
|
||||||
|
};
|
||||||
|
|
||||||
|
alloc.stack(vec![
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("This float literal is too "),
|
||||||
|
alloc.text(big_or_small),
|
||||||
|
alloc.reflow(":"),
|
||||||
|
]),
|
||||||
|
alloc.region(region),
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("Roc uses signed 64-bit floating points, allowing values between"),
|
||||||
|
alloc.text(format!("{:e}", f64::MIN)),
|
||||||
|
alloc.reflow(" and "),
|
||||||
|
alloc.text(format!("{:e}", f64::MAX)),
|
||||||
|
]),
|
||||||
|
hint,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
RuntimeError::InvalidFloat(FloatErrorKind::Error, region, _raw_str) => {
|
||||||
|
let hint = alloc
|
||||||
|
.hint()
|
||||||
|
.append(alloc.reflow("Learn more about number literals at TODO"));
|
||||||
|
|
||||||
|
alloc.stack(vec![
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("This float literal contains an invalid digit:"),
|
||||||
|
]),
|
||||||
|
alloc.region(region),
|
||||||
|
alloc.concat(vec![
|
||||||
|
alloc.reflow("Floating point literals can only contain the digits 0-9, or use scientific notation 10e4"),
|
||||||
|
]),
|
||||||
|
hint,
|
||||||
|
])
|
||||||
|
}
|
||||||
RuntimeError::InvalidInt(IntErrorKind::Empty, _base, _region, _raw_str) => {
|
RuntimeError::InvalidInt(IntErrorKind::Empty, _base, _region, _raw_str) => {
|
||||||
unreachable!("would never parse an empty int literal")
|
unreachable!("would never parse an empty int literal")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3181,6 +3181,49 @@ mod test_reporting {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn float_out_of_range() {
|
||||||
|
report_problem_as(
|
||||||
|
&format!(
|
||||||
|
r#"
|
||||||
|
overflow = 1{:e}
|
||||||
|
underflow = -1{:e}
|
||||||
|
|
||||||
|
overflow + underflow
|
||||||
|
"#,
|
||||||
|
f64::MAX,
|
||||||
|
f64::MAX,
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This float literal is too small:
|
||||||
|
|
||||||
|
3 ┆ underflow = -11.7976931348623157e308
|
||||||
|
┆ ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Roc uses signed 64-bit floating points, allowing values
|
||||||
|
between-1.7976931348623157e308 and 1.7976931348623157e308
|
||||||
|
|
||||||
|
Hint: Learn more about number literals at TODO
|
||||||
|
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This float literal is too big:
|
||||||
|
|
||||||
|
2 ┆ overflow = 11.7976931348623157e308
|
||||||
|
┆ ^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Roc uses signed 64-bit floating points, allowing values
|
||||||
|
between-1.7976931348623157e308 and 1.7976931348623157e308
|
||||||
|
|
||||||
|
Hint: Learn more about number literals at TODO
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn integer_malformed() {
|
fn integer_malformed() {
|
||||||
// the generated messages here are incorrect. Waiting for a rust nightly feature to land,
|
// the generated messages here are incorrect. Waiting for a rust nightly feature to land,
|
||||||
|
@ -3250,4 +3293,32 @@ mod test_reporting {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn float_malformed() {
|
||||||
|
report_problem_as(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
x = 3.0A
|
||||||
|
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
-- SYNTAX PROBLEM --------------------------------------------------------------
|
||||||
|
|
||||||
|
This float literal contains an invalid digit:
|
||||||
|
|
||||||
|
1 ┆ x = 3.0A
|
||||||
|
┆ ^^^^
|
||||||
|
|
||||||
|
Floating point literals can only contain the digits 0-9, or use
|
||||||
|
scientific notation 10e4
|
||||||
|
|
||||||
|
Hint: Learn more about number literals at TODO
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue