Merge pull request #2442 from rtfeldman/i/2332

Check lower bounds for numeric literals, and permit 128-bit literals
This commit is contained in:
Folkert de Vries 2022-02-03 19:50:03 +01:00 committed by GitHub
commit 860c5eb0ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 931 additions and 223 deletions

View file

@ -1,5 +1,5 @@
use bumpalo::Bump; use bumpalo::Bump;
use roc_can::expr::Recursive; use roc_can::expr::{IntValue, Recursive};
use roc_can::num::{ use roc_can::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult, finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult,
}; };
@ -78,7 +78,10 @@ pub fn expr_to_expr2<'a>(
match finish_parsing_num(string) { match finish_parsing_num(string) {
Ok(ParsedNumResult::UnknownNum(int) | ParsedNumResult::Int(int, _)) => { Ok(ParsedNumResult::UnknownNum(int) | ParsedNumResult::Int(int, _)) => {
let expr = Expr2::SmallInt { let expr = Expr2::SmallInt {
number: IntVal::I64(int), number: IntVal::I64(match int {
IntValue::U128(_) => todo!(),
IntValue::I128(n) => n as i64, // FIXME
}),
var: env.var_store.fresh(), var: env.var_store.fresh(),
// TODO non-hardcode // TODO non-hardcode
style: IntStyle::Decimal, style: IntStyle::Decimal,
@ -120,7 +123,10 @@ pub fn expr_to_expr2<'a>(
match finish_parsing_base(string, *base, *is_negative) { match finish_parsing_base(string, *base, *is_negative) {
Ok((int, _bound)) => { Ok((int, _bound)) => {
let expr = Expr2::SmallInt { let expr = Expr2::SmallInt {
number: IntVal::I64(int), number: IntVal::I64(match int {
IntValue::U128(_) => todo!(),
IntValue::I128(n) => n as i64, // FIXME
}),
var: env.var_store.fresh(), var: env.var_store.fresh(),
// TODO non-hardcode // TODO non-hardcode
style: IntStyle::from_base(*base), style: IntStyle::from_base(*base),

View file

@ -3,7 +3,7 @@
#![allow(unused_imports)] #![allow(unused_imports)]
use bumpalo::collections::Vec as BumpVec; use bumpalo::collections::Vec as BumpVec;
use roc_can::expr::unescape_char; use roc_can::expr::{unescape_char, IntValue};
use roc_can::num::{ use roc_can::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult, finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult,
}; };
@ -197,9 +197,20 @@ pub fn to_pattern2<'a>(
malformed_pattern(env, problem, region) malformed_pattern(env, problem, region)
} }
Ok(ParsedNumResult::UnknownNum(int)) => { Ok(ParsedNumResult::UnknownNum(int)) => {
Pattern2::NumLiteral(env.var_store.fresh(), int) Pattern2::NumLiteral(
env.var_store.fresh(),
match int {
IntValue::U128(_) => todo!(),
IntValue::I128(n) => n as i64, // FIXME
},
)
}
Ok(ParsedNumResult::Int(int, _bound)) => {
Pattern2::IntLiteral(IntVal::I64(match int {
IntValue::U128(_) => todo!(),
IntValue::I128(n) => n as i64, // FIXME
}))
} }
Ok(ParsedNumResult::Int(int, _bound)) => Pattern2::IntLiteral(IntVal::I64(int)),
Ok(ParsedNumResult::Float(int, _bound)) => { Ok(ParsedNumResult::Float(int, _bound)) => {
Pattern2::FloatLiteral(FloatVal::F64(int)) Pattern2::FloatLiteral(FloatVal::F64(int))
} }
@ -218,6 +229,10 @@ pub fn to_pattern2<'a>(
malformed_pattern(env, problem, region) malformed_pattern(env, problem, region)
} }
Ok((int, _bound)) => { Ok((int, _bound)) => {
let int = match int {
IntValue::U128(_) => todo!(),
IntValue::I128(n) => n as i64, // FIXME
};
if *is_negative { if *is_negative {
Pattern2::IntLiteral(IntVal::I64(-int)) Pattern2::IntLiteral(IntVal::I64(-int))
} else { } else {

View file

@ -1,5 +1,5 @@
use crate::def::Def; use crate::def::Def;
use crate::expr::{self, ClosureData, Expr::*}; use crate::expr::{self, ClosureData, Expr::*, IntValue};
use crate::expr::{Expr, Field, Recursive}; use crate::expr::{Expr, Field, Recursive};
use crate::num::{FloatWidth, IntWidth, NumWidth, NumericBound}; use crate::num::{FloatWidth, IntWidth, NumWidth, NumericBound};
use crate::pattern::Pattern; use crate::pattern::Pattern;
@ -5197,7 +5197,7 @@ where
num_var, num_var,
precision_var, precision_var,
ii.to_string().into_boxed_str(), ii.to_string().into_boxed_str(),
ii, IntValue::I128(ii),
bound, bound,
) )
} }
@ -5219,6 +5219,12 @@ fn float(
} }
#[inline(always)] #[inline(always)]
fn num(num_var: Variable, i: i64, bound: NumericBound<NumWidth>) -> Expr { fn num<I: Into<i128>>(num_var: Variable, i: I, bound: NumericBound<NumWidth>) -> Expr {
Num(num_var, i.to_string().into_boxed_str(), i, bound) let i = i.into();
Num(
num_var,
i.to_string().into_boxed_str(),
IntValue::I128(i),
bound,
)
} }

View file

@ -20,7 +20,7 @@ use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use roc_types::types::Alias; use roc_types::types::Alias;
use std::fmt::Debug; use std::fmt::{Debug, Display};
use std::{char, u32}; use std::{char, u32};
#[derive(Clone, Default, Debug, PartialEq)] #[derive(Clone, Default, Debug, PartialEq)]
@ -46,16 +46,37 @@ impl Output {
} }
} }
#[derive(Clone, Debug, PartialEq)]
pub enum IntValue {
I128(i128),
U128(u128),
}
impl Display for IntValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IntValue::I128(n) => Display::fmt(&n, f),
IntValue::U128(n) => Display::fmt(&n, f),
}
}
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Expr { pub enum Expr {
// Literals // Literals
// Num stores the `a` variable in `Num a`. Not the same as the variable // Num stores the `a` variable in `Num a`. Not the same as the variable
// stored in Int and Float below, which is strictly for better error messages // stored in Int and Float below, which is strictly for better error messages
Num(Variable, Box<str>, i64, NumericBound<NumWidth>), Num(Variable, Box<str>, IntValue, NumericBound<NumWidth>),
// Int and Float store a variable to generate better error messages // Int and Float store a variable to generate better error messages
Int(Variable, Variable, Box<str>, i128, NumericBound<IntWidth>), Int(
Variable,
Variable,
Box<str>,
IntValue,
NumericBound<IntWidth>,
),
Float(Variable, Variable, Box<str>, f64, NumericBound<FloatWidth>), Float(Variable, Variable, Box<str>, f64, NumericBound<FloatWidth>),
Str(Box<str>), Str(Box<str>),
List { List {
@ -802,13 +823,7 @@ pub fn canonicalize_expr<'a>(
// to keep borrowed values around and make this compile // to keep borrowed values around and make this compile
let int_string = int.to_string(); let int_string = int.to_string();
let int_str = int_string.as_str(); let int_str = int_string.as_str();
int_expr_from_result( int_expr_from_result(var_store, Ok((int_str, int, bound)), region, base, env)
var_store,
Ok((int_str, int as i128, bound)),
region,
base,
env,
)
} }
Err(e) => int_expr_from_result(var_store, Err(e), region, base, env), Err(e) => int_expr_from_result(var_store, Err(e), region, base, env),
}; };

View file

@ -1,5 +1,5 @@
use crate::env::Env; use crate::env::Env;
use crate::expr::Expr; use crate::expr::{Expr, IntValue};
use roc_parse::ast::Base; use roc_parse::ast::Base;
use roc_problem::can::Problem; use roc_problem::can::Problem;
use roc_problem::can::RuntimeError::*; use roc_problem::can::RuntimeError::*;
@ -9,11 +9,6 @@ use roc_types::subs::VarStore;
use std::i64; use std::i64;
use std::str; use std::str;
// TODO use rust's integer parsing again
//
// We're waiting for libcore here, see https://github.com/rust-lang/rust/issues/22639
// There is a nightly API for exposing the parse error.
#[inline(always)] #[inline(always)]
pub fn num_expr_from_result( pub fn num_expr_from_result(
var_store: &mut VarStore, var_store: &mut VarStore,
@ -29,7 +24,7 @@ pub fn num_expr_from_result(
var_store.fresh(), var_store.fresh(),
var_store.fresh(), var_store.fresh(),
(*str).into(), (*str).into(),
num.into(), num,
bound, bound,
), ),
Ok((str, ParsedNumResult::Float(num, bound))) => Expr::Float( Ok((str, ParsedNumResult::Float(num, bound))) => Expr::Float(
@ -55,7 +50,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<(&str, i128, NumericBound<IntWidth>), (&str, IntErrorKind)>, result: Result<(&str, IntValue, NumericBound<IntWidth>), (&str, IntErrorKind)>,
region: Region, region: Region,
base: Base, base: Base,
env: &mut Env, env: &mut Env,
@ -106,9 +101,9 @@ pub fn float_expr_from_result(
} }
pub enum ParsedNumResult { pub enum ParsedNumResult {
Int(i64, NumericBound<IntWidth>), Int(IntValue, NumericBound<IntWidth>),
Float(f64, NumericBound<FloatWidth>), Float(f64, NumericBound<FloatWidth>),
UnknownNum(i64), UnknownNum(IntValue),
} }
#[inline(always)] #[inline(always)]
@ -116,7 +111,7 @@ pub fn finish_parsing_num(raw: &str) -> Result<ParsedNumResult, (&str, IntErrorK
// Ignore underscores. // Ignore underscores.
let radix = 10; let radix = 10;
let (num, bound) = let (num, bound) =
from_str_radix::<i64>(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e.kind))?; from_str_radix(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e))?;
// Let's try to specialize the number // Let's try to specialize the number
Ok(match bound { Ok(match bound {
NumericBound::None => ParsedNumResult::UnknownNum(num), NumericBound::None => ParsedNumResult::UnknownNum(num),
@ -124,7 +119,11 @@ pub fn finish_parsing_num(raw: &str) -> Result<ParsedNumResult, (&str, IntErrorK
ParsedNumResult::Int(num, NumericBound::Exact(iw)) ParsedNumResult::Int(num, NumericBound::Exact(iw))
} }
NumericBound::Exact(NumWidth::Float(fw)) => { NumericBound::Exact(NumWidth::Float(fw)) => {
ParsedNumResult::Float(num as f64, NumericBound::Exact(fw)) let num = match num {
IntValue::I128(n) => n as f64,
IntValue::U128(n) => n as f64,
};
ParsedNumResult::Float(num, NumericBound::Exact(fw))
} }
}) })
} }
@ -134,7 +133,7 @@ pub fn finish_parsing_base(
raw: &str, raw: &str,
base: Base, base: Base,
is_negative: bool, is_negative: bool,
) -> Result<(i64, NumericBound<IntWidth>), (&str, IntErrorKind)> { ) -> Result<(IntValue, NumericBound<IntWidth>), (&str, IntErrorKind)> {
let radix = match base { let radix = match base {
Base::Hex => 16, Base::Hex => 16,
Base::Decimal => 10, Base::Decimal => 10,
@ -144,11 +143,10 @@ pub fn finish_parsing_base(
// Ignore underscores, insert - when negative to get correct underflow/overflow behavior // Ignore underscores, insert - when negative to get correct underflow/overflow behavior
(if is_negative { (if is_negative {
from_str_radix::<i64>(format!("-{}", raw.replace("_", "")).as_str(), radix) from_str_radix(format!("-{}", raw.replace("_", "")).as_str(), radix)
} else { } else {
from_str_radix::<i64>(raw.replace("_", "").as_str(), radix) from_str_radix(raw.replace("_", "").as_str(), radix)
}) })
.map_err(|e| e.kind)
.and_then(|(n, bound)| { .and_then(|(n, bound)| {
let bound = match bound { let bound = match bound {
NumericBound::None => NumericBound::None, NumericBound::None => NumericBound::None,
@ -164,15 +162,12 @@ pub fn finish_parsing_base(
pub fn finish_parsing_float( pub fn finish_parsing_float(
raw: &str, raw: &str,
) -> Result<(f64, NumericBound<FloatWidth>), (&str, FloatErrorKind)> { ) -> Result<(f64, NumericBound<FloatWidth>), (&str, FloatErrorKind)> {
let (bound, raw_without_suffix) = parse_literal_suffix(raw.as_bytes()); let (opt_bound, raw_without_suffix) = parse_literal_suffix(raw);
// Safety: `raw` is valid UTF8, and `parse_literal_suffix` will only chop UTF8
// characters off the end, if it chops off anything at all.
let raw_without_suffix = unsafe { str::from_utf8_unchecked(raw_without_suffix) };
let bound = match bound { let bound = match opt_bound {
NumericBound::None => NumericBound::None, None => NumericBound::None,
NumericBound::Exact(NumWidth::Float(fw)) => NumericBound::Exact(fw), Some(NumWidth::Float(fw)) => NumericBound::Exact(fw),
NumericBound::Exact(NumWidth::Int(_)) => return Err((raw, FloatErrorKind::IntSuffix)), Some(NumWidth::Int(_)) => return Err((raw, FloatErrorKind::IntSuffix)),
}; };
// Ignore underscores. // Ignore underscores.
@ -189,33 +184,33 @@ pub fn finish_parsing_float(
} }
} }
fn parse_literal_suffix(num_str: &[u8]) -> (NumericBound<NumWidth>, &[u8]) { fn parse_literal_suffix(num_str: &str) -> (Option<NumWidth>, &str) {
macro_rules! parse_num_suffix { macro_rules! parse_num_suffix {
($($suffix:expr, $width:expr)*) => {$( ($($suffix:expr, $width:expr)*) => {$(
if num_str.ends_with($suffix) { if num_str.ends_with($suffix) {
return (NumericBound::Exact($width), num_str.get(0..num_str.len() - $suffix.len()).unwrap()); return (Some($width), num_str.get(0..num_str.len() - $suffix.len()).unwrap());
} }
)*} )*}
} }
parse_num_suffix! { parse_num_suffix! {
b"u8", NumWidth::Int(IntWidth::U8) "u8", NumWidth::Int(IntWidth::U8)
b"u16", NumWidth::Int(IntWidth::U16) "u16", NumWidth::Int(IntWidth::U16)
b"u32", NumWidth::Int(IntWidth::U32) "u32", NumWidth::Int(IntWidth::U32)
b"u64", NumWidth::Int(IntWidth::U64) "u64", NumWidth::Int(IntWidth::U64)
b"u128", NumWidth::Int(IntWidth::U128) "u128", NumWidth::Int(IntWidth::U128)
b"i8", NumWidth::Int(IntWidth::I8) "i8", NumWidth::Int(IntWidth::I8)
b"i16", NumWidth::Int(IntWidth::I16) "i16", NumWidth::Int(IntWidth::I16)
b"i32", NumWidth::Int(IntWidth::I32) "i32", NumWidth::Int(IntWidth::I32)
b"i64", NumWidth::Int(IntWidth::I64) "i64", NumWidth::Int(IntWidth::I64)
b"i128", NumWidth::Int(IntWidth::I128) "i128", NumWidth::Int(IntWidth::I128)
b"nat", NumWidth::Int(IntWidth::Nat) "nat", NumWidth::Int(IntWidth::Nat)
b"dec", NumWidth::Float(FloatWidth::Dec) "dec", NumWidth::Float(FloatWidth::Dec)
b"f32", NumWidth::Float(FloatWidth::F32) "f32", NumWidth::Float(FloatWidth::F32)
b"f64", NumWidth::Float(FloatWidth::F64) "f64", NumWidth::Float(FloatWidth::F64)
} }
(NumericBound::None, num_str) (None, num_str)
} }
/// Integer parsing code taken from the rust libcore, /// Integer parsing code taken from the rust libcore,
@ -226,47 +221,11 @@ fn parse_literal_suffix(num_str: &[u8]) -> (NumericBound<NumWidth>, &[u8]) {
/// the LEGAL_DETAILS file in the root directory of this distribution. /// the LEGAL_DETAILS file in the root directory of this distribution.
/// ///
/// Thanks to the Rust project and its contributors! /// Thanks to the Rust project and its contributors!
trait FromStrRadixHelper: PartialOrd + Copy { fn from_str_radix(
fn min_value() -> Self;
fn max_value() -> Self;
fn from_u32(u: u32) -> Self;
fn checked_mul(&self, other: u32) -> Option<Self>;
fn checked_sub(&self, other: u32) -> Option<Self>;
fn checked_add(&self, other: u32) -> Option<Self>;
}
macro_rules! doit {
($($t:ty)*) => ($(impl FromStrRadixHelper for $t {
#[inline]
fn min_value() -> Self { Self::min_value() }
#[inline]
fn max_value() -> Self { Self::max_value() }
#[inline]
fn from_u32(u: u32) -> Self { u as Self }
#[inline]
fn checked_mul(&self, other: u32) -> Option<Self> {
Self::checked_mul(*self, other as Self)
}
#[inline]
fn checked_sub(&self, other: u32) -> Option<Self> {
Self::checked_sub(*self, other as Self)
}
#[inline]
fn checked_add(&self, other: u32) -> Option<Self> {
Self::checked_add(*self, other as Self)
}
})*)
}
// 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, src: &str,
radix: u32, radix: u32,
) -> Result<(T, NumericBound<NumWidth>), ParseIntError> { ) -> Result<(IntValue, NumericBound<NumWidth>), IntErrorKind> {
use self::IntErrorKind::*; use self::IntErrorKind::*;
use self::ParseIntError as PIE;
assert!( assert!(
(2..=36).contains(&radix), (2..=36).contains(&radix),
@ -274,65 +233,119 @@ fn from_str_radix<T: FromStrRadixHelper>(
radix radix
); );
if src.is_empty() { let (opt_exact_bound, src) = parse_literal_suffix(src);
return Err(PIE { kind: Empty });
}
let is_signed_ty = T::from_u32(0) > T::min_value(); use std::num::IntErrorKind as StdIEK;
let result = match i128::from_str_radix(src, radix) {
// all valid digits are ascii, so we will just iterate over the utf8 bytes Ok(result) => IntValue::I128(result),
// and cast them to chars. .to_digit() will safely return None for anything Err(pie) => match pie.kind() {
// other than a valid ascii digit for the given radix, including the first-byte StdIEK::Empty => return Err(IntErrorKind::Empty),
// of multi-byte sequences StdIEK::InvalidDigit => return Err(IntErrorKind::InvalidDigit),
let src = src.as_bytes(); StdIEK::NegOverflow => return Err(IntErrorKind::Underflow),
StdIEK::PosOverflow => {
let (is_positive, digits) = match src[0] { // try a u128
b'+' => (true, &src[1..]), match u128::from_str_radix(src, radix) {
b'-' if is_signed_ty => (false, &src[1..]), Ok(result) => IntValue::U128(result),
_ => (true, src), Err(pie) => match pie.kind() {
StdIEK::InvalidDigit => return Err(IntErrorKind::InvalidDigit),
StdIEK::PosOverflow => return Err(IntErrorKind::Overflow),
StdIEK::Empty | StdIEK::Zero | StdIEK::NegOverflow => unreachable!(),
_ => unreachable!("I thought all possibilities were exhausted, but std::num added a new one")
},
}
}
StdIEK::Zero => unreachable!("Parsed a i128"),
_ => unreachable!(
"I thought all possibilities were exhausted, but std::num added a new one"
),
},
}; };
let (bound, digits) = parse_literal_suffix(digits); let (lower_bound, is_negative) = match result {
IntValue::I128(num) => (lower_bound_of_int(num), num <= 0),
IntValue::U128(_) => (IntWidth::U128, false),
};
if digits.is_empty() { match opt_exact_bound {
return Err(PIE { kind: Empty }); None => {
// TODO: use the lower bound
Ok((result, NumericBound::None))
}
Some(bound @ NumWidth::Float(_)) => {
// For now, assume floats can represent all integers
// TODO: this is somewhat incorrect, revisit
Ok((result, NumericBound::Exact(bound)))
}
Some(NumWidth::Int(exact_width)) => {
// We need to check if the exact bound >= lower bound.
if exact_width.is_superset(&lower_bound, is_negative) {
// Great! Use the exact bound.
Ok((result, NumericBound::Exact(NumWidth::Int(exact_width))))
} else {
// This is something like 200i8; the lower bound is u8, which holds strictly more
// ints on the positive side than i8 does. Report an error depending on which side
// of the integers we checked.
let err = if is_negative {
UnderflowsSuffix {
suffix_type: exact_width.type_str(),
min_value: exact_width.min_value(),
}
} else {
OverflowsSuffix {
suffix_type: exact_width.type_str(),
max_value: exact_width.max_value(),
}
};
Err(err)
}
}
} }
}
let mut result = T::from_u32(0); fn lower_bound_of_int(result: i128) -> IntWidth {
if is_positive { use IntWidth::*;
// The number is positive if result >= 0 {
for &c in digits { // Positive
let x = match (c as char).to_digit(radix) { let result = result as u128;
Some(x) => x, if result > U64.max_value() {
None => return Err(PIE { kind: InvalidDigit }), I128
}; } else if result > I64.max_value() {
result = match result.checked_mul(radix) { U64
Some(result) => result, } else if result > U32.max_value() {
None => return Err(PIE { kind: Overflow }), I64
}; } else if result > I32.max_value() {
result = match result.checked_add(x) { U32
Some(result) => result, } else if result > U16.max_value() {
None => return Err(PIE { kind: Overflow }), I32
}; } else if result > I16.max_value() {
U16
} else if result > U8.max_value() {
I16
} else if result > I8.max_value() {
U8
} else {
I8
} }
} else { } else {
// The number is negative // Negative
for &c in digits { if result < I64.min_value() {
let x = match (c as char).to_digit(radix) { I128
Some(x) => x, } else if result < I32.min_value() {
None => return Err(PIE { kind: InvalidDigit }), I64
}; } else if result < I16.min_value() {
result = match result.checked_mul(radix) { I32
Some(result) => result, } else if result < I8.min_value() {
None => return Err(PIE { kind: Underflow }), I16
}; } else {
result = match result.checked_sub(x) { I8
Some(result) => result,
None => return Err(PIE { kind: Underflow }),
};
} }
} }
Ok((result, bound)) }
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum IntSign {
Unsigned,
Signed,
} }
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
@ -350,6 +363,109 @@ pub enum IntWidth {
Nat, Nat,
} }
impl IntWidth {
/// Returns the `IntSign` and bit width of a variant.
fn sign_and_width(&self) -> (IntSign, u32) {
use IntSign::*;
use IntWidth::*;
match self {
U8 => (Unsigned, 8),
U16 => (Unsigned, 16),
U32 => (Unsigned, 32),
U64 => (Unsigned, 64),
U128 => (Unsigned, 128),
I8 => (Signed, 8),
I16 => (Signed, 16),
I32 => (Signed, 32),
I64 => (Signed, 64),
I128 => (Signed, 128),
// TODO: this is platform specific!
Nat => (Unsigned, 64),
}
}
fn type_str(&self) -> &'static str {
use IntWidth::*;
match self {
U8 => "U8",
U16 => "U16",
U32 => "U32",
U64 => "U64",
U128 => "U128",
I8 => "I8",
I16 => "I16",
I32 => "I32",
I64 => "I64",
I128 => "I128",
Nat => "Nat",
}
}
fn max_value(&self) -> u128 {
use IntWidth::*;
match self {
U8 => u8::MAX as u128,
U16 => u16::MAX as u128,
U32 => u32::MAX as u128,
U64 => u64::MAX as u128,
U128 => u128::MAX,
I8 => i8::MAX as u128,
I16 => i16::MAX as u128,
I32 => i32::MAX as u128,
I64 => i64::MAX as u128,
I128 => i128::MAX as u128,
// TODO: this is platform specific!
Nat => u64::MAX as u128,
}
}
fn min_value(&self) -> i128 {
use IntWidth::*;
match self {
U8 | U16 | U32 | U64 | U128 | Nat => 0,
I8 => i8::MIN as i128,
I16 => i16::MIN as i128,
I32 => i32::MIN as i128,
I64 => i64::MIN as i128,
I128 => i128::MIN,
}
}
/// Checks if `self` represents superset of integers that `lower_bound` represents, on a particular
/// side of the integers relative to 0.
///
/// If `is_negative` is true, the negative side is checked; otherwise the positive side is checked.
pub fn is_superset(&self, lower_bound: &Self, is_negative: bool) -> bool {
use IntSign::*;
if is_negative {
match (self.sign_and_width(), lower_bound.sign_and_width()) {
((Signed, us), (Signed, lower_bound)) => us >= lower_bound,
// Unsigned ints can never represent negative numbers; signed (non-zero width)
// ints always can.
((Unsigned, _), (Signed, _)) => false,
((Signed, _), (Unsigned, _)) => true,
// Trivially true; both can only express 0.
((Unsigned, _), (Unsigned, _)) => true,
}
} else {
match (self.sign_and_width(), lower_bound.sign_and_width()) {
((Signed, us), (Signed, lower_bound))
| ((Unsigned, us), (Unsigned, lower_bound)) => us >= lower_bound,
// Unsigned ints with the same bit width as their unsigned counterparts can always
// express 2x more integers on the positive side as unsigned ints.
((Unsigned, us), (Signed, lower_bound)) => us >= lower_bound,
// ...but that means signed int widths can represent less than their unsigned
// counterparts, so the below is true iff the bit width is strictly greater. E.g.
// i16 is a superset of u8, but i16 is not a superset of u16.
((Signed, us), (Unsigned, lower_bound)) => us > lower_bound,
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FloatWidth { pub enum FloatWidth {
Dec, Dec,
@ -374,28 +490,3 @@ where
/// Must have exactly the width `W`. /// Must have exactly the width `W`.
Exact(W), Exact(W),
} }
/// An error which can be returned when parsing an integer.
///
/// This error is used as the error type for the `from_str_radix()` functions
/// on the primitive integer types, such as [`i8::from_str_radix`].
///
/// # Potential causes
///
/// Among other causes, `ParseIntError` can be thrown because of leading or trailing whitespace
/// in the string e.g., when it is obtained from the standard input.
/// Using the [`str.trim()`] method ensures that no whitespace remains before parsing.
///
/// [`str.trim()`]: ../../std/primitive.str.html#method.trim
/// [`i8::from_str_radix`]: ../../std/primitive.i8.html#method.from_str_radix
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseIntError {
kind: IntErrorKind,
}
impl ParseIntError {
/// Outputs the detailed cause of parsing an integer failing.
pub fn kind(&self) -> &IntErrorKind {
&self.kind
}
}

View file

@ -1,5 +1,5 @@
use crate::env::Env; use crate::env::Env;
use crate::expr::{canonicalize_expr, unescape_char, Expr, Output}; use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output};
use crate::num::{ use crate::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatWidth, IntWidth, NumWidth, finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatWidth, IntWidth, NumWidth,
NumericBound, ParsedNumResult, NumericBound, ParsedNumResult,
@ -29,8 +29,14 @@ pub enum Pattern {
ext_var: Variable, ext_var: Variable,
destructs: Vec<Loc<RecordDestruct>>, destructs: Vec<Loc<RecordDestruct>>,
}, },
NumLiteral(Variable, Box<str>, i64, NumericBound<NumWidth>), NumLiteral(Variable, Box<str>, IntValue, NumericBound<NumWidth>),
IntLiteral(Variable, Variable, Box<str>, i64, NumericBound<IntWidth>), IntLiteral(
Variable,
Variable,
Box<str>,
IntValue,
NumericBound<IntWidth>,
),
FloatLiteral(Variable, Variable, Box<str>, f64, NumericBound<FloatWidth>), FloatLiteral(Variable, Variable, Box<str>, f64, NumericBound<FloatWidth>),
StrLiteral(Box<str>), StrLiteral(Box<str>),
Underscore, Underscore,
@ -247,10 +253,20 @@ pub fn canonicalize_pattern<'a>(
let problem = MalformedPatternProblem::MalformedBase(base); let problem = MalformedPatternProblem::MalformedBase(base);
malformed_pattern(env, problem, region) malformed_pattern(env, problem, region)
} }
Ok((IntValue::U128(_), _)) if is_negative => {
// Can't negate a u128; that doesn't fit in any integer literal type we support.
let problem = MalformedPatternProblem::MalformedInt;
malformed_pattern(env, problem, region)
}
Ok((int, bound)) => { Ok((int, bound)) => {
let sign_str = if is_negative { "-" } else { "" }; let sign_str = if is_negative { "-" } else { "" };
let int_str = format!("{}{}", sign_str, int.to_string()).into_boxed_str(); let int_str = format!("{}{}", sign_str, int.to_string()).into_boxed_str();
let i = if is_negative { -int } else { int }; let i = match int {
// Safety: this is fine because I128::MAX = |I128::MIN| - 1
IntValue::I128(n) if is_negative => IntValue::I128(-n),
IntValue::I128(n) => IntValue::I128(n),
IntValue::U128(_) => unreachable!(),
};
Pattern::IntLiteral(var_store.fresh(), var_store.fresh(), int_str, i, bound) Pattern::IntLiteral(var_store.fresh(), var_store.fresh(), int_str, i, bound)
} }
}, },

View file

@ -15,7 +15,7 @@ mod test_can {
use crate::helpers::{can_expr_with, test_home, CanExprOut}; use crate::helpers::{can_expr_with, test_home, CanExprOut};
use bumpalo::Bump; use bumpalo::Bump;
use roc_can::expr::Expr::{self, *}; use roc_can::expr::Expr::{self, *};
use roc_can::expr::{ClosureData, Recursive}; use roc_can::expr::{ClosureData, IntValue, Recursive};
use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError}; use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError};
use roc_region::all::{Position, Region}; use roc_region::all::{Position, Region};
use std::{f64, i64}; use std::{f64, i64};
@ -47,7 +47,7 @@ mod test_can {
match actual_out.loc_expr.value { match actual_out.loc_expr.value {
Expr::Int(_, _, _, actual, _) => { Expr::Int(_, _, _, actual, _) => {
assert_eq!(expected, actual); assert_eq!(IntValue::I128(expected), actual);
} }
actual => { actual => {
panic!("Expected an Int *, but got: {:?}", actual); panic!("Expected an Int *, but got: {:?}", actual);
@ -55,13 +55,13 @@ mod test_can {
} }
} }
fn assert_can_num(input: &str, expected: i64) { fn assert_can_num(input: &str, expected: i128) {
let arena = Bump::new(); let arena = Bump::new();
let actual_out = can_expr_with(&arena, test_home(), input); let actual_out = can_expr_with(&arena, test_home(), input);
match actual_out.loc_expr.value { match actual_out.loc_expr.value {
Expr::Num(_, _, actual, _) => { Expr::Num(_, _, actual, _) => {
assert_eq!(expected, actual); assert_eq!(IntValue::I128(expected), actual);
} }
actual => { actual => {
panic!("Expected a Num, but got: {:?}", actual); panic!("Expected a Num, but got: {:?}", actual);
@ -79,7 +79,7 @@ mod test_can {
fn int_too_large() { fn int_too_large() {
use roc_parse::ast::Base; use roc_parse::ast::Base;
let string = (i64::MAX as i128 + 1).to_string(); let string = "340_282_366_920_938_463_463_374_607_431_768_211_456".to_string();
assert_can( assert_can(
&string.clone(), &string.clone(),
@ -96,7 +96,7 @@ mod test_can {
fn int_too_small() { fn int_too_small() {
use roc_parse::ast::Base; use roc_parse::ast::Base;
let string = (i64::MIN as i128 - 1).to_string(); let string = "-170_141_183_460_469_231_731_687_303_715_884_105_729".to_string();
assert_can( assert_can(
&string.clone(), &string.clone(),
@ -186,12 +186,12 @@ mod test_can {
#[test] #[test]
fn num_max() { fn num_max() {
assert_can_num(&(i64::MAX.to_string()), i64::MAX); assert_can_num(&(i64::MAX.to_string()), i64::MAX.into());
} }
#[test] #[test]
fn num_min() { fn num_min() {
assert_can_num(&(i64::MIN.to_string()), i64::MIN); assert_can_num(&(i64::MIN.to_string()), i64::MIN.into());
} }
#[test] #[test]

View file

@ -771,11 +771,13 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
env.context.bool_type().const_int(*int as u64, false).into() env.context.bool_type().const_int(*int as u64, false).into()
} }
Layout::Builtin(Builtin::Int(int_width)) => { Layout::Builtin(Builtin::Int(int_width)) => {
int_with_precision(env, *int as i128, *int_width).into() int_with_precision(env, *int, *int_width).into()
} }
_ => panic!("Invalid layout for int literal = {:?}", layout), _ => panic!("Invalid layout for int literal = {:?}", layout),
}, },
U128(int) => const_u128(env, *int).into(),
Float(float) => match layout { Float(float) => match layout {
Layout::Builtin(Builtin::Float(float_width)) => { Layout::Builtin(Builtin::Float(float_width)) => {
float_with_precision(env, *float, *float_width) float_with_precision(env, *float, *float_width)
@ -3073,6 +3075,19 @@ fn const_i128<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, value: i128) -> IntValu
.const_int_arbitrary_precision(&[a, b]) .const_int_arbitrary_precision(&[a, b])
} }
fn const_u128<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, value: u128) -> IntValue<'ctx> {
// truncate the lower 64 bits
let value = value as u128;
let a = value as u64;
// get the upper 64 bits
let b = (value >> 64) as u64;
env.context
.i128_type()
.const_int_arbitrary_precision(&[a, b])
}
fn build_switch_ir<'a, 'ctx, 'env>( fn build_switch_ir<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,

View file

@ -1638,7 +1638,9 @@ fn literal_spec(
match literal { match literal {
Str(_) => new_static_string(builder, block), Str(_) => new_static_string(builder, block),
Int(_) | Float(_) | Decimal(_) | Bool(_) | Byte(_) => builder.add_make_tuple(block, &[]), Int(_) | U128(_) | Float(_) | Decimal(_) | Bool(_) | Byte(_) => {
builder.add_make_tuple(block, &[])
}
} }
} }

View file

@ -87,6 +87,7 @@ enum Test<'a> {
arguments: Vec<(Pattern<'a>, Layout<'a>)>, arguments: Vec<(Pattern<'a>, Layout<'a>)>,
}, },
IsInt(i128, IntWidth), IsInt(i128, IntWidth),
IsU128(u128),
IsFloat(u64, FloatWidth), IsFloat(u64, FloatWidth),
IsDecimal(RocDec), IsDecimal(RocDec),
IsStr(Box<str>), IsStr(Box<str>),
@ -136,6 +137,10 @@ impl<'a> Hash for Test<'a> {
state.write_u8(6); state.write_u8(6);
v.0.hash(state); v.0.hash(state);
} }
IsU128(v) => {
state.write_u8(7);
v.hash(state);
}
} }
} }
} }
@ -311,6 +316,7 @@ fn tests_are_complete_help(last_test: &Test, number_of_tests: usize) -> bool {
Test::IsByte { num_alts, .. } => number_of_tests == *num_alts, Test::IsByte { num_alts, .. } => number_of_tests == *num_alts,
Test::IsBit(_) => number_of_tests == 2, Test::IsBit(_) => number_of_tests == 2,
Test::IsInt(_, _) => false, Test::IsInt(_, _) => false,
Test::IsU128(_) => false,
Test::IsFloat(_, _) => false, Test::IsFloat(_, _) => false,
Test::IsDecimal(_) => false, Test::IsDecimal(_) => false,
Test::IsStr(_) => false, Test::IsStr(_) => false,
@ -565,6 +571,7 @@ fn test_at_path<'a>(
num_alts: union.alternatives.len(), num_alts: union.alternatives.len(),
}, },
IntLiteral(v, precision) => IsInt(*v, *precision), IntLiteral(v, precision) => IsInt(*v, *precision),
U128Literal(v) => IsU128(*v),
FloatLiteral(v, precision) => IsFloat(*v, *precision), FloatLiteral(v, precision) => IsFloat(*v, *precision),
DecimalLiteral(v) => IsDecimal(*v), DecimalLiteral(v) => IsDecimal(*v),
StrLiteral(v) => IsStr(v.clone()), StrLiteral(v) => IsStr(v.clone()),
@ -823,6 +830,18 @@ fn to_relevant_branch_help<'a>(
_ => None, _ => None,
}, },
U128Literal(int) => match test {
IsU128(is_int) if int == *is_int => {
start.extend(end);
Some(Branch {
goal: branch.goal,
guard: branch.guard.clone(),
patterns: start,
})
}
_ => None,
},
FloatLiteral(float, p1) => match test { FloatLiteral(float, p1) => match test {
IsFloat(test_float, p2) if float == *test_float => { IsFloat(test_float, p2) if float == *test_float => {
debug_assert_eq!(p1, *p2); debug_assert_eq!(p1, *p2);
@ -934,6 +953,7 @@ fn needs_tests(pattern: &Pattern) -> bool {
| BitLiteral { .. } | BitLiteral { .. }
| EnumLiteral { .. } | EnumLiteral { .. }
| IntLiteral(_, _) | IntLiteral(_, _)
| U128Literal(_)
| FloatLiteral(_, _) | FloatLiteral(_, _)
| DecimalLiteral(_) | DecimalLiteral(_)
| StrLiteral(_) => true, | StrLiteral(_) => true,
@ -1305,6 +1325,14 @@ fn test_to_equality<'a>(
(stores, lhs_symbol, rhs_symbol, None) (stores, lhs_symbol, rhs_symbol, None)
} }
Test::IsU128(test_int) => {
let lhs = Expr::Literal(Literal::U128(test_int));
let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, Layout::int_width(IntWidth::U128), lhs));
(stores, lhs_symbol, rhs_symbol, None)
}
Test::IsFloat(test_int, precision) => { Test::IsFloat(test_int, precision) => {
// TODO maybe we can actually use i64 comparison here? // TODO maybe we can actually use i64 comparison here?
let test_float = f64::from_bits(test_int as u64); let test_float = f64::from_bits(test_int as u64);

View file

@ -54,6 +54,7 @@ pub enum Pattern {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Literal { pub enum Literal {
Int(i128), Int(i128),
U128(u128),
Bit(bool), Bit(bool),
Byte(u8), Byte(u8),
Float(u64), Float(u64),
@ -66,6 +67,7 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern {
match pattern { match pattern {
IntLiteral(v, _) => Literal(Literal::Int(*v)), IntLiteral(v, _) => Literal(Literal::Int(*v)),
U128Literal(v) => Literal(Literal::U128(*v)),
FloatLiteral(v, _) => Literal(Literal::Float(*v)), FloatLiteral(v, _) => Literal(Literal::Float(*v)),
DecimalLiteral(v) => Literal(Literal::Decimal(*v)), DecimalLiteral(v) => Literal(Literal::Decimal(*v)),
StrLiteral(v) => Literal(Literal::Str(v.clone())), StrLiteral(v) => Literal(Literal::Str(v.clone())),

View file

@ -8,7 +8,7 @@ use crate::layout::{
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_can::expr::ClosureData; use roc_can::expr::{ClosureData, IntValue};
use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap}; use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap};
use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
@ -1278,6 +1278,7 @@ impl ModifyRc {
pub enum Literal<'a> { pub enum Literal<'a> {
// Literals // Literals
Int(i128), Int(i128),
U128(u128),
Float(f64), Float(f64),
Decimal(RocDec), Decimal(RocDec),
Str(&'a str), Str(&'a str),
@ -1524,6 +1525,7 @@ impl<'a> Literal<'a> {
match self { match self {
Int(lit) => alloc.text(format!("{}i64", lit)), Int(lit) => alloc.text(format!("{}i64", lit)),
U128(lit) => alloc.text(format!("{}u128", lit)),
Float(lit) => alloc.text(format!("{}f64", lit)), Float(lit) => alloc.text(format!("{}f64", lit)),
// TODO: Add proper Dec.to_str // TODO: Add proper Dec.to_str
Decimal(lit) => alloc.text(format!("{}Dec", lit.0)), Decimal(lit) => alloc.text(format!("{}Dec", lit.0)),
@ -3011,7 +3013,10 @@ fn try_make_literal<'a>(
match can_expr { match can_expr {
Int(_, precision, _, int, _bound) => { Int(_, precision, _, int, _bound) => {
match num_argument_to_int_or_float(env.subs, env.target_info, *precision, false) { match num_argument_to_int_or_float(env.subs, env.target_info, *precision, false) {
IntOrFloat::Int(_) => Some(Literal::Int(*int)), IntOrFloat::Int(_) => Some(match *int {
IntValue::I128(n) => Literal::Int(n),
IntValue::U128(n) => Literal::U128(n),
}),
_ => unreachable!("unexpected float precision for integer"), _ => unreachable!("unexpected float precision for integer"),
} }
} }
@ -3039,8 +3044,14 @@ fn try_make_literal<'a>(
Num(var, num_str, num, _bound) => { Num(var, num_str, num, _bound) => {
// first figure out what kind of number this is // first figure out what kind of number this is
match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) { match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) {
IntOrFloat::Int(_) => Some(Literal::Int((*num).into())), IntOrFloat::Int(_) => Some(match *num {
IntOrFloat::Float(_) => Some(Literal::Float(*num as f64)), IntValue::I128(n) => Literal::Int(n),
IntValue::U128(n) => Literal::U128(n),
}),
IntOrFloat::Float(_) => Some(match *num {
IntValue::I128(n) => Literal::Float(n as f64),
IntValue::U128(n) => Literal::Float(n as f64),
}),
IntOrFloat::DecimalFloatType => { IntOrFloat::DecimalFloatType => {
let dec = match RocDec::from_str(num_str) { let dec = match RocDec::from_str(num_str) {
Some(d) => d, Some(d) => d,
@ -3076,7 +3087,10 @@ pub fn with_hole<'a>(
match num_argument_to_int_or_float(env.subs, env.target_info, precision, false) { match num_argument_to_int_or_float(env.subs, env.target_info, precision, false) {
IntOrFloat::Int(precision) => Stmt::Let( IntOrFloat::Int(precision) => Stmt::Let(
assigned, assigned,
Expr::Literal(Literal::Int(int)), Expr::Literal(match int {
IntValue::I128(n) => Literal::Int(n),
IntValue::U128(n) => Literal::U128(n),
}),
Layout::Builtin(Builtin::Int(precision)), Layout::Builtin(Builtin::Int(precision)),
hole, hole,
), ),
@ -3120,13 +3134,19 @@ pub fn with_hole<'a>(
match num_argument_to_int_or_float(env.subs, env.target_info, var, false) { match num_argument_to_int_or_float(env.subs, env.target_info, var, false) {
IntOrFloat::Int(precision) => Stmt::Let( IntOrFloat::Int(precision) => Stmt::Let(
assigned, assigned,
Expr::Literal(Literal::Int(num.into())), Expr::Literal(match num {
IntValue::I128(n) => Literal::Int(n),
IntValue::U128(n) => Literal::U128(n),
}),
Layout::int_width(precision), Layout::int_width(precision),
hole, hole,
), ),
IntOrFloat::Float(precision) => Stmt::Let( IntOrFloat::Float(precision) => Stmt::Let(
assigned, assigned,
Expr::Literal(Literal::Float(num as f64)), Expr::Literal(match num {
IntValue::I128(n) => Literal::Float(n as f64),
IntValue::U128(n) => Literal::Float(n as f64),
}),
Layout::float_width(precision), Layout::float_width(precision),
hole, hole,
), ),
@ -6211,6 +6231,7 @@ fn store_pattern_help<'a>(
return StorePattern::NotProductive(stmt); return StorePattern::NotProductive(stmt);
} }
IntLiteral(_, _) IntLiteral(_, _)
| U128Literal(_)
| FloatLiteral(_, _) | FloatLiteral(_, _)
| DecimalLiteral(_) | DecimalLiteral(_)
| EnumLiteral { .. } | EnumLiteral { .. }
@ -7583,6 +7604,7 @@ fn call_specialized_proc<'a>(
pub enum Pattern<'a> { pub enum Pattern<'a> {
Identifier(Symbol), Identifier(Symbol),
Underscore, Underscore,
U128Literal(u128),
IntLiteral(i128, IntWidth), IntLiteral(i128, IntWidth),
FloatLiteral(u64, FloatWidth), FloatLiteral(u64, FloatWidth),
DecimalLiteral(RocDec), DecimalLiteral(RocDec),
@ -7664,7 +7686,13 @@ fn from_can_pattern_help<'a>(
Identifier(symbol) => Ok(Pattern::Identifier(*symbol)), Identifier(symbol) => Ok(Pattern::Identifier(*symbol)),
IntLiteral(_, precision_var, _, int, _bound) => { IntLiteral(_, precision_var, _, int, _bound) => {
match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, false) { match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, false) {
IntOrFloat::Int(precision) => Ok(Pattern::IntLiteral(*int as i128, precision)), IntOrFloat::Int(precision) => {
let int = match *int {
IntValue::I128(n) => Pattern::IntLiteral(n, precision),
IntValue::U128(n) => Pattern::U128Literal(n),
};
Ok(int)
}
other => { other => {
panic!( panic!(
"Invalid precision for int pattern: {:?} has {:?}", "Invalid precision for int pattern: {:?} has {:?}",
@ -7706,8 +7734,18 @@ fn from_can_pattern_help<'a>(
} }
NumLiteral(var, num_str, num, _bound) => { NumLiteral(var, num_str, num, _bound) => {
match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) { match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) {
IntOrFloat::Int(precision) => Ok(Pattern::IntLiteral(*num as i128, precision)), IntOrFloat::Int(precision) => Ok(match num {
IntOrFloat::Float(precision) => Ok(Pattern::FloatLiteral(*num as u64, precision)), IntValue::I128(num) => Pattern::IntLiteral(*num, precision),
IntValue::U128(num) => Pattern::U128Literal(*num),
}),
IntOrFloat::Float(precision) => {
// TODO: this may be lossy
let num = match *num {
IntValue::I128(n) => f64::to_bits(n as f64),
IntValue::U128(n) => f64::to_bits(n as f64),
};
Ok(Pattern::FloatLiteral(num, precision))
}
IntOrFloat::DecimalFloatType => { IntOrFloat::DecimalFloatType => {
let dec = match RocDec::from_str(num_str) { let dec = match RocDec::from_str(num_str) {
Some(d) => d, Some(d) => d,

View file

@ -109,6 +109,16 @@ pub enum IntErrorKind {
Underflow, Underflow,
/// This is an integer, but it has a float numeric suffix. /// This is an integer, but it has a float numeric suffix.
FloatSuffix, FloatSuffix,
/// The integer literal overflows the width of the suffix associated with it.
OverflowsSuffix {
suffix_type: &'static str,
max_value: u128,
},
/// The integer literal underflows the width of the suffix associated with it.
UnderflowsSuffix {
suffix_type: &'static str,
min_value: i128,
},
} }
/// Enum to store the various types of errors that can cause parsing a float to fail. /// Enum to store the various types of errors that can cause parsing a float to fail.

View file

@ -26,6 +26,8 @@ const VALUE_NOT_EXPOSED: &str = "NOT EXPOSED";
const MODULE_NOT_IMPORTED: &str = "MODULE NOT IMPORTED"; const MODULE_NOT_IMPORTED: &str = "MODULE NOT IMPORTED";
const NESTED_DATATYPE: &str = "NESTED DATATYPE"; const NESTED_DATATYPE: &str = "NESTED DATATYPE";
const CONFLICTING_NUMBER_SUFFIX: &str = "CONFLICTING NUMBER SUFFIX"; const CONFLICTING_NUMBER_SUFFIX: &str = "CONFLICTING NUMBER SUFFIX";
const NUMBER_OVERFLOWS_SUFFIX: &str = "NUMBER OVERFLOWS SUFFIX";
const NUMBER_UNDERFLOWS_SUFFIX: &str = "NUMBER UNDERFLOWS SUFFIX";
pub fn can_problem<'b>( pub fn can_problem<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
@ -1136,10 +1138,28 @@ fn pretty_runtime_error<'b>(
} }
RuntimeError::InvalidInt(error_kind @ IntErrorKind::Underflow, _base, region, _raw_str) RuntimeError::InvalidInt(error_kind @ IntErrorKind::Underflow, _base, region, _raw_str)
| RuntimeError::InvalidInt(error_kind @ IntErrorKind::Overflow, _base, region, _raw_str) => { | RuntimeError::InvalidInt(error_kind @ IntErrorKind::Overflow, _base, region, _raw_str) => {
let big_or_small = if let IntErrorKind::Underflow = error_kind { let (big_or_small, info) = if let IntErrorKind::Underflow = error_kind {
"small" (
"small",
alloc.concat(vec![
alloc.reflow(
"The smallest number representable in Roc is the minimum I128 value, ",
),
alloc.int_literal(i128::MIN),
alloc.text("."),
]),
)
} else { } else {
"big" (
"big",
alloc.concat(vec![
alloc.reflow(
"The largest number representable in Roc is the maximum U128 value, ",
),
alloc.int_literal(u128::MAX),
alloc.text("."),
]),
)
}; };
let tip = alloc let tip = alloc
@ -1153,7 +1173,7 @@ fn pretty_runtime_error<'b>(
alloc.reflow(":"), alloc.reflow(":"),
]), ]),
alloc.region(lines.convert_region(region)), alloc.region(lines.convert_region(region)),
alloc.reflow("Roc uses signed 64-bit integers, allowing values between 9_223_372_036_854_775_808 and 9_223_372_036_854_775_807."), info,
tip, tip,
]); ]);
@ -1169,6 +1189,56 @@ fn pretty_runtime_error<'b>(
title = CONFLICTING_NUMBER_SUFFIX; title = CONFLICTING_NUMBER_SUFFIX;
} }
RuntimeError::InvalidInt(
IntErrorKind::OverflowsSuffix {
suffix_type,
max_value,
},
_base,
region,
_raw_str,
) => {
doc = alloc.stack(vec![
alloc.concat(vec![alloc.reflow(
"This integer literal overflows the type indicated by its suffix:",
)]),
alloc.region(lines.convert_region(region)),
alloc.tip().append(alloc.concat(vec![
alloc.reflow("The suffix indicates this integer is a "),
alloc.type_str(suffix_type),
alloc.reflow(", whose maximum value is "),
alloc.int_literal(max_value),
alloc.reflow("."),
])),
]);
title = NUMBER_OVERFLOWS_SUFFIX;
}
RuntimeError::InvalidInt(
IntErrorKind::UnderflowsSuffix {
suffix_type,
min_value,
},
_base,
region,
_raw_str,
) => {
doc = alloc.stack(vec![
alloc.concat(vec![alloc.reflow(
"This integer literal underflows the type indicated by its suffix:",
)]),
alloc.region(lines.convert_region(region)),
alloc.tip().append(alloc.concat(vec![
alloc.reflow("The suffix indicates this integer is a "),
alloc.type_str(suffix_type),
alloc.reflow(", whose minimum value is "),
alloc.int_literal(min_value),
alloc.reflow("."),
])),
]);
title = NUMBER_UNDERFLOWS_SUFFIX;
}
RuntimeError::InvalidOptionalValue { RuntimeError::InvalidOptionalValue {
field_name, field_name,
field_region, field_region,

View file

@ -148,6 +148,7 @@ fn pattern_to_doc_help<'b>(
Anything => alloc.text("_"), Anything => alloc.text("_"),
Literal(l) => match l { Literal(l) => match l {
Int(i) => alloc.text(i.to_string()), Int(i) => alloc.text(i.to_string()),
U128(i) => alloc.text(i.to_string()),
Bit(true) => alloc.text("True"), Bit(true) => alloc.text("True"),
Bit(false) => alloc.text("False"), Bit(false) => alloc.text("False"),
Byte(b) => alloc.text(b.to_string()), Byte(b) => alloc.text(b.to_string()),

View file

@ -632,6 +632,39 @@ impl<'a> RocDocAllocator<'a> {
self.text(format!("{}", ident.as_inline_str())) self.text(format!("{}", ident.as_inline_str()))
.annotate(Annotation::Symbol) .annotate(Annotation::Symbol)
} }
pub fn int_literal<I>(&'a self, int: I) -> DocBuilder<'a, Self, Annotation>
where
I: ToString,
{
let s = int.to_string();
let is_negative = s.starts_with('-');
if s.len() < 7 + (is_negative as usize) {
// If the number is not at least in the millions, return it as-is.
return self.text(s);
}
// Otherwise, let's add numeric separators to make it easier to read.
let mut result = String::with_capacity(s.len() + s.len() / 3);
for (idx, c) in s
.get((is_negative as usize)..)
.unwrap()
.chars()
.rev()
.enumerate()
{
if idx != 0 && idx % 3 == 0 {
result.push('_');
}
result.push(c);
}
if is_negative {
result.push('-');
}
self.text(result.chars().rev().collect::<String>())
}
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone)]

View file

@ -3409,15 +3409,15 @@ mod test_reporting {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
x = 9_223_372_036_854_775_807_000 x = 170_141_183_460_469_231_731_687_303_715_884_105_728_000
y = -9_223_372_036_854_775_807_000 y = -170_141_183_460_469_231_731_687_303_715_884_105_728_000
h = 0x8FFF_FFFF_FFFF_FFFF h = 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF
l = -0x8FFF_FFFF_FFFF_FFFF l = -0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF
minlit = -9_223_372_036_854_775_808 minlit = -170_141_183_460_469_231_731_687_303_715_884_105_728
maxlit = 9_223_372_036_854_775_807 maxlit = 340_282_366_920_938_463_463_374_607_431_768_211_455
x + y + h + l + minlit + maxlit x + y + h + l + minlit + maxlit
"# "#
@ -3428,11 +3428,11 @@ mod test_reporting {
This integer literal is too big: This integer literal is too big:
1 x = 9_223_372_036_854_775_807_000 1 x = 170_141_183_460_469_231_731_687_303_715_884_105_728_000
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Roc uses signed 64-bit integers, allowing values between The largest number representable in Roc is the maximum U128 value,
9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. 340_282_366_920_938_463_463_374_607_431_768_211_455.
Tip: Learn more about number literals at TODO Tip: Learn more about number literals at TODO
@ -3440,11 +3440,11 @@ mod test_reporting {
This integer literal is too small: This integer literal is too small:
3 y = -9_223_372_036_854_775_807_000 3 y = -170_141_183_460_469_231_731_687_303_715_884_105_728_000
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Roc uses signed 64-bit integers, allowing values between The smallest number representable in Roc is the minimum I128 value,
9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. -170_141_183_460_469_231_731_687_303_715_884_105_728.
Tip: Learn more about number literals at TODO Tip: Learn more about number literals at TODO
@ -3452,11 +3452,11 @@ mod test_reporting {
This integer literal is too big: This integer literal is too big:
5 h = 0x8FFF_FFFF_FFFF_FFFF 5 h = 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF
^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Roc uses signed 64-bit integers, allowing values between The largest number representable in Roc is the maximum U128 value,
9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. 340_282_366_920_938_463_463_374_607_431_768_211_455.
Tip: Learn more about number literals at TODO Tip: Learn more about number literals at TODO
@ -3464,11 +3464,11 @@ mod test_reporting {
This integer literal is too small: This integer literal is too small:
6 l = -0x8FFF_FFFF_FFFF_FFFF 6 l = -0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Roc uses signed 64-bit integers, allowing values between The smallest number representable in Roc is the minimum I128 value,
9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. -170_141_183_460_469_231_731_687_303_715_884_105_728.
Tip: Learn more about number literals at TODO Tip: Learn more about number literals at TODO
"# "#
@ -7527,4 +7527,364 @@ I need all branches in an `if` to have the same type!
), ),
) )
} }
#[test]
fn u8_overflow() {
report_problem_as(
"256u8",
indoc!(
r#"
NUMBER OVERFLOWS SUFFIX
This integer literal overflows the type indicated by its suffix:
1 256u8
^^^^^
Tip: The suffix indicates this integer is a U8, whose maximum value is
255.
"#
),
)
}
#[test]
fn negative_u8() {
report_problem_as(
"-1u8",
indoc!(
r#"
NUMBER UNDERFLOWS SUFFIX
This integer literal underflows the type indicated by its suffix:
1 -1u8
^^^^
Tip: The suffix indicates this integer is a U8, whose minimum value is
0.
"#
),
)
}
#[test]
fn u16_overflow() {
report_problem_as(
"65536u16",
indoc!(
r#"
NUMBER OVERFLOWS SUFFIX
This integer literal overflows the type indicated by its suffix:
1 65536u16
^^^^^^^^
Tip: The suffix indicates this integer is a U16, whose maximum value
is 65535.
"#
),
)
}
#[test]
fn negative_u16() {
report_problem_as(
"-1u16",
indoc!(
r#"
NUMBER UNDERFLOWS SUFFIX
This integer literal underflows the type indicated by its suffix:
1 -1u16
^^^^^
Tip: The suffix indicates this integer is a U16, whose minimum value
is 0.
"#
),
)
}
#[test]
fn u32_overflow() {
report_problem_as(
"4_294_967_296u32",
indoc!(
r#"
NUMBER OVERFLOWS SUFFIX
This integer literal overflows the type indicated by its suffix:
1 4_294_967_296u32
^^^^^^^^^^^^^^^^
Tip: The suffix indicates this integer is a U32, whose maximum value
is 4_294_967_295.
"#
),
)
}
#[test]
fn negative_u32() {
report_problem_as(
"-1u32",
indoc!(
r#"
NUMBER UNDERFLOWS SUFFIX
This integer literal underflows the type indicated by its suffix:
1 -1u32
^^^^^
Tip: The suffix indicates this integer is a U32, whose minimum value
is 0.
"#
),
)
}
#[test]
fn u64_overflow() {
report_problem_as(
"18_446_744_073_709_551_616u64",
indoc!(
r#"
NUMBER OVERFLOWS SUFFIX
This integer literal overflows the type indicated by its suffix:
1 18_446_744_073_709_551_616u64
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Tip: The suffix indicates this integer is a U64, whose maximum value
is 18_446_744_073_709_551_615.
"#
),
)
}
#[test]
fn negative_u64() {
report_problem_as(
"-1u64",
indoc!(
r#"
NUMBER UNDERFLOWS SUFFIX
This integer literal underflows the type indicated by its suffix:
1 -1u64
^^^^^
Tip: The suffix indicates this integer is a U64, whose minimum value
is 0.
"#
),
)
}
#[test]
fn negative_u128() {
report_problem_as(
"-1u128",
indoc!(
r#"
NUMBER UNDERFLOWS SUFFIX
This integer literal underflows the type indicated by its suffix:
1 -1u128
^^^^^^
Tip: The suffix indicates this integer is a U128, whose minimum value
is 0.
"#
),
)
}
#[test]
fn i8_overflow() {
report_problem_as(
"128i8",
indoc!(
r#"
NUMBER OVERFLOWS SUFFIX
This integer literal overflows the type indicated by its suffix:
1 128i8
^^^^^
Tip: The suffix indicates this integer is a I8, whose maximum value is
127.
"#
),
)
}
#[test]
fn i8_underflow() {
report_problem_as(
"-129i8",
indoc!(
r#"
NUMBER UNDERFLOWS SUFFIX
This integer literal underflows the type indicated by its suffix:
1 -129i8
^^^^^^
Tip: The suffix indicates this integer is a I8, whose minimum value is
-128.
"#
),
)
}
#[test]
fn i16_overflow() {
report_problem_as(
"32768i16",
indoc!(
r#"
NUMBER OVERFLOWS SUFFIX
This integer literal overflows the type indicated by its suffix:
1 32768i16
^^^^^^^^
Tip: The suffix indicates this integer is a I16, whose maximum value
is 32767.
"#
),
)
}
#[test]
fn i16_underflow() {
report_problem_as(
"-32769i16",
indoc!(
r#"
NUMBER UNDERFLOWS SUFFIX
This integer literal underflows the type indicated by its suffix:
1 -32769i16
^^^^^^^^^
Tip: The suffix indicates this integer is a I16, whose minimum value
is -32768.
"#
),
)
}
#[test]
fn i32_overflow() {
report_problem_as(
"2_147_483_648i32",
indoc!(
r#"
NUMBER OVERFLOWS SUFFIX
This integer literal overflows the type indicated by its suffix:
1 2_147_483_648i32
^^^^^^^^^^^^^^^^
Tip: The suffix indicates this integer is a I32, whose maximum value
is 2_147_483_647.
"#
),
)
}
#[test]
fn i32_underflow() {
report_problem_as(
"-2_147_483_649i32",
indoc!(
r#"
NUMBER UNDERFLOWS SUFFIX
This integer literal underflows the type indicated by its suffix:
1 -2_147_483_649i32
^^^^^^^^^^^^^^^^^
Tip: The suffix indicates this integer is a I32, whose minimum value
is -2_147_483_648.
"#
),
)
}
#[test]
fn i64_overflow() {
report_problem_as(
"9_223_372_036_854_775_808i64",
indoc!(
r#"
NUMBER OVERFLOWS SUFFIX
This integer literal overflows the type indicated by its suffix:
1 9_223_372_036_854_775_808i64
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Tip: The suffix indicates this integer is a I64, whose maximum value
is 9_223_372_036_854_775_807.
"#
),
)
}
#[test]
fn i64_underflow() {
report_problem_as(
"-9_223_372_036_854_775_809i64",
indoc!(
r#"
NUMBER UNDERFLOWS SUFFIX
This integer literal underflows the type indicated by its suffix:
1 -9_223_372_036_854_775_809i64
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Tip: The suffix indicates this integer is a I64, whose minimum value
is -9_223_372_036_854_775_808.
"#
),
)
}
#[test]
fn i128_overflow() {
report_problem_as(
"170_141_183_460_469_231_731_687_303_715_884_105_728i128",
indoc!(
r#"
NUMBER OVERFLOWS SUFFIX
This integer literal overflows the type indicated by its suffix:
1 170_141_183_460_469_231_731_687_303_715_884_105_728i128
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Tip: The suffix indicates this integer is a I128, whose maximum value
is 170_141_183_460_469_231_731_687_303_715_884_105_727.
"#
),
)
}
} }