refactor number bounds

This commit is contained in:
Folkert 2022-05-21 18:53:50 +02:00
parent b46721e43b
commit cb40aab21f
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
7 changed files with 186 additions and 185 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, NumericBound}; use crate::num::{FloatBound, IntBound, IntWidth, 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;
@ -5414,8 +5414,8 @@ fn defn_help(
}) })
} }
fn num_no_bound() -> NumericBound { fn num_no_bound() -> NumBound {
NumericBound::None NumBound::None
} }
fn int_no_bound() -> IntBound { fn int_no_bound() -> IntBound {
@ -5453,7 +5453,7 @@ fn frac(num_var: Variable, precision_var: Variable, f: f64, bound: FloatBound) -
} }
#[inline(always)] #[inline(always)]
fn num<I: Into<i128>>(num_var: Variable, i: I, bound: NumericBound) -> Expr { fn num<I: Into<i128>>(num_var: Variable, i: I, bound: NumBound) -> Expr {
let i = i.into(); let i = i.into();
Num( Num(
num_var, num_var,

View file

@ -5,7 +5,7 @@ use crate::def::{can_defs_with_return, Def};
use crate::env::Env; use crate::env::Env;
use crate::num::{ use crate::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result, finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result,
int_expr_from_result, num_expr_from_result, FloatBound, IntBound, NumericBound, int_expr_from_result, num_expr_from_result, FloatBound, IntBound, NumBound,
}; };
use crate::pattern::{canonicalize_pattern, BindingsFromPattern, Pattern}; use crate::pattern::{canonicalize_pattern, BindingsFromPattern, Pattern};
use crate::procedure::References; use crate::procedure::References;
@ -82,7 +82,7 @@ pub enum Expr {
// Num stores the `a` variable in `Num a`. Not the same as the variable // Num stores the `a` variable in `Num a`. Not the same as the variable
// stored in Int and Float below, which is strictly for better error messages // stored in Int and Float below, which is strictly for better error messages
Num(Variable, Box<str>, IntValue, NumericBound), Num(Variable, Box<str>, IntValue, NumBound),
// Int and Float store a variable to generate better error messages // Int and Float store a variable to generate better error messages
Int(Variable, Variable, Box<str>, IntValue, IntBound), Int(Variable, Variable, Box<str>, IntValue, IntBound),

View file

@ -5,8 +5,9 @@ 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};
use roc_types::subs::VarStore; use roc_types::subs::VarStore;
use std::i64;
use std::str; use std::str;
#[inline(always)] #[inline(always)]
@ -103,7 +104,7 @@ pub fn float_expr_from_result(
pub enum ParsedNumResult { pub enum ParsedNumResult {
Int(IntValue, IntBound), Int(IntValue, IntBound),
Float(f64, FloatBound), Float(f64, FloatBound),
UnknownNum(IntValue, NumericBound), UnknownNum(IntValue, NumBound),
} }
#[inline(always)] #[inline(always)]
@ -139,8 +140,8 @@ pub fn finish_parsing_base(
.and_then(|parsed| match parsed { .and_then(|parsed| match parsed {
ParsedNumResult::Float(..) => Err(IntErrorKind::FloatSuffix), ParsedNumResult::Float(..) => Err(IntErrorKind::FloatSuffix),
ParsedNumResult::Int(val, bound) => Ok((val, bound)), ParsedNumResult::Int(val, bound) => Ok((val, bound)),
ParsedNumResult::UnknownNum(val, NumericBound::None) => Ok((val, IntBound::None)), ParsedNumResult::UnknownNum(val, NumBound::None) => Ok((val, IntBound::None)),
ParsedNumResult::UnknownNum(val, NumericBound::AtLeastIntOrFloat { sign, width }) => { ParsedNumResult::UnknownNum(val, NumBound::AtLeastIntOrFloat { sign, width }) => {
Ok((val, IntBound::AtLeast { sign, width })) Ok((val, IntBound::AtLeast { sign, width }))
} }
}) })
@ -270,7 +271,7 @@ fn from_str_radix(src: &str, radix: u32) -> Result<ParsedNumResult, IntErrorKind
}; };
Ok(ParsedNumResult::UnknownNum( Ok(ParsedNumResult::UnknownNum(
result, result,
NumericBound::AtLeastIntOrFloat { NumBound::AtLeastIntOrFloat {
sign: sign_demand, sign: sign_demand,
width: lower_bound, width: lower_bound,
}, },
@ -352,169 +353,3 @@ fn lower_bound_of_int(result: i128) -> IntWidth {
} }
} }
} }
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum IntSign {
Unsigned,
Signed,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum IntWidth {
U8,
U16,
U32,
U64,
U128,
I8,
I16,
I32,
I64,
I128,
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,
F32,
F64,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SignDemand {
/// Can be signed or unsigned.
NoDemand,
/// Must be signed.
Signed,
}
/// Describes a bound on the width of an integer.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum IntBound {
/// There is no bound on the width.
None,
/// Must have an exact width.
Exact(IntWidth),
/// Must have a certain sign and a minimum width.
AtLeast { sign: SignDemand, width: IntWidth },
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FloatBound {
None,
Exact(FloatWidth),
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum NumericBound {
None,
/// Must be an integer of a certain size, or any float.
AtLeastIntOrFloat {
sign: SignDemand,
width: IntWidth,
},
}

View file

@ -2,8 +2,8 @@ use crate::annotation::freshen_opaque_def;
use crate::env::Env; use crate::env::Env;
use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output}; use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output};
use crate::num::{ use crate::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatBound, IntBound, finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatBound, IntBound, NumBound,
NumericBound, ParsedNumResult, ParsedNumResult,
}; };
use crate::scope::Scope; use crate::scope::Scope;
use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::ident::{Ident, Lowercase, TagName};
@ -55,7 +55,7 @@ pub enum Pattern {
ext_var: Variable, ext_var: Variable,
destructs: Vec<Loc<RecordDestruct>>, destructs: Vec<Loc<RecordDestruct>>,
}, },
NumLiteral(Variable, Box<str>, IntValue, NumericBound), NumLiteral(Variable, Box<str>, IntValue, NumBound),
IntLiteral(Variable, Variable, Box<str>, IntValue, IntBound), IntLiteral(Variable, Variable, Box<str>, IntValue, IntBound),
FloatLiteral(Variable, Variable, Box<str>, f64, FloatBound), FloatLiteral(Variable, Variable, Box<str>, f64, FloatBound),
StrLiteral(Box<str>), StrLiteral(Box<str>),

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, NumericBound, SignDemand}; use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, 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::subs::Variable; use roc_types::subs::Variable;
@ -117,7 +117,7 @@ pub fn num_literal(
num_var: Variable, num_var: Variable,
expected: Expected<Type>, expected: Expected<Type>,
region: Region, region: Region,
bound: NumericBound, bound: NumBound,
) -> Constraint { ) -> Constraint {
let open_number_type = crate::builtins::num_num(Type::Variable(num_var)); let open_number_type = crate::builtins::num_num(Type::Variable(num_var));
@ -338,11 +338,11 @@ impl TypedNumericBound for FloatBound {
} }
} }
impl TypedNumericBound for NumericBound { impl TypedNumericBound for NumBound {
fn bounded_range(&self) -> Vec<Variable> { fn bounded_range(&self) -> Vec<Variable> {
match self { match self {
NumericBound::None => vec![], NumBound::None => vec![],
&NumericBound::AtLeastIntOrFloat { sign, width } => { &NumBound::AtLeastIntOrFloat { sign, width } => {
let mut range = IntBound::AtLeast { sign, width }.bounded_range(); let mut range = IntBound::AtLeast { sign, width }.bounded_range();
range.extend_from_slice(&[Variable::F32, Variable::F64, Variable::DEC]); range.extend_from_slice(&[Variable::F32, Variable::F64, Variable::DEC]);
range range

View file

@ -2,6 +2,7 @@
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant)]
pub mod builtin_aliases; pub mod builtin_aliases;
pub mod num;
pub mod pretty_print; pub mod pretty_print;
pub mod solved_types; pub mod solved_types;
pub mod subs; pub mod subs;

165
compiler/types/src/num.rs Normal file
View file

@ -0,0 +1,165 @@
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum IntSign {
Unsigned,
Signed,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum IntWidth {
U8,
U16,
U32,
U64,
U128,
I8,
I16,
I32,
I64,
I128,
Nat,
}
impl IntWidth {
/// Returns the `IntSign` and bit width of a variant.
pub 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),
}
}
pub 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",
}
}
pub 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,
}
}
pub 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,
F32,
F64,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SignDemand {
/// Can be signed or unsigned.
NoDemand,
/// Must be signed.
Signed,
}
/// Describes a bound on the width of an integer.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum IntBound {
/// There is no bound on the width.
None,
/// Must have an exact width.
Exact(IntWidth),
/// Must have a certain sign and a minimum width.
AtLeast { sign: SignDemand, width: IntWidth },
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FloatBound {
None,
Exact(FloatWidth),
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum NumBound {
None,
/// Must be an integer of a certain size, or any float.
AtLeastIntOrFloat {
sign: SignDemand,
width: IntWidth,
},
}