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::expr::{self, AnnotatedMark, ClosureData, Expr::*, IntValue};
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 roc_collections::all::SendMap;
use roc_module::called_via::CalledVia;
@ -1577,7 +1577,7 @@ fn str_to_num(symbol: Symbol, var_store: &mut VarStore) -> Def {
errorcode_var,
Variable::UNSIGNED8,
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,
Variable::NATURAL,
0,
IntBound::Exact(IntWidth::Nat),
IntBound::Exact(IntLitWidth::Nat),
);
let clos = Closure(ClosureData {

View File

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

View File

@ -1,7 +1,7 @@
use arrayvec::ArrayVec;
use roc_can::constraint::{Constraint, Constraints};
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_region::all::Region;
use roc_types::num::NumericRange;
@ -23,7 +23,7 @@ pub fn add_numeric_bound_constr(
let range = bound.numeric_bound();
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 {
NumericBound::None => {
@ -41,7 +41,7 @@ pub fn add_numeric_bound_constr(
total_num_type
}
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 because_suffix =
constraints.equal_types(total_num_type.clone(), expected, category, region);
@ -330,6 +330,6 @@ impl TypedNumericBound for NumBound {
pub enum NumericBound {
None,
FloatExact(FloatWidth),
IntExact(IntWidth),
IntExact(IntLitWidth),
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
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NumericRange {
IntAtLeastSigned(IntWidth),
IntAtLeastEitherSign(IntWidth),
NumAtLeastSigned(IntWidth),
NumAtLeastEitherSign(IntWidth),
IntAtLeastSigned(IntLitWidth),
IntAtLeastEitherSign(IntLitWidth),
NumAtLeastSigned(IntLitWidth),
NumAtLeastEitherSign(IntLitWidth),
}
impl NumericRange {
pub fn contains_symbol(&self, symbol: Symbol) -> Option<bool> {
let contains = match symbol {
Symbol::NUM_I8 => self.contains_int_width(IntWidth::I8),
Symbol::NUM_U8 => self.contains_int_width(IntWidth::U8),
Symbol::NUM_I16 => self.contains_int_width(IntWidth::I16),
Symbol::NUM_U16 => self.contains_int_width(IntWidth::U16),
Symbol::NUM_I32 => self.contains_int_width(IntWidth::I32),
Symbol::NUM_U32 => self.contains_int_width(IntWidth::U32),
Symbol::NUM_I64 => self.contains_int_width(IntWidth::I64),
Symbol::NUM_NAT => self.contains_int_width(IntWidth::Nat),
Symbol::NUM_U64 => self.contains_int_width(IntWidth::U64),
Symbol::NUM_I128 => self.contains_int_width(IntWidth::I128),
Symbol::NUM_U128 => self.contains_int_width(IntWidth::U128),
Symbol::NUM_I8 => self.contains_int_width(IntLitWidth::I8),
Symbol::NUM_U8 => self.contains_int_width(IntLitWidth::U8),
Symbol::NUM_I16 => self.contains_int_width(IntLitWidth::I16),
Symbol::NUM_U16 => self.contains_int_width(IntLitWidth::U16),
Symbol::NUM_I32 => self.contains_int_width(IntLitWidth::I32),
Symbol::NUM_U32 => self.contains_int_width(IntLitWidth::U32),
Symbol::NUM_I64 => self.contains_int_width(IntLitWidth::I64),
Symbol::NUM_NAT => self.contains_int_width(IntLitWidth::Nat),
Symbol::NUM_U64 => self.contains_int_width(IntLitWidth::U64),
Symbol::NUM_I128 => self.contains_int_width(IntLitWidth::I128),
Symbol::NUM_U128 => self.contains_int_width(IntLitWidth::U128),
Symbol::NUM_DEC => self.contains_float_width(FloatWidth::Dec),
Symbol::NUM_F32 => self.contains_float_width(FloatWidth::F32),
@ -48,7 +48,7 @@ impl NumericRange {
true
}
fn contains_int_width(&self, width: IntWidth) -> bool {
fn contains_int_width(&self, width: IntLitWidth) -> bool {
use NumericRange::*;
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
}
fn width(&self) -> IntWidth {
fn width(&self) -> IntLitWidth {
use NumericRange::*;
match self {
IntAtLeastSigned(w)
@ -83,7 +83,7 @@ impl NumericRange {
pub fn intersection(&self, other: &Self) -> Option<Self> {
use NumericRange::*;
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
(IntAtLeastSigned(_), _) | (_, IntAtLeastSigned(_)) => IntAtLeastSigned,
// 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 {
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 end = SIGNED_VARIABLES.len() - 3;
&SIGNED_VARIABLES[start..end]
}
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 end = ALL_VARIABLES.len() - 3;
&ALL_VARIABLES[start..end]
}
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();
&SIGNED_VARIABLES[start..]
}
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();
&ALL_VARIABLES[start..]
@ -150,7 +150,7 @@ enum IntSignedness {
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum IntWidth {
pub enum IntLitWidth {
U8,
U16,
U32,
@ -162,13 +162,21 @@ pub enum IntWidth {
I64,
I128,
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.
fn signedness_and_width(&self) -> (IntSignedness, u32) {
use IntLitWidth::*;
use IntSignedness::*;
use IntWidth::*;
match self {
U8 => (Unsigned, 8),
U16 => (Unsigned, 16),
@ -180,13 +188,16 @@ impl IntWidth {
I32 => (Signed, 32),
I64 => (Signed, 64),
I128 => (Signed, 128),
// TODO: this is platform specific!
// TODO: Nat is platform specific!
Nat => (Unsigned, 64),
F32 => (Signed, 24),
F64 => (Signed, 53),
Dec => (Signed, 128),
}
}
pub fn type_str(&self) -> &'static str {
use IntWidth::*;
use IntLitWidth::*;
match self {
U8 => "U8",
U16 => "U16",
@ -199,11 +210,14 @@ impl IntWidth {
I64 => "I64",
I128 => "I128",
Nat => "Nat",
F32 => "F32",
F64 => "F64",
Dec => "Dec",
}
}
pub fn max_value(&self) -> u128 {
use IntWidth::*;
use IntLitWidth::*;
match self {
U8 => u8::MAX as u128,
U16 => u16::MAX as u128,
@ -217,11 +231,17 @@ impl IntWidth {
I128 => i128::MAX as u128,
// TODO: this is platform specific!
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 {
use IntWidth::*;
use IntLitWidth::*;
match self {
U8 | U16 | U32 | U64 | U128 | Nat => 0,
I8 => i8::MIN as i128,
@ -229,6 +249,12 @@ impl IntWidth {
I32 => i32::MIN as i128,
I64 => i64::MIN as i128,
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.
None,
/// Must have an exact width.
Exact(IntWidth),
Exact(IntLitWidth),
/// 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)]
@ -311,23 +340,26 @@ pub enum NumBound {
/// Must be an integer of a certain size, or any float.
AtLeastIntOrFloat {
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 {
IntWidth::U8 => Variable::U8,
IntWidth::U16 => Variable::U16,
IntWidth::U32 => Variable::U32,
IntWidth::U64 => Variable::U64,
IntWidth::U128 => Variable::U128,
IntWidth::I8 => Variable::I8,
IntWidth::I16 => Variable::I16,
IntWidth::I32 => Variable::I32,
IntWidth::I64 => Variable::I64,
IntWidth::I128 => Variable::I128,
IntWidth::Nat => Variable::NAT,
IntLitWidth::U8 => Variable::U8,
IntLitWidth::U16 => Variable::U16,
IntLitWidth::U32 => Variable::U32,
IntLitWidth::U64 => Variable::U64,
IntLitWidth::U128 => Variable::U128,
IntLitWidth::I8 => Variable::I8,
IntLitWidth::I16 => Variable::I16,
IntLitWidth::I32 => Variable::I32,
IntLitWidth::I64 => Variable::I64,
IntLitWidth::I128 => Variable::I128,
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::I16,
Variable::U16,
Variable::F32,
Variable::I32,
Variable::U32,
Variable::F64,
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::I128,
Variable::U128,
Variable::F32,
Variable::F64,
Variable::DEC,
Variable::U128,
];
const SIGNED_VARIABLES: &[Variable] = &[
Variable::I8,
Variable::I16,
Variable::F32,
Variable::I32,
Variable::F64,
Variable::I64,
Variable::I128,
Variable::F32,
Variable::F64,
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:
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:
@ -7223,7 +7223,7 @@ All branches in an `if` must have the same type!
This `a` value is a:
I64, I128, F32, F64, or Dec
F64, I64, I128, or Dec
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:
I64, I128, F32, F64, or Dec
F64, I64, I128, or Dec
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:
I8, I16, I32, I64, I128, F32, F64, or Dec
I8, I16, F32, I32, F64, I64, I128, or Dec
But the branch patterns have type:
@ -9461,4 +9461,29 @@ All branches in an `if` must have the same type!
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
"###
);
}