Support ordering floats in numeric literal bounds

This commit is contained in:
Ayaz Hafiz 2022-07-04 12:20:39 -04:00 committed by ayazhafiz
parent 1905e1815d
commit 5a18490050
No known key found for this signature in database
GPG Key ID: B443F7A3030C9AED
5 changed files with 147 additions and 82 deletions

View File

@ -1,7 +1,7 @@
use crate::def::Def; use crate::def::Def;
use crate::expr::{self, AnnotatedMark, ClosureData, Expr::*, IntValue}; use crate::expr::{self, AnnotatedMark, ClosureData, Expr::*, IntValue};
use crate::expr::{Expr, Field, Recursive}; use crate::expr::{Expr, Field, Recursive};
use crate::num::{FloatBound, IntBound, IntWidth, NumBound}; use crate::num::{FloatBound, IntBound, IntLitWidth, NumBound};
use crate::pattern::Pattern; use crate::pattern::Pattern;
use roc_collections::all::SendMap; use roc_collections::all::SendMap;
use roc_module::called_via::CalledVia; use roc_module::called_via::CalledVia;
@ -1577,7 +1577,7 @@ fn str_to_num(symbol: Symbol, var_store: &mut VarStore) -> Def {
errorcode_var, errorcode_var,
Variable::UNSIGNED8, Variable::UNSIGNED8,
0, 0,
IntBound::Exact(IntWidth::U8), IntBound::Exact(IntLitWidth::U8),
), ),
), ),
], ],
@ -2175,7 +2175,7 @@ fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def {
index_var, index_var,
Variable::NATURAL, Variable::NATURAL,
0, 0,
IntBound::Exact(IntWidth::Nat), IntBound::Exact(IntLitWidth::Nat),
); );
let clos = Closure(ClosureData { let clos = Closure(ClosureData {

View File

@ -5,7 +5,7 @@ use roc_problem::can::Problem;
use roc_problem::can::RuntimeError::*; use roc_problem::can::RuntimeError::*;
use roc_problem::can::{FloatErrorKind, IntErrorKind}; use roc_problem::can::{FloatErrorKind, IntErrorKind};
use roc_region::all::Region; use roc_region::all::Region;
pub use roc_types::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumBound, SignDemand}; pub use roc_types::num::{FloatBound, FloatWidth, IntBound, IntLitWidth, NumBound, SignDemand};
use roc_types::subs::VarStore; use roc_types::subs::VarStore;
use std::str; use std::str;
@ -174,7 +174,7 @@ pub fn finish_parsing_float(raw: &str) -> Result<(&str, f64, FloatBound), (&str,
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum ParsedWidth { enum ParsedWidth {
Int(IntWidth), Int(IntLitWidth),
Float(FloatWidth), Float(FloatWidth),
} }
@ -188,17 +188,17 @@ fn parse_literal_suffix(num_str: &str) -> (Option<ParsedWidth>, &str) {
} }
parse_num_suffix! { parse_num_suffix! {
"u8", ParsedWidth::Int(IntWidth::U8) "u8", ParsedWidth::Int(IntLitWidth::U8)
"u16", ParsedWidth::Int(IntWidth::U16) "u16", ParsedWidth::Int(IntLitWidth::U16)
"u32", ParsedWidth::Int(IntWidth::U32) "u32", ParsedWidth::Int(IntLitWidth::U32)
"u64", ParsedWidth::Int(IntWidth::U64) "u64", ParsedWidth::Int(IntLitWidth::U64)
"u128", ParsedWidth::Int(IntWidth::U128) "u128", ParsedWidth::Int(IntLitWidth::U128)
"i8", ParsedWidth::Int(IntWidth::I8) "i8", ParsedWidth::Int(IntLitWidth::I8)
"i16", ParsedWidth::Int(IntWidth::I16) "i16", ParsedWidth::Int(IntLitWidth::I16)
"i32", ParsedWidth::Int(IntWidth::I32) "i32", ParsedWidth::Int(IntLitWidth::I32)
"i64", ParsedWidth::Int(IntWidth::I64) "i64", ParsedWidth::Int(IntLitWidth::I64)
"i128", ParsedWidth::Int(IntWidth::I128) "i128", ParsedWidth::Int(IntLitWidth::I128)
"nat", ParsedWidth::Int(IntWidth::Nat) "nat", ParsedWidth::Int(IntLitWidth::Nat)
"dec", ParsedWidth::Float(FloatWidth::Dec) "dec", ParsedWidth::Float(FloatWidth::Dec)
"f32", ParsedWidth::Float(FloatWidth::F32) "f32", ParsedWidth::Float(FloatWidth::F32)
"f64", ParsedWidth::Float(FloatWidth::F64) "f64", ParsedWidth::Float(FloatWidth::F64)
@ -256,9 +256,9 @@ fn from_str_radix(src: &str, radix: u32) -> Result<ParsedNumResult, IntErrorKind
IntValue::I128(bytes) => { IntValue::I128(bytes) => {
let num = i128::from_ne_bytes(bytes); let num = i128::from_ne_bytes(bytes);
(lower_bound_of_int(num), num < 0) (lower_bound_of_int_literal(num), num < 0)
} }
IntValue::U128(_) => (IntWidth::U128, false), IntValue::U128(_) => (IntLitWidth::U128, false),
}; };
match opt_exact_bound { match opt_exact_bound {
@ -314,8 +314,8 @@ fn from_str_radix(src: &str, radix: u32) -> Result<ParsedNumResult, IntErrorKind
} }
} }
fn lower_bound_of_int(result: i128) -> IntWidth { fn lower_bound_of_int_literal(result: i128) -> IntLitWidth {
use IntWidth::*; use IntLitWidth::*;
if result >= 0 { if result >= 0 {
// Positive // Positive
let result = result as u128; let result = result as u128;
@ -323,12 +323,16 @@ fn lower_bound_of_int(result: i128) -> IntWidth {
I128 I128
} else if result > I64.max_value() { } else if result > I64.max_value() {
U64 U64
} else if result > U32.max_value() { } else if result > F64.max_value() {
I64 I64
} else if result > U32.max_value() {
F64
} else if result > I32.max_value() { } else if result > I32.max_value() {
U32 U32
} else if result > U16.max_value() { } else if result > F32.max_value() {
I32 I32
} else if result > U16.max_value() {
F32
} else if result > I16.max_value() { } else if result > I16.max_value() {
U16 U16
} else if result > U8.max_value() { } else if result > U8.max_value() {
@ -342,10 +346,14 @@ fn lower_bound_of_int(result: i128) -> IntWidth {
// Negative // Negative
if result < I64.min_value() { if result < I64.min_value() {
I128 I128
} else if result < I32.min_value() { } else if result < F64.min_value() {
I64 I64
} else if result < I16.min_value() { } else if result < I32.min_value() {
F64
} else if result < F32.min_value() {
I32 I32
} else if result < I16.min_value() {
F32
} else if result < I8.min_value() { } else if result < I8.min_value() {
I16 I16
} else { } else {

View File

@ -1,7 +1,7 @@
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use roc_can::constraint::{Constraint, Constraints}; use roc_can::constraint::{Constraint, Constraints};
use roc_can::expected::Expected::{self, *}; use roc_can::expected::Expected::{self, *};
use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumBound, SignDemand}; use roc_can::num::{FloatBound, FloatWidth, IntBound, IntLitWidth, NumBound, SignDemand};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::Region; use roc_region::all::Region;
use roc_types::num::NumericRange; use roc_types::num::NumericRange;
@ -23,7 +23,7 @@ pub fn add_numeric_bound_constr(
let range = bound.numeric_bound(); let range = bound.numeric_bound();
let total_num_type = num_type; let total_num_type = num_type;
use roc_types::num::{float_width_to_variable, int_width_to_variable}; use roc_types::num::{float_width_to_variable, int_lit_width_to_variable};
match range { match range {
NumericBound::None => { NumericBound::None => {
@ -41,7 +41,7 @@ pub fn add_numeric_bound_constr(
total_num_type total_num_type
} }
NumericBound::IntExact(width) => { NumericBound::IntExact(width) => {
let actual_type = Variable(int_width_to_variable(width)); let actual_type = Variable(int_lit_width_to_variable(width));
let expected = Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region); let expected = Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region);
let because_suffix = let because_suffix =
constraints.equal_types(total_num_type.clone(), expected, category, region); constraints.equal_types(total_num_type.clone(), expected, category, region);
@ -330,6 +330,6 @@ impl TypedNumericBound for NumBound {
pub enum NumericBound { pub enum NumericBound {
None, None,
FloatExact(FloatWidth), FloatExact(FloatWidth),
IntExact(IntWidth), IntExact(IntLitWidth),
Range(NumericRange), Range(NumericRange),
} }

View File

@ -5,26 +5,26 @@ use roc_module::symbol::Symbol;
/// e.g. `-5` cannot be unsigned, and 300 does not fit in a U8 /// e.g. `-5` cannot be unsigned, and 300 does not fit in a U8
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NumericRange { pub enum NumericRange {
IntAtLeastSigned(IntWidth), IntAtLeastSigned(IntLitWidth),
IntAtLeastEitherSign(IntWidth), IntAtLeastEitherSign(IntLitWidth),
NumAtLeastSigned(IntWidth), NumAtLeastSigned(IntLitWidth),
NumAtLeastEitherSign(IntWidth), NumAtLeastEitherSign(IntLitWidth),
} }
impl NumericRange { impl NumericRange {
pub fn contains_symbol(&self, symbol: Symbol) -> Option<bool> { pub fn contains_symbol(&self, symbol: Symbol) -> Option<bool> {
let contains = match symbol { let contains = match symbol {
Symbol::NUM_I8 => self.contains_int_width(IntWidth::I8), Symbol::NUM_I8 => self.contains_int_width(IntLitWidth::I8),
Symbol::NUM_U8 => self.contains_int_width(IntWidth::U8), Symbol::NUM_U8 => self.contains_int_width(IntLitWidth::U8),
Symbol::NUM_I16 => self.contains_int_width(IntWidth::I16), Symbol::NUM_I16 => self.contains_int_width(IntLitWidth::I16),
Symbol::NUM_U16 => self.contains_int_width(IntWidth::U16), Symbol::NUM_U16 => self.contains_int_width(IntLitWidth::U16),
Symbol::NUM_I32 => self.contains_int_width(IntWidth::I32), Symbol::NUM_I32 => self.contains_int_width(IntLitWidth::I32),
Symbol::NUM_U32 => self.contains_int_width(IntWidth::U32), Symbol::NUM_U32 => self.contains_int_width(IntLitWidth::U32),
Symbol::NUM_I64 => self.contains_int_width(IntWidth::I64), Symbol::NUM_I64 => self.contains_int_width(IntLitWidth::I64),
Symbol::NUM_NAT => self.contains_int_width(IntWidth::Nat), Symbol::NUM_NAT => self.contains_int_width(IntLitWidth::Nat),
Symbol::NUM_U64 => self.contains_int_width(IntWidth::U64), Symbol::NUM_U64 => self.contains_int_width(IntLitWidth::U64),
Symbol::NUM_I128 => self.contains_int_width(IntWidth::I128), Symbol::NUM_I128 => self.contains_int_width(IntLitWidth::I128),
Symbol::NUM_U128 => self.contains_int_width(IntWidth::U128), Symbol::NUM_U128 => self.contains_int_width(IntLitWidth::U128),
Symbol::NUM_DEC => self.contains_float_width(FloatWidth::Dec), Symbol::NUM_DEC => self.contains_float_width(FloatWidth::Dec),
Symbol::NUM_F32 => self.contains_float_width(FloatWidth::F32), Symbol::NUM_F32 => self.contains_float_width(FloatWidth::F32),
@ -48,7 +48,7 @@ impl NumericRange {
true true
} }
fn contains_int_width(&self, width: IntWidth) -> bool { fn contains_int_width(&self, width: IntLitWidth) -> bool {
use NumericRange::*; use NumericRange::*;
let (range_signedness, at_least_width) = match self { let (range_signedness, at_least_width) = match self {
@ -68,7 +68,7 @@ impl NumericRange {
width.signedness_and_width().1 >= at_least_width.signedness_and_width().1 width.signedness_and_width().1 >= at_least_width.signedness_and_width().1
} }
fn width(&self) -> IntWidth { fn width(&self) -> IntLitWidth {
use NumericRange::*; use NumericRange::*;
match self { match self {
IntAtLeastSigned(w) IntAtLeastSigned(w)
@ -83,7 +83,7 @@ impl NumericRange {
pub fn intersection(&self, other: &Self) -> Option<Self> { pub fn intersection(&self, other: &Self) -> Option<Self> {
use NumericRange::*; use NumericRange::*;
let (left, right) = (self.width(), other.width()); let (left, right) = (self.width(), other.width());
let constructor: fn(IntWidth) -> NumericRange = match (self, other) { let constructor: fn(IntLitWidth) -> NumericRange = match (self, other) {
// Matching against a signed int, the intersection must also be a signed int // Matching against a signed int, the intersection must also be a signed int
(IntAtLeastSigned(_), _) | (_, IntAtLeastSigned(_)) => IntAtLeastSigned, (IntAtLeastSigned(_), _) | (_, IntAtLeastSigned(_)) => IntAtLeastSigned,
// It's a signed number, but also an int, so the intersection must be a signed int // It's a signed number, but also an int, so the intersection must be a signed int
@ -114,27 +114,27 @@ impl NumericRange {
match self { match self {
IntAtLeastSigned(width) => { IntAtLeastSigned(width) => {
let target = int_width_to_variable(*width); let target = int_lit_width_to_variable(*width);
let start = SIGNED_VARIABLES.iter().position(|v| *v == target).unwrap(); let start = SIGNED_VARIABLES.iter().position(|v| *v == target).unwrap();
let end = SIGNED_VARIABLES.len() - 3; let end = SIGNED_VARIABLES.len() - 3;
&SIGNED_VARIABLES[start..end] &SIGNED_VARIABLES[start..end]
} }
IntAtLeastEitherSign(width) => { IntAtLeastEitherSign(width) => {
let target = int_width_to_variable(*width); let target = int_lit_width_to_variable(*width);
let start = ALL_VARIABLES.iter().position(|v| *v == target).unwrap(); let start = ALL_VARIABLES.iter().position(|v| *v == target).unwrap();
let end = ALL_VARIABLES.len() - 3; let end = ALL_VARIABLES.len() - 3;
&ALL_VARIABLES[start..end] &ALL_VARIABLES[start..end]
} }
NumAtLeastSigned(width) => { NumAtLeastSigned(width) => {
let target = int_width_to_variable(*width); let target = int_lit_width_to_variable(*width);
let start = SIGNED_VARIABLES.iter().position(|v| *v == target).unwrap(); let start = SIGNED_VARIABLES.iter().position(|v| *v == target).unwrap();
&SIGNED_VARIABLES[start..] &SIGNED_VARIABLES[start..]
} }
NumAtLeastEitherSign(width) => { NumAtLeastEitherSign(width) => {
let target = int_width_to_variable(*width); let target = int_lit_width_to_variable(*width);
let start = ALL_VARIABLES.iter().position(|v| *v == target).unwrap(); let start = ALL_VARIABLES.iter().position(|v| *v == target).unwrap();
&ALL_VARIABLES[start..] &ALL_VARIABLES[start..]
@ -150,7 +150,7 @@ enum IntSignedness {
} }
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum IntWidth { pub enum IntLitWidth {
U8, U8,
U16, U16,
U32, U32,
@ -162,13 +162,21 @@ pub enum IntWidth {
I64, I64,
I128, I128,
Nat, Nat,
// An int literal can be promoted to an f32/f64/Dec if appropriate. The respective widths for
// integers that can be stored in these float types without losing precision are:
// f32: +/- 2^24
// f64: +/- 2^53
// dec: Int128::MAX/Int128::MIN
F32,
F64,
Dec,
} }
impl IntWidth { impl IntLitWidth {
/// Returns the `IntSignedness` and bit width of a variant. /// Returns the `IntSignedness` and bit width of a variant.
fn signedness_and_width(&self) -> (IntSignedness, u32) { fn signedness_and_width(&self) -> (IntSignedness, u32) {
use IntLitWidth::*;
use IntSignedness::*; use IntSignedness::*;
use IntWidth::*;
match self { match self {
U8 => (Unsigned, 8), U8 => (Unsigned, 8),
U16 => (Unsigned, 16), U16 => (Unsigned, 16),
@ -180,13 +188,16 @@ impl IntWidth {
I32 => (Signed, 32), I32 => (Signed, 32),
I64 => (Signed, 64), I64 => (Signed, 64),
I128 => (Signed, 128), I128 => (Signed, 128),
// TODO: this is platform specific! // TODO: Nat is platform specific!
Nat => (Unsigned, 64), Nat => (Unsigned, 64),
F32 => (Signed, 24),
F64 => (Signed, 53),
Dec => (Signed, 128),
} }
} }
pub fn type_str(&self) -> &'static str { pub fn type_str(&self) -> &'static str {
use IntWidth::*; use IntLitWidth::*;
match self { match self {
U8 => "U8", U8 => "U8",
U16 => "U16", U16 => "U16",
@ -199,11 +210,14 @@ impl IntWidth {
I64 => "I64", I64 => "I64",
I128 => "I128", I128 => "I128",
Nat => "Nat", Nat => "Nat",
F32 => "F32",
F64 => "F64",
Dec => "Dec",
} }
} }
pub fn max_value(&self) -> u128 { pub fn max_value(&self) -> u128 {
use IntWidth::*; use IntLitWidth::*;
match self { match self {
U8 => u8::MAX as u128, U8 => u8::MAX as u128,
U16 => u16::MAX as u128, U16 => u16::MAX as u128,
@ -217,11 +231,17 @@ impl IntWidth {
I128 => i128::MAX as u128, I128 => i128::MAX as u128,
// TODO: this is platform specific! // TODO: this is platform specific!
Nat => u64::MAX as u128, Nat => u64::MAX as u128,
// Max int value without losing precision: 2^24
F32 => 16_777_216,
// Max int value without losing precision: 2^53
F64 => 9_007_199_254_740_992,
// Max int value without losing precision: I128::MAX
Dec => i128::MAX as u128,
} }
} }
pub fn min_value(&self) -> i128 { pub fn min_value(&self) -> i128 {
use IntWidth::*; use IntLitWidth::*;
match self { match self {
U8 | U16 | U32 | U64 | U128 | Nat => 0, U8 | U16 | U32 | U64 | U128 | Nat => 0,
I8 => i8::MIN as i128, I8 => i8::MIN as i128,
@ -229,6 +249,12 @@ impl IntWidth {
I32 => i32::MIN as i128, I32 => i32::MIN as i128,
I64 => i64::MIN as i128, I64 => i64::MIN as i128,
I128 => i128::MIN, I128 => i128::MIN,
// Min int value without losing precision: -2^24
F32 => -16_777_216,
// Min int value without losing precision: -2^53
F64 => -9_007_199_254_740_992,
// Min int value without losing precision: I128::MIN
Dec => i128::MIN,
} }
} }
@ -294,9 +320,12 @@ pub enum IntBound {
/// There is no bound on the width. /// There is no bound on the width.
None, None,
/// Must have an exact width. /// Must have an exact width.
Exact(IntWidth), Exact(IntLitWidth),
/// Must have a certain sign and a minimum width. /// Must have a certain sign and a minimum width.
AtLeast { sign: SignDemand, width: IntWidth }, AtLeast {
sign: SignDemand,
width: IntLitWidth,
},
} }
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
@ -311,23 +340,26 @@ pub enum NumBound {
/// Must be an integer of a certain size, or any float. /// Must be an integer of a certain size, or any float.
AtLeastIntOrFloat { AtLeastIntOrFloat {
sign: SignDemand, sign: SignDemand,
width: IntWidth, width: IntLitWidth,
}, },
} }
pub const fn int_width_to_variable(w: IntWidth) -> Variable { pub const fn int_lit_width_to_variable(w: IntLitWidth) -> Variable {
match w { match w {
IntWidth::U8 => Variable::U8, IntLitWidth::U8 => Variable::U8,
IntWidth::U16 => Variable::U16, IntLitWidth::U16 => Variable::U16,
IntWidth::U32 => Variable::U32, IntLitWidth::U32 => Variable::U32,
IntWidth::U64 => Variable::U64, IntLitWidth::U64 => Variable::U64,
IntWidth::U128 => Variable::U128, IntLitWidth::U128 => Variable::U128,
IntWidth::I8 => Variable::I8, IntLitWidth::I8 => Variable::I8,
IntWidth::I16 => Variable::I16, IntLitWidth::I16 => Variable::I16,
IntWidth::I32 => Variable::I32, IntLitWidth::I32 => Variable::I32,
IntWidth::I64 => Variable::I64, IntLitWidth::I64 => Variable::I64,
IntWidth::I128 => Variable::I128, IntLitWidth::I128 => Variable::I128,
IntWidth::Nat => Variable::NAT, IntLitWidth::Nat => Variable::NAT,
IntLitWidth::F32 => Variable::F32,
IntLitWidth::F64 => Variable::F64,
IntLitWidth::Dec => Variable::DEC,
} }
} }
@ -344,25 +376,25 @@ const ALL_VARIABLES: &[Variable] = &[
Variable::U8, Variable::U8,
Variable::I16, Variable::I16,
Variable::U16, Variable::U16,
Variable::F32,
Variable::I32, Variable::I32,
Variable::U32, Variable::U32,
Variable::F64,
Variable::I64, Variable::I64,
Variable::NAT, // FIXME: Nat's order here depends on the platfor, Variable::NAT, // FIXME: Nat's order here depends on the platform
Variable::U64, Variable::U64,
Variable::I128, Variable::I128,
Variable::U128,
Variable::F32,
Variable::F64,
Variable::DEC, Variable::DEC,
Variable::U128,
]; ];
const SIGNED_VARIABLES: &[Variable] = &[ const SIGNED_VARIABLES: &[Variable] = &[
Variable::I8, Variable::I8,
Variable::I16, Variable::I16,
Variable::F32,
Variable::I32, Variable::I32,
Variable::F64,
Variable::I64, Variable::I64,
Variable::I128, Variable::I128,
Variable::F32,
Variable::F64,
Variable::DEC, Variable::DEC,
]; ];

View File

@ -7197,7 +7197,7 @@ All branches in an `if` must have the same type!
This argument is a number of type: This argument is a number of type:
I8, I16, I32, I64, I128, F32, F64, or Dec I8, I16, F32, I32, F64, I64, I128, or Dec
But `get` needs the 2nd argument to be: But `get` needs the 2nd argument to be:
@ -7223,7 +7223,7 @@ All branches in an `if` must have the same type!
This `a` value is a: This `a` value is a:
I64, I128, F32, F64, or Dec F64, I64, I128, or Dec
But `get` needs the 2nd argument to be: But `get` needs the 2nd argument to be:
@ -7250,7 +7250,7 @@ All branches in an `if` must have the same type!
This `b` value is a: This `b` value is a:
I64, I128, F32, F64, or Dec F64, I64, I128, or Dec
But `get` needs the 2nd argument to be: But `get` needs the 2nd argument to be:
@ -7278,7 +7278,7 @@ All branches in an `if` must have the same type!
The `when` condition is a number of type: The `when` condition is a number of type:
I8, I16, I32, I64, I128, F32, F64, or Dec I8, I16, F32, I32, F64, I64, I128, or Dec
But the branch patterns have type: But the branch patterns have type:
@ -9461,4 +9461,29 @@ All branches in an `if` must have the same type!
U128 U128
"### "###
); );
test_report!(
num_literals_cannot_fit_in_same_type,
indoc!(
r#"
170141183460469231731687303715884105728 == -170141183460469231731687303715884105728
"#
),
@r###"
TYPE MISMATCH /code/proj/Main.roc
The 2nd argument to `isEq` is not what I expect:
4 170141183460469231731687303715884105728 == -170141183460469231731687303715884105728
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This argument is a number of type:
I128 or Dec
But `isEq` needs the 2nd argument to be:
U128
"###
);
} }