diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index 5c2cb791fc..9a0b810a6f 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -121,8 +121,13 @@ pub fn finish_parsing_float(raw: &str) -> Result { } } -/// Integer parsing code taken from the rust stdlib, +/// Integer parsing code taken from the rust libcore, /// pulled in so we can give custom error messages +/// +/// The Rust Project is dual-licensed under Apache 2.0 and MIT terms. +/// As roc is Apache licensed, we use this rust code under the apache 2.0 license +/// +/// Thanks to the rust-lang project and its contributors trait FromStrRadixHelper: PartialOrd + Copy { fn min_value() -> Self; diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index 9f105ac114..ae92b3361e 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -68,6 +68,7 @@ pub enum IntErrorKind { /// Value being parsed is empty. /// /// Among other causes, this variant will be constructed when parsing an empty string. + /// In roc, this can happen with non-base-10 literals, e.g. `0x` or `0b` without any digits Empty, /// Contains an invalid digit. /// diff --git a/compiler/reporting/src/error/canonicalize.rs b/compiler/reporting/src/error/canonicalize.rs index 413f7fd87f..244e5bb04d 100644 --- a/compiler/reporting/src/error/canonicalize.rs +++ b/compiler/reporting/src/error/canonicalize.rs @@ -430,12 +430,22 @@ fn pretty_runtime_error<'b>( hint, ]) } - RuntimeError::InvalidInt(IntErrorKind::Empty, _base, _region, _raw_str) => { - unreachable!("would never parse an empty int literal") - } - RuntimeError::InvalidInt(IntErrorKind::InvalidDigit, base, region, _raw_str) => { + RuntimeError::InvalidInt(error @ IntErrorKind::InvalidDigit, base, region, _raw_str) + | RuntimeError::InvalidInt(error @ IntErrorKind::Empty, base, region, _raw_str) => { use roc_parse::ast::Base::*; + let (problem, contains) = if let IntErrorKind::InvalidDigit = error { + ( + "an invalid digit", + alloc.reflow(" can only contain the digits "), + ) + } else { + ( + "no digits", + alloc.reflow(" must contain at least one of the digits "), + ) + }; + let name = match base { Decimal => "integer", Octal => "octal integer", @@ -465,12 +475,14 @@ fn pretty_runtime_error<'b>( alloc.concat(vec![ alloc.reflow("This "), alloc.text(name), - alloc.reflow(" literal contains an invalid digit:"), + alloc.reflow(" literal contains "), + alloc.text(problem), + alloc.text(":"), ]), alloc.region(region), alloc.concat(vec![ alloc.text(plurals), - alloc.reflow(" can only contain the digits "), + contains, alloc.text(charset), alloc.text("."), ]), diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index 5c958b6933..d654549a9d 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -3310,6 +3310,64 @@ mod test_reporting { ) } + #[test] + fn integer_empty() { + report_problem_as( + indoc!( + r#" + dec = 20 + + hex = 0x + + oct = 0o + + bin = 0b + + dec + hex + oct + bin + "# + ), + indoc!( + r#" + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This hex integer literal contains no digits: + + 3 ┆ hex = 0x + ┆ ^^ + + Hexadecimal (base-16) integer literals must contain at least one of + the digits 0-9, a-f and A-F. + + Hint: Learn more about number literals at TODO + + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This octal integer literal contains no digits: + + 5 ┆ oct = 0o + ┆ ^^ + + Octal (base-8) integer literals must contain at least one of the + digits 0-7. + + Hint: Learn more about number literals at TODO + + -- SYNTAX PROBLEM -------------------------------------------------------------- + + This binary integer literal contains no digits: + + 7 ┆ bin = 0b + ┆ ^^ + + Binary (base-2) integer literals must contain at least one of the + digits 0 and 1. + + Hint: Learn more about number literals at TODO + "# + ), + ) + } + #[test] fn float_malformed() { report_problem_as(