diff --git a/ast/src/solve_type.rs b/ast/src/solve_type.rs index 5b05fdb518..f4f72e1774 100644 --- a/ast/src/solve_type.rs +++ b/ast/src/solve_type.rs @@ -1849,18 +1849,8 @@ fn deep_copy_var_help( } RangedNumber(typ, vars) => { - let mut new_vars = Vec::with_capacity(vars.len()); - - for var_index in vars { - let var = subs[var_index]; - let new_var = deep_copy_var_help(subs, max_rank, pools, var); - new_vars.push(new_var); - } - - let new_slice = VariableSubsSlice::insert_into_subs(subs, new_vars.drain(..)); - let new_real_type = deep_copy_var_help(subs, max_rank, pools, typ); - let new_content = RangedNumber(new_real_type, new_slice); + let new_content = RangedNumber(new_real_type, vars); subs.set(copy, make_descriptor(new_content)); diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 4b4e775600..cbff764422 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -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, NumericBound}; +use crate::num::{FloatBound, IntBound, IntWidth, NumBound}; use crate::pattern::Pattern; use roc_collections::all::SendMap; use roc_module::called_via::CalledVia; @@ -5414,8 +5414,8 @@ fn defn_help( }) } -fn num_no_bound() -> NumericBound { - NumericBound::None +fn num_no_bound() -> NumBound { + NumBound::None } fn int_no_bound() -> IntBound { @@ -5453,7 +5453,7 @@ fn frac(num_var: Variable, precision_var: Variable, f: f64, bound: FloatBound) - } #[inline(always)] -fn num>(num_var: Variable, i: I, bound: NumericBound) -> Expr { +fn num>(num_var: Variable, i: I, bound: NumBound) -> Expr { let i = i.into(); Num( num_var, diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index e12417879c..685ad54fc3 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -5,7 +5,7 @@ use crate::def::{can_defs_with_return, Def}; use crate::env::Env; use crate::num::{ 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::procedure::References; @@ -82,7 +82,7 @@ pub enum Expr { // 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, IntValue, NumericBound), + Num(Variable, Box, IntValue, NumBound), // Int and Float store a variable to generate better error messages Int(Variable, Variable, Box, IntValue, IntBound), diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index 2437286ab3..9a7041f8b4 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -5,8 +5,9 @@ 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}; use roc_types::subs::VarStore; -use std::i64; + use std::str; #[inline(always)] @@ -103,7 +104,7 @@ pub fn float_expr_from_result( pub enum ParsedNumResult { Int(IntValue, IntBound), Float(f64, FloatBound), - UnknownNum(IntValue, NumericBound), + UnknownNum(IntValue, NumBound), } #[inline(always)] @@ -139,8 +140,8 @@ pub fn finish_parsing_base( .and_then(|parsed| match parsed { ParsedNumResult::Float(..) => Err(IntErrorKind::FloatSuffix), ParsedNumResult::Int(val, bound) => Ok((val, bound)), - ParsedNumResult::UnknownNum(val, NumericBound::None) => Ok((val, IntBound::None)), - ParsedNumResult::UnknownNum(val, NumericBound::AtLeastIntOrFloat { sign, width }) => { + ParsedNumResult::UnknownNum(val, NumBound::None) => Ok((val, IntBound::None)), + ParsedNumResult::UnknownNum(val, NumBound::AtLeastIntOrFloat { sign, width }) => { Ok((val, IntBound::AtLeast { sign, width })) } }) @@ -270,7 +271,7 @@ fn from_str_radix(src: &str, radix: u32) -> Result 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, - }, -} diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index 4baf77b3e1..a55a415133 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -2,8 +2,8 @@ use crate::annotation::freshen_opaque_def; use crate::env::Env; use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output}; use crate::num::{ - finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatBound, IntBound, - NumericBound, ParsedNumResult, + finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatBound, IntBound, NumBound, + ParsedNumResult, }; use crate::scope::Scope; use roc_module::ident::{Ident, Lowercase, TagName}; @@ -55,7 +55,7 @@ pub enum Pattern { ext_var: Variable, destructs: Vec>, }, - NumLiteral(Variable, Box, IntValue, NumericBound), + NumLiteral(Variable, Box, IntValue, NumBound), IntLiteral(Variable, Variable, Box, IntValue, IntBound), FloatLiteral(Variable, Variable, Box, f64, FloatBound), StrLiteral(Box), diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 9f1b4c918d..17128e9de1 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -1,9 +1,10 @@ use arrayvec::ArrayVec; use roc_can::constraint::{Constraint, Constraints}; 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_region::all::Region; +use roc_types::num::NumericRange; use roc_types::subs::Variable; use roc_types::types::Type::{self, *}; use roc_types::types::{AliasKind, Category}; @@ -19,14 +20,18 @@ pub fn add_numeric_bound_constr( region: Region, category: Category, ) -> Type { - let range = bound.bounded_range(); - + let range = bound.numeric_bound(); let total_num_type = num_type; - match range.len() { - 0 => total_num_type, - 1 => { - let actual_type = Variable(range[0]); + use roc_types::num::{float_width_to_variable, int_width_to_variable}; + + match range { + NumericBound::None => { + // no additional constraints + total_num_type + } + NumericBound::FloatExact(width) => { + let actual_type = Variable(float_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); @@ -35,7 +40,17 @@ pub fn add_numeric_bound_constr( total_num_type } - _ => RangedNumber(Box::new(total_num_type), range), + NumericBound::IntExact(width) => { + let actual_type = Variable(int_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); + + num_constraints.extend([because_suffix]); + + total_num_type + } + NumericBound::Range(range) => RangedNumber(Box::new(total_num_type), range), } } @@ -117,7 +132,7 @@ pub fn num_literal( num_var: Variable, expected: Expected, region: Region, - bound: NumericBound, + bound: NumBound, ) -> Constraint { let open_number_type = crate::builtins::num_num(Type::Variable(num_var)); @@ -264,83 +279,57 @@ pub fn num_num(typ: Type) -> Type { } pub trait TypedNumericBound { - fn bounded_range(&self) -> Vec; + fn numeric_bound(&self) -> NumericBound; } impl TypedNumericBound for IntBound { - fn bounded_range(&self) -> Vec { + fn numeric_bound(&self) -> NumericBound { match self { - IntBound::None => vec![], - IntBound::Exact(w) => vec![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, - }], - IntBound::AtLeast { sign, width } => { - let whole_range: &[(IntWidth, Variable)] = match sign { - SignDemand::NoDemand => { - &[ - (IntWidth::I8, Variable::I8), - (IntWidth::U8, Variable::U8), - (IntWidth::I16, Variable::I16), - (IntWidth::U16, Variable::U16), - (IntWidth::I32, Variable::I32), - (IntWidth::U32, Variable::U32), - (IntWidth::I64, Variable::I64), - (IntWidth::Nat, Variable::NAT), // FIXME: Nat's order here depends on the platform! - (IntWidth::U64, Variable::U64), - (IntWidth::I128, Variable::I128), - (IntWidth::U128, Variable::U128), - ] - } - SignDemand::Signed => &[ - (IntWidth::I8, Variable::I8), - (IntWidth::I16, Variable::I16), - (IntWidth::I32, Variable::I32), - (IntWidth::I64, Variable::I64), - (IntWidth::I128, Variable::I128), - ], - }; - whole_range - .iter() - .skip_while(|(lower_bound, _)| *lower_bound != *width) - .map(|(_, var)| *var) - .collect() - } + IntBound::None => NumericBound::None, + IntBound::Exact(w) => NumericBound::IntExact(*w), + IntBound::AtLeast { + sign: SignDemand::NoDemand, + width, + } => NumericBound::Range(NumericRange::IntAtLeastEitherSign(*width)), + IntBound::AtLeast { + sign: SignDemand::Signed, + width, + } => NumericBound::Range(NumericRange::IntAtLeastSigned(*width)), } } } impl TypedNumericBound for FloatBound { - fn bounded_range(&self) -> Vec { + fn numeric_bound(&self) -> NumericBound { match self { - FloatBound::None => vec![], - FloatBound::Exact(w) => vec![match w { - FloatWidth::Dec => Variable::DEC, - FloatWidth::F32 => Variable::F32, - FloatWidth::F64 => Variable::F64, - }], + FloatBound::None => NumericBound::None, + FloatBound::Exact(w) => NumericBound::FloatExact(*w), } } } -impl TypedNumericBound for NumericBound { - fn bounded_range(&self) -> Vec { +impl TypedNumericBound for NumBound { + fn numeric_bound(&self) -> NumericBound { match self { - NumericBound::None => vec![], - &NumericBound::AtLeastIntOrFloat { sign, width } => { - let mut range = IntBound::AtLeast { sign, width }.bounded_range(); - range.extend_from_slice(&[Variable::F32, Variable::F64, Variable::DEC]); - range - } + NumBound::None => NumericBound::None, + &NumBound::AtLeastIntOrFloat { + sign: SignDemand::NoDemand, + width, + } => NumericBound::Range(NumericRange::NumAtLeastEitherSign(width)), + &NumBound::AtLeastIntOrFloat { + sign: SignDemand::Signed, + width, + } => NumericBound::Range(NumericRange::NumAtLeastSigned(width)), } } } + +/// A bound placed on a number because of its literal value. +/// e.g. `-5` cannot be unsigned, and 300 does not fit in a U8 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum NumericBound { + None, + FloatExact(FloatWidth), + IntExact(IntWidth), + Range(NumericRange), +} diff --git a/compiler/mono/src/copy.rs b/compiler/mono/src/copy.rs index 2a674be136..0a53657554 100644 --- a/compiler/mono/src/copy.rs +++ b/compiler/mono/src/copy.rs @@ -611,15 +611,10 @@ fn deep_copy_type_vars<'a>( }) } - RangedNumber(typ, range_vars) => { + RangedNumber(typ, range) => { let new_typ = descend_var!(typ); - descend_slice!(range_vars); - perform_clone!({ - let new_range_vars = clone_var_slice!(range_vars); - - RangedNumber(new_typ, new_range_vars) - }) + perform_clone!(RangedNumber(new_typ, range)) } Error => Error, }; diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 5e1e9a9a68..22ff28a81f 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1854,10 +1854,9 @@ fn type_to_variable<'a>( Variable(_) | EmptyRec | EmptyTagUnion => { unreachable!("This variant should never be deferred!") } - RangedNumber(typ, vars) => { + RangedNumber(typ, range) => { let ty_var = helper!(typ); - let vars = VariableSubsSlice::insert_into_subs(subs, vars.iter().copied()); - let content = Content::RangedNumber(ty_var, vars); + let content = Content::RangedNumber(ty_var, *range); register_with_known_var(subs, destination, rank, pools, content) } @@ -3015,10 +3014,8 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) { stack.push(var); } - &RangedNumber(typ, vars) => { + &RangedNumber(typ, _) => { stack.push(typ); - - stack.extend(var_slice!(vars)); } } } @@ -3267,12 +3264,10 @@ fn deep_copy_var_help( copy } - RangedNumber(typ, range_vars) => { + RangedNumber(typ, range) => { let new_type_var = deep_copy_var_help(subs, max_rank, pool, visited, typ); - let new_variables = copy_sequence!(range_vars.len(), range_vars); - - let new_content = RangedNumber(new_type_var, new_variables); + let new_content = RangedNumber(new_type_var, range); subs.set_content_unchecked(copy, new_content); diff --git a/compiler/types/src/lib.rs b/compiler/types/src/lib.rs index edba25cac4..0846e058aa 100644 --- a/compiler/types/src/lib.rs +++ b/compiler/types/src/lib.rs @@ -2,6 +2,7 @@ // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. #![allow(clippy::large_enum_variant)] pub mod builtin_aliases; +pub mod num; pub mod pretty_print; pub mod solved_types; pub mod subs; diff --git a/compiler/types/src/num.rs b/compiler/types/src/num.rs new file mode 100644 index 0000000000..725ff00fbe --- /dev/null +++ b/compiler/types/src/num.rs @@ -0,0 +1,327 @@ +use crate::subs::Variable; +use roc_module::symbol::Symbol; + +/// A bound placed on a number because of its literal value. +/// 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), +} + +impl NumericRange { + pub fn contains_symbol(&self, symbol: Symbol) -> Option { + 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_DEC => self.contains_float_width(FloatWidth::Dec), + Symbol::NUM_F32 => self.contains_float_width(FloatWidth::F32), + Symbol::NUM_F64 => self.contains_float_width(FloatWidth::F64), + + Symbol::NUM_NUM | Symbol::NUM_INT | Symbol::NUM_FRAC => { + // these satisfy any range that they are given + true + } + + _ => { + return None; + } + }; + + Some(contains) + } + + fn contains_float_width(&self, _width: FloatWidth) -> bool { + // we don't currently check the float width + true + } + + fn contains_int_width(&self, width: IntWidth) -> bool { + use NumericRange::*; + + let (range_signedness, at_least_width) = match self { + IntAtLeastSigned(width) => (SignDemand::Signed, width), + IntAtLeastEitherSign(width) => (SignDemand::NoDemand, width), + NumAtLeastSigned(width) => (SignDemand::Signed, width), + NumAtLeastEitherSign(width) => (SignDemand::NoDemand, width), + }; + + let (actual_signedness, _) = width.signedness_and_width(); + + if let (IntSignedness::Unsigned, SignDemand::Signed) = (actual_signedness, range_signedness) + { + return false; + } + + width.signedness_and_width().1 >= at_least_width.signedness_and_width().1 + } + + pub fn variable_slice(&self) -> &'static [Variable] { + use NumericRange::*; + + match self { + IntAtLeastSigned(width) => { + let target = int_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 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 start = SIGNED_VARIABLES.iter().position(|v| *v == target).unwrap(); + + &SIGNED_VARIABLES[start..] + } + NumAtLeastEitherSign(width) => { + let target = int_width_to_variable(*width); + let start = ALL_VARIABLES.iter().position(|v| *v == target).unwrap(); + + &ALL_VARIABLES[start..] + } + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum IntSignedness { + 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 `IntSignedness` and bit width of a variant. + fn signedness_and_width(&self) -> (IntSignedness, u32) { + use IntSignedness::*; + 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 IntSignedness::*; + + if is_negative { + match ( + self.signedness_and_width(), + lower_bound.signedness_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.signedness_and_width(), + lower_bound.signedness_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, + }, +} + +pub const fn int_width_to_variable(w: IntWidth) -> 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, + } +} + +pub const fn float_width_to_variable(w: FloatWidth) -> Variable { + match w { + FloatWidth::Dec => Variable::DEC, + FloatWidth::F32 => Variable::F32, + FloatWidth::F64 => Variable::F64, + } +} + +const ALL_VARIABLES: &[Variable] = &[ + Variable::I8, + Variable::U8, + Variable::I16, + Variable::U16, + Variable::I32, + Variable::U32, + Variable::I64, + Variable::NAT, // FIXME: Nat's order here depends on the platfor, + Variable::U64, + Variable::I128, + Variable::U128, + Variable::F32, + Variable::F64, + Variable::DEC, +]; + +const SIGNED_VARIABLES: &[Variable] = &[ + Variable::I8, + Variable::I16, + Variable::I32, + Variable::I64, + Variable::I128, + Variable::F32, + Variable::F64, + Variable::DEC, +]; diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index 6500a1b4ce..ad764009da 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -220,12 +220,8 @@ fn find_names_needed( // TODO should we also look in the actual variable? // find_names_needed(_actual, subs, roots, root_appearances, names_taken); } - &RangedNumber(typ, vars) => { + &RangedNumber(typ, _) => { find_names_needed(typ, subs, roots, root_appearances, names_taken); - for var_index in vars { - let var = subs[var_index]; - find_names_needed(var, subs, roots, root_appearances, names_taken); - } } Error | Structure(Erroneous(_)) | Structure(EmptyRecord) | Structure(EmptyTagUnion) => { // Errors and empty records don't need names. diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index da17f69d44..5baee3d4fc 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -713,8 +713,7 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt: ) } Content::RangedNumber(typ, range) => { - let slice = subs.get_subs_slice(*range); - write!(f, "RangedNumber({:?}, {:?})", typ, slice) + write!(f, "RangedNumber({:?}, {:?})", typ, range) } Content::Error => write!(f, "Error"), } @@ -2009,7 +2008,7 @@ pub enum Content { }, Structure(FlatType), Alias(Symbol, AliasVariables, Variable, AliasKind), - RangedNumber(Variable, VariableSubsSlice), + RangedNumber(Variable, crate::num::NumericRange), Error, } @@ -3025,16 +3024,10 @@ fn explicit_substitute( in_var } - RangedNumber(typ, vars) => { - for index in vars.into_iter() { - let var = subs[index]; - let new_var = explicit_substitute(subs, from, to, var, seen); - subs[index] = new_var; - } - + RangedNumber(typ, range) => { let new_typ = explicit_substitute(subs, from, to, typ, seen); - subs.set_content(in_var, RangedNumber(new_typ, vars)); + subs.set_content(in_var, RangedNumber(new_typ, range)); in_var } @@ -3094,12 +3087,7 @@ fn get_var_names( get_var_names(subs, subs[arg_var], answer) }), - RangedNumber(typ, vars) => { - let taken_names = get_var_names(subs, typ, taken_names); - vars.into_iter().fold(taken_names, |answer, var| { - get_var_names(subs, subs[var], answer) - }) - } + RangedNumber(typ, _) => get_var_names(subs, typ, taken_names), Structure(flat_type) => match flat_type { FlatType::Apply(_, args) => { @@ -3340,12 +3328,12 @@ fn content_to_err_type( RangedNumber(typ, range) => { let err_type = var_to_err_type(subs, state, typ); - if state.context == ErrorTypeContext::ExpandRanges { - let mut types = Vec::with_capacity(range.len()); - for var_index in range { - let var = subs[var_index]; + dbg!(range); - types.push(var_to_err_type(subs, state, var)); + if state.context == ErrorTypeContext::ExpandRanges { + let mut types = Vec::new(); + for var in range.variable_slice() { + types.push(var_to_err_type(subs, state, *var)); } ErrorType::Range(Box::new(err_type), types) } else { @@ -3645,9 +3633,8 @@ fn restore_help(subs: &mut Subs, initial: Variable) { stack.push(*var); } - RangedNumber(typ, vars) => { + RangedNumber(typ, _vars) => { stack.push(*typ); - stack.extend(var_slice(*vars)); } } } @@ -3833,10 +3820,7 @@ impl StorageSubs { Self::offset_variable(offsets, *actual), *kind, ), - RangedNumber(typ, vars) => RangedNumber( - Self::offset_variable(offsets, *typ), - Self::offset_variable_slice(offsets, *vars), - ), + RangedNumber(typ, range) => RangedNumber(Self::offset_variable(offsets, *typ), *range), Error => Content::Error, } } @@ -4262,18 +4246,10 @@ fn deep_copy_var_to_help(env: &mut DeepCopyVarToEnv<'_>, var: Variable) -> Varia copy } - RangedNumber(typ, vars) => { + RangedNumber(typ, range) => { let new_typ = deep_copy_var_to_help(env, typ); - let new_vars = SubsSlice::reserve_into_subs(env.target, vars.len()); - - for (target_index, var_index) in (new_vars.indices()).zip(vars) { - let var = env.source[var_index]; - let copy_var = deep_copy_var_to_help(env, var); - env.target.variables[target_index] = copy_var; - } - - let new_content = RangedNumber(new_typ, new_vars); + let new_content = RangedNumber(new_typ, range); env.target.set(copy, make_descriptor(new_content)); copy @@ -4731,18 +4707,10 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl copy } - RangedNumber(typ, vars) => { + RangedNumber(typ, range) => { let new_typ = copy_import_to_help(env, max_rank, typ); - let new_vars = SubsSlice::reserve_into_subs(env.target, vars.len()); - - for (target_index, var_index) in (new_vars.indices()).zip(vars) { - let var = env.source[var_index]; - let copy_var = copy_import_to_help(env, max_rank, var); - env.target.variables[target_index] = copy_var; - } - - let new_content = RangedNumber(new_typ, new_vars); + let new_content = RangedNumber(new_typ, range); env.target.set(copy, make_descriptor(new_content)); copy diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 963694b6c7..fcb0631b2a 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -1,3 +1,4 @@ +use crate::num::NumericRange; use crate::pretty_print::Parens; use crate::subs::{ GetSubsSlice, RecordFields, Subs, UnionTags, VarStore, Variable, VariableSubsSlice, @@ -254,7 +255,7 @@ pub enum Type { /// Applying a type to some arguments (e.g. Dict.Dict String Int) Apply(Symbol, Vec, Region), Variable(Variable), - RangedNumber(Box, Vec), + RangedNumber(Box, NumericRange), /// A type error, which will code gen to a runtime error Erroneous(Problem), } @@ -324,7 +325,7 @@ impl Clone for Type { } Self::Apply(arg0, arg1, arg2) => Self::Apply(*arg0, arg1.clone(), *arg2), Self::Variable(arg0) => Self::Variable(*arg0), - Self::RangedNumber(arg0, arg1) => Self::RangedNumber(arg0.clone(), arg1.clone()), + Self::RangedNumber(arg0, arg1) => Self::RangedNumber(arg0.clone(), *arg1), Self::Erroneous(arg0) => Self::Erroneous(arg0.clone()), } } @@ -1089,9 +1090,7 @@ impl Type { } => actual_type.contains_variable(rep_variable), HostExposedAlias { actual, .. } => actual.contains_variable(rep_variable), Apply(_, args, _) => args.iter().any(|arg| arg.contains_variable(rep_variable)), - RangedNumber(typ, vars) => { - typ.contains_variable(rep_variable) || vars.iter().any(|&v| v == rep_variable) - } + RangedNumber(typ, _) => typ.contains_variable(rep_variable), EmptyRec | EmptyTagUnion | Erroneous(_) => false, } } @@ -1594,9 +1593,8 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { } variables_help(actual, accum); } - RangedNumber(typ, vars) => { + RangedNumber(typ, _) => { variables_help(typ, accum); - accum.extend(vars.iter().copied()); } Apply(_, args, _) => { for x in args { @@ -1730,9 +1728,8 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { } variables_help_detailed(actual, accum); } - RangedNumber(typ, vars) => { + RangedNumber(typ, _) => { variables_help_detailed(typ, accum); - accum.type_variables.extend(vars); } Apply(_, args, _) => { for x in args { diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index fe75e8726f..055cd24383 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -5,6 +5,7 @@ use roc_debug_flags::{ROC_PRINT_MISMATCHES, ROC_PRINT_UNIFICATIONS}; use roc_error_macros::internal_error; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; +use roc_types::num::NumericRange; use roc_types::subs::Content::{self, *}; use roc_types::subs::{ AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, Mark, OptVariable, @@ -413,7 +414,7 @@ fn unify_ranged_number( pool: &mut Pool, ctx: &Context, real_var: Variable, - range_vars: VariableSubsSlice, + range_vars: NumericRange, ) -> Outcome { let other_content = &ctx.second_desc.content; @@ -431,7 +432,7 @@ fn unify_ranged_number( &RangedNumber(other_real_var, other_range_vars) => { let outcome = unify_pool(subs, pool, real_var, other_real_var, ctx.mode); if outcome.mismatches.is_empty() { - check_valid_range(subs, pool, ctx.first, other_range_vars, ctx.mode) + check_valid_range(subs, ctx.first, other_range_vars) } else { outcome } @@ -444,41 +445,41 @@ fn unify_ranged_number( return outcome; } - check_valid_range(subs, pool, ctx.second, range_vars, ctx.mode) + check_valid_range(subs, ctx.second, range_vars) } -fn check_valid_range( - subs: &mut Subs, - pool: &mut Pool, - var: Variable, - range: VariableSubsSlice, - mode: Mode, -) -> Outcome { - let slice = subs.get_subs_slice(range).to_vec(); +fn check_valid_range(subs: &mut Subs, var: Variable, range: NumericRange) -> Outcome { + let content = subs.get_content_without_compacting(var); - let mut it = slice.iter().peekable(); - while let Some(&possible_var) = it.next() { - let snapshot = subs.snapshot(); - let old_pool = pool.clone(); - let outcome = unify_pool(subs, pool, var, possible_var, mode | Mode::RIGID_AS_FLEX); - if outcome.mismatches.is_empty() { - // Okay, we matched some type in the range. - subs.rollback_to(snapshot); - *pool = old_pool; - return Outcome::default(); - } else if it.peek().is_some() { - // We failed to match something in the range, but there are still things we can try. - subs.rollback_to(snapshot); - *pool = old_pool; - } else { - subs.commit_snapshot(snapshot); + match content { + &Content::Alias(symbol, _, actual, _) => { + match range.contains_symbol(symbol) { + None => { + // symbol not recognized; go into the alias + return check_valid_range(subs, actual, range); + } + Some(false) => { + let outcome = Outcome { + mismatches: vec![Mismatch::TypeNotInRange], + must_implement_ability: Default::default(), + }; + + return outcome; + } + Some(true) => { /* fall through */ } + } + } + + Content::RangedNumber(_, _) => { + // these ranges always intersect, we need more information before we can say more + } + + _ => { + // anything else is definitely a type error, and will be reported elsewhere } } - Outcome { - mismatches: vec![Mismatch::TypeNotInRange], - ..Outcome::default() - } + Outcome::default() } #[inline(always)] @@ -576,7 +577,7 @@ fn unify_alias( RangedNumber(other_real_var, other_range_vars) => { let outcome = unify_pool(subs, pool, real_var, *other_real_var, ctx.mode); if outcome.mismatches.is_empty() { - check_valid_range(subs, pool, real_var, *other_range_vars, ctx.mode) + check_valid_range(subs, real_var, *other_range_vars) } else { outcome } @@ -637,7 +638,7 @@ fn unify_opaque( // This opaque might be a number, check if it unifies with the target ranged number var. let outcome = unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode); if outcome.mismatches.is_empty() { - check_valid_range(subs, pool, ctx.first, *other_range_vars, ctx.mode) + check_valid_range(subs, ctx.first, *other_range_vars) } else { outcome } @@ -768,7 +769,7 @@ fn unify_structure( RangedNumber(other_real_var, other_range_vars) => { let outcome = unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode); if outcome.mismatches.is_empty() { - check_valid_range(subs, pool, ctx.first, *other_range_vars, ctx.mode) + check_valid_range(subs, ctx.first, *other_range_vars) } else { outcome }