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 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),

View file

@ -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 {

View file

@ -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<NumWidth>) -> Expr {
Num(num_var, i.to_string().into_boxed_str(), i, bound)
fn num<I: Into<i128>>(num_var: Variable, i: I, bound: NumericBound<NumWidth>) -> Expr {
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_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<str>, i64, NumericBound<NumWidth>),
Num(Variable, Box<str>, IntValue, NumericBound<NumWidth>),
// 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>),
Str(Box<str>),
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),
};

View file

@ -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<IntWidth>), (&str, IntErrorKind)>,
result: Result<(&str, IntValue, NumericBound<IntWidth>), (&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<IntWidth>),
Int(IntValue, NumericBound<IntWidth>),
Float(f64, NumericBound<FloatWidth>),
UnknownNum(i64),
UnknownNum(IntValue),
}
#[inline(always)]
@ -116,7 +111,7 @@ pub fn finish_parsing_num(raw: &str) -> Result<ParsedNumResult, (&str, IntErrorK
// Ignore underscores.
let radix = 10;
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
Ok(match bound {
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))
}
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,
base: Base,
is_negative: bool,
) -> Result<(i64, NumericBound<IntWidth>), (&str, IntErrorKind)> {
) -> Result<(IntValue, NumericBound<IntWidth>), (&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::<i64>(format!("-{}", raw.replace("_", "")).as_str(), radix)
from_str_radix(format!("-{}", raw.replace("_", "")).as_str(), radix)
} 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)| {
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<FloatWidth>), (&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<NumWidth>, &[u8]) {
fn parse_literal_suffix(num_str: &str) -> (Option<NumWidth>, &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<NumWidth>, &[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<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>(
fn from_str_radix(
src: &str,
radix: u32,
) -> Result<(T, NumericBound<NumWidth>), ParseIntError> {
) -> Result<(IntValue, NumericBound<NumWidth>), IntErrorKind> {
use self::IntErrorKind::*;
use self::ParseIntError as PIE;
assert!(
(2..=36).contains(&radix),
@ -274,65 +233,119 @@ fn from_str_radix<T: FromStrRadixHelper>(
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
}
}

View file

@ -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<Loc<RecordDestruct>>,
},
NumLiteral(Variable, Box<str>, i64, NumericBound<NumWidth>),
IntLiteral(Variable, Variable, Box<str>, i64, NumericBound<IntWidth>),
NumLiteral(Variable, Box<str>, IntValue, NumericBound<NumWidth>),
IntLiteral(
Variable,
Variable,
Box<str>,
IntValue,
NumericBound<IntWidth>,
),
FloatLiteral(Variable, Variable, Box<str>, f64, NumericBound<FloatWidth>),
StrLiteral(Box<str>),
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)
}
},

View file

@ -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]

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()
}
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>,

View file

@ -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, &[])
}
}
}

View file

@ -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<str>),
@ -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);

View file

@ -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())),

View file

@ -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,

View file

@ -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.

View file

@ -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,

View file

@ -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()),

View file

@ -632,6 +632,39 @@ impl<'a> RocDocAllocator<'a> {
self.text(format!("{}", ident.as_inline_str()))
.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)]

View file

@ -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.
"#
),
)
}
}