diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index 1d0553797d..5bcd4edd19 100644 --- a/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -1,5 +1,5 @@ use bumpalo::Bump; -use roc_can::expr::Recursive; +use roc_can::expr::{IntValue, Recursive}; use roc_can::num::{ 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) { Ok(ParsedNumResult::UnknownNum(int) | ParsedNumResult::Int(int, _)) => { 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(), // TODO non-hardcode style: IntStyle::Decimal, @@ -120,7 +123,10 @@ pub fn expr_to_expr2<'a>( match finish_parsing_base(string, *base, *is_negative) { Ok((int, _bound)) => { 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(), // TODO non-hardcode style: IntStyle::from_base(*base), diff --git a/ast/src/lang/core/pattern.rs b/ast/src/lang/core/pattern.rs index 4dcf759964..aabb06254f 100644 --- a/ast/src/lang/core/pattern.rs +++ b/ast/src/lang/core/pattern.rs @@ -3,7 +3,7 @@ #![allow(unused_imports)] use bumpalo::collections::Vec as BumpVec; -use roc_can::expr::unescape_char; +use roc_can::expr::{unescape_char, IntValue}; use roc_can::num::{ finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult, }; @@ -197,9 +197,20 @@ pub fn to_pattern2<'a>( malformed_pattern(env, problem, region) } 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)) => { Pattern2::FloatLiteral(FloatVal::F64(int)) } @@ -218,6 +229,10 @@ pub fn to_pattern2<'a>( malformed_pattern(env, problem, region) } Ok((int, _bound)) => { + let int = match int { + IntValue::U128(_) => todo!(), + IntValue::I128(n) => n as i64, // FIXME + }; if *is_negative { Pattern2::IntLiteral(IntVal::I64(-int)) } else { diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 42e27f49ed..93f6b6947b 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1,5 +1,5 @@ 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::num::{FloatWidth, IntWidth, NumWidth, NumericBound}; use crate::pattern::Pattern; @@ -5197,7 +5197,7 @@ where num_var, precision_var, ii.to_string().into_boxed_str(), - ii, + IntValue::I128(ii), bound, ) } @@ -5219,6 +5219,12 @@ fn float( } #[inline(always)] -fn num(num_var: Variable, i: i64, bound: NumericBound) -> Expr { - Num(num_var, i.to_string().into_boxed_str(), i, bound) +fn num>(num_var: Variable, i: I, bound: NumericBound) -> Expr { + let i = i.into(); + Num( + num_var, + i.to_string().into_boxed_str(), + IntValue::I128(i), + bound, + ) } diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 1f12b281ce..3d4954e5ad 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -20,7 +20,7 @@ use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError}; use roc_region::all::{Loc, Region}; use roc_types::subs::{VarStore, Variable}; use roc_types::types::Alias; -use std::fmt::Debug; +use std::fmt::{Debug, Display}; use std::{char, u32}; #[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)] pub enum Expr { // Literals // 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 - Num(Variable, Box, i64, NumericBound), + Num(Variable, Box, IntValue, NumericBound), // Int and Float store a variable to generate better error messages - Int(Variable, Variable, Box, i128, NumericBound), + Int( + Variable, + Variable, + Box, + IntValue, + NumericBound, + ), Float(Variable, Variable, Box, f64, NumericBound), Str(Box), List { @@ -802,13 +823,7 @@ pub fn canonicalize_expr<'a>( // to keep borrowed values around and make this compile let int_string = int.to_string(); let int_str = int_string.as_str(); - int_expr_from_result( - var_store, - Ok((int_str, int as i128, bound)), - region, - base, - env, - ) + int_expr_from_result(var_store, Ok((int_str, int, bound)), region, base, env) } Err(e) => int_expr_from_result(var_store, Err(e), region, base, env), }; diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index cffbec95db..847d729466 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -1,5 +1,5 @@ use crate::env::Env; -use crate::expr::Expr; +use crate::expr::{Expr, IntValue}; use roc_parse::ast::Base; use roc_problem::can::Problem; use roc_problem::can::RuntimeError::*; @@ -9,11 +9,6 @@ use roc_types::subs::VarStore; use std::i64; 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)] pub fn num_expr_from_result( var_store: &mut VarStore, @@ -29,7 +24,7 @@ pub fn num_expr_from_result( var_store.fresh(), var_store.fresh(), (*str).into(), - num.into(), + num, bound, ), Ok((str, ParsedNumResult::Float(num, bound))) => Expr::Float( @@ -55,7 +50,7 @@ pub fn num_expr_from_result( #[inline(always)] pub fn int_expr_from_result( var_store: &mut VarStore, - result: Result<(&str, i128, NumericBound), (&str, IntErrorKind)>, + result: Result<(&str, IntValue, NumericBound), (&str, IntErrorKind)>, region: Region, base: Base, env: &mut Env, @@ -106,9 +101,9 @@ pub fn float_expr_from_result( } pub enum ParsedNumResult { - Int(i64, NumericBound), + Int(IntValue, NumericBound), Float(f64, NumericBound), - UnknownNum(i64), + UnknownNum(IntValue), } #[inline(always)] @@ -116,7 +111,7 @@ pub fn finish_parsing_num(raw: &str) -> Result(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 Ok(match bound { NumericBound::None => ParsedNumResult::UnknownNum(num), @@ -124,7 +119,11 @@ pub fn finish_parsing_num(raw: &str) -> Result { - 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, base: Base, is_negative: bool, -) -> Result<(i64, NumericBound), (&str, IntErrorKind)> { +) -> Result<(IntValue, NumericBound), (&str, IntErrorKind)> { let radix = match base { Base::Hex => 16, Base::Decimal => 10, @@ -144,11 +143,10 @@ pub fn finish_parsing_base( // Ignore underscores, insert - when negative to get correct underflow/overflow behavior (if is_negative { - from_str_radix::(format!("-{}", raw.replace("_", "")).as_str(), radix) + from_str_radix(format!("-{}", raw.replace("_", "")).as_str(), radix) } else { - from_str_radix::(raw.replace("_", "").as_str(), radix) + from_str_radix(raw.replace("_", "").as_str(), radix) }) - .map_err(|e| e.kind) .and_then(|(n, bound)| { let bound = match bound { NumericBound::None => NumericBound::None, @@ -164,15 +162,12 @@ pub fn finish_parsing_base( pub fn finish_parsing_float( raw: &str, ) -> Result<(f64, NumericBound), (&str, FloatErrorKind)> { - let (bound, raw_without_suffix) = parse_literal_suffix(raw.as_bytes()); - // 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 (opt_bound, raw_without_suffix) = parse_literal_suffix(raw); - let bound = match bound { - NumericBound::None => NumericBound::None, - NumericBound::Exact(NumWidth::Float(fw)) => NumericBound::Exact(fw), - NumericBound::Exact(NumWidth::Int(_)) => return Err((raw, FloatErrorKind::IntSuffix)), + let bound = match opt_bound { + None => NumericBound::None, + Some(NumWidth::Float(fw)) => NumericBound::Exact(fw), + Some(NumWidth::Int(_)) => return Err((raw, FloatErrorKind::IntSuffix)), }; // Ignore underscores. @@ -189,33 +184,33 @@ pub fn finish_parsing_float( } } -fn parse_literal_suffix(num_str: &[u8]) -> (NumericBound, &[u8]) { +fn parse_literal_suffix(num_str: &str) -> (Option, &str) { macro_rules! parse_num_suffix { ($($suffix:expr, $width:expr)*) => {$( 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! { - b"u8", NumWidth::Int(IntWidth::U8) - b"u16", NumWidth::Int(IntWidth::U16) - b"u32", NumWidth::Int(IntWidth::U32) - b"u64", NumWidth::Int(IntWidth::U64) - b"u128", NumWidth::Int(IntWidth::U128) - b"i8", NumWidth::Int(IntWidth::I8) - b"i16", NumWidth::Int(IntWidth::I16) - b"i32", NumWidth::Int(IntWidth::I32) - b"i64", NumWidth::Int(IntWidth::I64) - b"i128", NumWidth::Int(IntWidth::I128) - b"nat", NumWidth::Int(IntWidth::Nat) - b"dec", NumWidth::Float(FloatWidth::Dec) - b"f32", NumWidth::Float(FloatWidth::F32) - b"f64", NumWidth::Float(FloatWidth::F64) + "u8", NumWidth::Int(IntWidth::U8) + "u16", NumWidth::Int(IntWidth::U16) + "u32", NumWidth::Int(IntWidth::U32) + "u64", NumWidth::Int(IntWidth::U64) + "u128", NumWidth::Int(IntWidth::U128) + "i8", NumWidth::Int(IntWidth::I8) + "i16", NumWidth::Int(IntWidth::I16) + "i32", NumWidth::Int(IntWidth::I32) + "i64", NumWidth::Int(IntWidth::I64) + "i128", NumWidth::Int(IntWidth::I128) + "nat", NumWidth::Int(IntWidth::Nat) + "dec", NumWidth::Float(FloatWidth::Dec) + "f32", NumWidth::Float(FloatWidth::F32) + "f64", NumWidth::Float(FloatWidth::F64) } - (NumericBound::None, num_str) + (None, num_str) } /// Integer parsing code taken from the rust libcore, @@ -226,47 +221,11 @@ fn parse_literal_suffix(num_str: &[u8]) -> (NumericBound, &[u8]) { /// the LEGAL_DETAILS file in the root directory of this distribution. /// /// Thanks to the Rust project and its contributors! -trait FromStrRadixHelper: PartialOrd + Copy { - fn min_value() -> Self; - fn max_value() -> Self; - fn from_u32(u: u32) -> Self; - fn checked_mul(&self, other: u32) -> Option; - fn checked_sub(&self, other: u32) -> Option; - fn checked_add(&self, other: u32) -> Option; -} - -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::checked_mul(*self, other as Self) - } - #[inline] - fn checked_sub(&self, other: u32) -> Option { - Self::checked_sub(*self, other as Self) - } - #[inline] - fn checked_add(&self, other: u32) -> Option { - 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( +fn from_str_radix( src: &str, radix: u32, -) -> Result<(T, NumericBound), ParseIntError> { +) -> Result<(IntValue, NumericBound), IntErrorKind> { use self::IntErrorKind::*; - use self::ParseIntError as PIE; assert!( (2..=36).contains(&radix), @@ -274,65 +233,119 @@ fn from_str_radix( radix ); - if src.is_empty() { - return Err(PIE { kind: Empty }); - } + let (opt_exact_bound, src) = parse_literal_suffix(src); - let is_signed_ty = T::from_u32(0) > T::min_value(); - - // all valid digits are ascii, so we will just iterate over the utf8 bytes - // and cast them to chars. .to_digit() will safely return None for anything - // other than a valid ascii digit for the given radix, including the first-byte - // of multi-byte sequences - let src = src.as_bytes(); - - let (is_positive, digits) = match src[0] { - b'+' => (true, &src[1..]), - b'-' if is_signed_ty => (false, &src[1..]), - _ => (true, src), + use std::num::IntErrorKind as StdIEK; + let result = match i128::from_str_radix(src, radix) { + Ok(result) => IntValue::I128(result), + Err(pie) => match pie.kind() { + StdIEK::Empty => return Err(IntErrorKind::Empty), + StdIEK::InvalidDigit => return Err(IntErrorKind::InvalidDigit), + StdIEK::NegOverflow => return Err(IntErrorKind::Underflow), + StdIEK::PosOverflow => { + // try a u128 + match u128::from_str_radix(src, radix) { + Ok(result) => IntValue::U128(result), + 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() { - return Err(PIE { kind: Empty }); + match opt_exact_bound { + 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); - if is_positive { - // The number is positive - for &c in digits { - let x = match (c as char).to_digit(radix) { - Some(x) => x, - None => return Err(PIE { kind: InvalidDigit }), - }; - result = match result.checked_mul(radix) { - Some(result) => result, - None => return Err(PIE { kind: Overflow }), - }; - result = match result.checked_add(x) { - Some(result) => result, - None => return Err(PIE { kind: Overflow }), - }; +fn lower_bound_of_int(result: i128) -> IntWidth { + use IntWidth::*; + if result >= 0 { + // Positive + let result = result as u128; + if result > U64.max_value() { + I128 + } else if result > I64.max_value() { + U64 + } else if result > U32.max_value() { + I64 + } else if result > I32.max_value() { + U32 + } else if result > U16.max_value() { + 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 { - // The number is negative - for &c in digits { - let x = match (c as char).to_digit(radix) { - Some(x) => x, - None => return Err(PIE { kind: InvalidDigit }), - }; - result = match result.checked_mul(radix) { - Some(result) => result, - None => return Err(PIE { kind: Underflow }), - }; - result = match result.checked_sub(x) { - Some(result) => result, - None => return Err(PIE { kind: Underflow }), - }; + // Negative + if result < I64.min_value() { + I128 + } else if result < I32.min_value() { + I64 + } else if result < I16.min_value() { + I32 + } else if result < I8.min_value() { + I16 + } else { + I8 } } - Ok((result, bound)) +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum IntSign { + Unsigned, + Signed, } #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -350,6 +363,109 @@ pub enum IntWidth { 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)] pub enum FloatWidth { Dec, @@ -374,28 +490,3 @@ where /// Must have exactly the width `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 - } -} diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index eeb8c4b7ce..ac85b53306 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -1,5 +1,5 @@ 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::{ finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatWidth, IntWidth, NumWidth, NumericBound, ParsedNumResult, @@ -29,8 +29,14 @@ pub enum Pattern { ext_var: Variable, destructs: Vec>, }, - NumLiteral(Variable, Box, i64, NumericBound), - IntLiteral(Variable, Variable, Box, i64, NumericBound), + NumLiteral(Variable, Box, IntValue, NumericBound), + IntLiteral( + Variable, + Variable, + Box, + IntValue, + NumericBound, + ), FloatLiteral(Variable, Variable, Box, f64, NumericBound), StrLiteral(Box), Underscore, @@ -247,10 +253,20 @@ pub fn canonicalize_pattern<'a>( let problem = MalformedPatternProblem::MalformedBase(base); 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)) => { let sign_str = if is_negative { "-" } else { "" }; 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) } }, diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index e526334175..3489b0677f 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -15,7 +15,7 @@ mod test_can { use crate::helpers::{can_expr_with, test_home, CanExprOut}; use bumpalo::Bump; 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_region::all::{Position, Region}; use std::{f64, i64}; @@ -47,7 +47,7 @@ mod test_can { match actual_out.loc_expr.value { Expr::Int(_, _, _, actual, _) => { - assert_eq!(expected, actual); + assert_eq!(IntValue::I128(expected), actual); } 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 actual_out = can_expr_with(&arena, test_home(), input); match actual_out.loc_expr.value { Expr::Num(_, _, actual, _) => { - assert_eq!(expected, actual); + assert_eq!(IntValue::I128(expected), actual); } actual => { panic!("Expected a Num, but got: {:?}", actual); @@ -79,7 +79,7 @@ mod test_can { fn int_too_large() { 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( &string.clone(), @@ -96,7 +96,7 @@ mod test_can { fn int_too_small() { 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( &string.clone(), @@ -186,12 +186,12 @@ mod test_can { #[test] fn num_max() { - assert_can_num(&(i64::MAX.to_string()), i64::MAX); + assert_can_num(&(i64::MAX.to_string()), i64::MAX.into()); } #[test] fn num_min() { - assert_can_num(&(i64::MIN.to_string()), i64::MIN); + assert_can_num(&(i64::MIN.to_string()), i64::MIN.into()); } #[test] diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 031ce7b1db..de90098631 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -771,11 +771,13 @@ pub fn build_exp_literal<'a, 'ctx, 'env>( env.context.bool_type().const_int(*int as u64, false).into() } 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), }, + U128(int) => const_u128(env, *int).into(), + Float(float) => match layout { Layout::Builtin(Builtin::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]) } +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>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index 6481f924c4..e52409c240 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -1638,7 +1638,9 @@ fn literal_spec( match literal { 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, &[]) + } } } diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index 8f78a32458..b37391d9cd 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -87,6 +87,7 @@ enum Test<'a> { arguments: Vec<(Pattern<'a>, Layout<'a>)>, }, IsInt(i128, IntWidth), + IsU128(u128), IsFloat(u64, FloatWidth), IsDecimal(RocDec), IsStr(Box), @@ -136,6 +137,10 @@ impl<'a> Hash for Test<'a> { state.write_u8(6); 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::IsBit(_) => number_of_tests == 2, Test::IsInt(_, _) => false, + Test::IsU128(_) => false, Test::IsFloat(_, _) => false, Test::IsDecimal(_) => false, Test::IsStr(_) => false, @@ -565,6 +571,7 @@ fn test_at_path<'a>( num_alts: union.alternatives.len(), }, IntLiteral(v, precision) => IsInt(*v, *precision), + U128Literal(v) => IsU128(*v), FloatLiteral(v, precision) => IsFloat(*v, *precision), DecimalLiteral(v) => IsDecimal(*v), StrLiteral(v) => IsStr(v.clone()), @@ -823,6 +830,18 @@ fn to_relevant_branch_help<'a>( _ => 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 { IsFloat(test_float, p2) if float == *test_float => { debug_assert_eq!(p1, *p2); @@ -934,6 +953,7 @@ fn needs_tests(pattern: &Pattern) -> bool { | BitLiteral { .. } | EnumLiteral { .. } | IntLiteral(_, _) + | U128Literal(_) | FloatLiteral(_, _) | DecimalLiteral(_) | StrLiteral(_) => true, @@ -1305,6 +1325,14 @@ fn test_to_equality<'a>( (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) => { // TODO maybe we can actually use i64 comparison here? let test_float = f64::from_bits(test_int as u64); diff --git a/compiler/mono/src/exhaustive.rs b/compiler/mono/src/exhaustive.rs index a29ca6cf12..2fac242662 100644 --- a/compiler/mono/src/exhaustive.rs +++ b/compiler/mono/src/exhaustive.rs @@ -54,6 +54,7 @@ pub enum Pattern { #[derive(Clone, Debug, PartialEq)] pub enum Literal { Int(i128), + U128(u128), Bit(bool), Byte(u8), Float(u64), @@ -66,6 +67,7 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern { match pattern { IntLiteral(v, _) => Literal(Literal::Int(*v)), + U128Literal(v) => Literal(Literal::U128(*v)), FloatLiteral(v, _) => Literal(Literal::Float(*v)), DecimalLiteral(v) => Literal(Literal::Decimal(*v)), StrLiteral(v) => Literal(Literal::Str(v.clone())), diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 2296d5b55d..75f89ae6fa 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -8,7 +8,7 @@ use crate::layout::{ use bumpalo::collections::Vec; use bumpalo::Bump; 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_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; @@ -1278,6 +1278,7 @@ impl ModifyRc { pub enum Literal<'a> { // Literals Int(i128), + U128(u128), Float(f64), Decimal(RocDec), Str(&'a str), @@ -1524,6 +1525,7 @@ impl<'a> Literal<'a> { match self { Int(lit) => alloc.text(format!("{}i64", lit)), + U128(lit) => alloc.text(format!("{}u128", lit)), Float(lit) => alloc.text(format!("{}f64", lit)), // TODO: Add proper Dec.to_str Decimal(lit) => alloc.text(format!("{}Dec", lit.0)), @@ -3011,7 +3013,10 @@ fn try_make_literal<'a>( match can_expr { Int(_, precision, _, int, _bound) => { 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"), } } @@ -3039,8 +3044,14 @@ fn try_make_literal<'a>( Num(var, num_str, num, _bound) => { // first figure out what kind of number this is match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) { - IntOrFloat::Int(_) => Some(Literal::Int((*num).into())), - IntOrFloat::Float(_) => Some(Literal::Float(*num as f64)), + IntOrFloat::Int(_) => Some(match *num { + 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 => { let dec = match RocDec::from_str(num_str) { 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) { IntOrFloat::Int(precision) => Stmt::Let( 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)), hole, ), @@ -3120,13 +3134,19 @@ pub fn with_hole<'a>( match num_argument_to_int_or_float(env.subs, env.target_info, var, false) { IntOrFloat::Int(precision) => Stmt::Let( 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), hole, ), IntOrFloat::Float(precision) => Stmt::Let( 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), hole, ), @@ -6211,6 +6231,7 @@ fn store_pattern_help<'a>( return StorePattern::NotProductive(stmt); } IntLiteral(_, _) + | U128Literal(_) | FloatLiteral(_, _) | DecimalLiteral(_) | EnumLiteral { .. } @@ -7583,6 +7604,7 @@ fn call_specialized_proc<'a>( pub enum Pattern<'a> { Identifier(Symbol), Underscore, + U128Literal(u128), IntLiteral(i128, IntWidth), FloatLiteral(u64, FloatWidth), DecimalLiteral(RocDec), @@ -7664,7 +7686,13 @@ fn from_can_pattern_help<'a>( Identifier(symbol) => Ok(Pattern::Identifier(*symbol)), IntLiteral(_, precision_var, _, int, _bound) => { 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 => { panic!( "Invalid precision for int pattern: {:?} has {:?}", @@ -7706,8 +7734,18 @@ fn from_can_pattern_help<'a>( } NumLiteral(var, num_str, num, _bound) => { 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::Float(precision) => Ok(Pattern::FloatLiteral(*num as u64, precision)), + IntOrFloat::Int(precision) => Ok(match num { + 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 => { let dec = match RocDec::from_str(num_str) { Some(d) => d, diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index f7011d6b1d..f8d964e8fc 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -109,6 +109,16 @@ pub enum IntErrorKind { Underflow, /// This is an integer, but it has a float numeric suffix. 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. diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index fd34e795c3..3ad20cebf6 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -26,6 +26,8 @@ const VALUE_NOT_EXPOSED: &str = "NOT EXPOSED"; const MODULE_NOT_IMPORTED: &str = "MODULE NOT IMPORTED"; const NESTED_DATATYPE: &str = "NESTED DATATYPE"; 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>( 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::Overflow, _base, region, _raw_str) => { - let big_or_small = if let IntErrorKind::Underflow = error_kind { - "small" + let (big_or_small, info) = if let IntErrorKind::Underflow = error_kind { + ( + "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 { - "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 @@ -1153,7 +1173,7 @@ fn pretty_runtime_error<'b>( alloc.reflow(":"), ]), 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, ]); @@ -1169,6 +1189,56 @@ fn pretty_runtime_error<'b>( 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 { field_name, field_region, diff --git a/reporting/src/error/mono.rs b/reporting/src/error/mono.rs index a3df3d2338..6b5518a717 100644 --- a/reporting/src/error/mono.rs +++ b/reporting/src/error/mono.rs @@ -148,6 +148,7 @@ fn pattern_to_doc_help<'b>( Anything => alloc.text("_"), Literal(l) => match l { Int(i) => alloc.text(i.to_string()), + U128(i) => alloc.text(i.to_string()), Bit(true) => alloc.text("True"), Bit(false) => alloc.text("False"), Byte(b) => alloc.text(b.to_string()), diff --git a/reporting/src/report.rs b/reporting/src/report.rs index 07a7cbfca7..c74de2e0ff 100644 --- a/reporting/src/report.rs +++ b/reporting/src/report.rs @@ -632,6 +632,39 @@ impl<'a> RocDocAllocator<'a> { self.text(format!("{}", ident.as_inline_str())) .annotate(Annotation::Symbol) } + + pub fn int_literal(&'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::()) + } } #[derive(Copy, Clone)] diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index e81c0919a5..8535556e6c 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -3409,15 +3409,15 @@ mod test_reporting { report_problem_as( indoc!( 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 - l = -0x8FFF_FFFF_FFFF_FFFF + h = 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF + l = -0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF - minlit = -9_223_372_036_854_775_808 - maxlit = 9_223_372_036_854_775_807 + minlit = -170_141_183_460_469_231_731_687_303_715_884_105_728 + maxlit = 340_282_366_920_938_463_463_374_607_431_768_211_455 x + y + h + l + minlit + maxlit "# @@ -3428,11 +3428,11 @@ mod test_reporting { 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 - −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. + The largest number representable in Roc is the maximum U128 value, + 340_282_366_920_938_463_463_374_607_431_768_211_455. Tip: Learn more about number literals at TODO @@ -3440,11 +3440,11 @@ mod test_reporting { 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 - −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. + The smallest number representable in Roc is the minimum I128 value, + -170_141_183_460_469_231_731_687_303_715_884_105_728. Tip: Learn more about number literals at TODO @@ -3452,11 +3452,11 @@ mod test_reporting { 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 - −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. + The largest number representable in Roc is the maximum U128 value, + 340_282_366_920_938_463_463_374_607_431_768_211_455. Tip: Learn more about number literals at TODO @@ -3464,11 +3464,11 @@ mod test_reporting { 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 - −9_223_372_036_854_775_808 and 9_223_372_036_854_775_807. + The smallest number representable in Roc is the minimum I128 value, + -170_141_183_460_469_231_731_687_303_715_884_105_728. 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. + "# + ), + ) + } }