diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 93f6b6947b..3a8d1ba9e4 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, ClosureData, Expr::*, IntValue}; use crate::expr::{Expr, Field, Recursive}; -use crate::num::{FloatWidth, IntWidth, NumWidth, NumericBound}; +use crate::num::{FloatBound, IntBound, IntWidth, NumericBound}; use crate::pattern::Pattern; use roc_collections::all::SendMap; use roc_module::called_via::CalledVia; @@ -867,7 +867,7 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def { args: vec![ ( arg_var, - int::(var_store.fresh(), var_store.fresh(), 1, num_no_bound()), + int::(var_store.fresh(), var_store.fresh(), 1, int_no_bound()), ), ( arg_var, @@ -965,7 +965,7 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def { (float_var, Var(Symbol::ARG_1)), ( float_var, - float(unbound_zero_var, precision_var, 0.0, num_no_bound()), + float(unbound_zero_var, precision_var, 0.0, float_no_bound()), ), ], ret_var: bool_var, @@ -1014,7 +1014,7 @@ fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def { (float_var, Var(Symbol::ARG_1)), ( float_var, - float(unbound_zero_var, precision_var, 0.0, num_no_bound()), + float(unbound_zero_var, precision_var, 0.0, float_no_bound()), ), ], ret_var: bool_var, @@ -1253,162 +1253,82 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def { /// Num.minI8: I8 fn num_min_i8(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - i8::MIN, - NumericBound::Exact(IntWidth::I8), - ) + int_min_or_max::(symbol, var_store, i8::MIN, IntBound::Exact(IntWidth::I8)) } /// Num.maxI8: I8 fn num_max_i8(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - i8::MAX, - NumericBound::Exact(IntWidth::I8), - ) + int_min_or_max::(symbol, var_store, i8::MAX, IntBound::Exact(IntWidth::I8)) } /// Num.minU8: U8 fn num_min_u8(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - u8::MIN, - NumericBound::Exact(IntWidth::U8), - ) + int_min_or_max::(symbol, var_store, u8::MIN, IntBound::Exact(IntWidth::U8)) } /// Num.maxU8: U8 fn num_max_u8(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - u8::MAX, - NumericBound::Exact(IntWidth::U8), - ) + int_min_or_max::(symbol, var_store, u8::MAX, IntBound::Exact(IntWidth::U8)) } /// Num.minI16: I16 fn num_min_i16(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - i16::MIN, - NumericBound::Exact(IntWidth::I16), - ) + int_min_or_max::(symbol, var_store, i16::MIN, IntBound::Exact(IntWidth::I16)) } /// Num.maxI16: I16 fn num_max_i16(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - i16::MAX, - NumericBound::Exact(IntWidth::I16), - ) + int_min_or_max::(symbol, var_store, i16::MAX, IntBound::Exact(IntWidth::I16)) } /// Num.minU16: U16 fn num_min_u16(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - u16::MIN, - NumericBound::Exact(IntWidth::U16), - ) + int_min_or_max::(symbol, var_store, u16::MIN, IntBound::Exact(IntWidth::U16)) } /// Num.maxU16: U16 fn num_max_u16(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - u16::MAX, - NumericBound::Exact(IntWidth::U16), - ) + int_min_or_max::(symbol, var_store, u16::MAX, IntBound::Exact(IntWidth::U16)) } /// Num.minI32: I32 fn num_min_i32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - i32::MIN, - NumericBound::Exact(IntWidth::I32), - ) + int_min_or_max::(symbol, var_store, i32::MIN, IntBound::Exact(IntWidth::I32)) } /// Num.maxI32: I32 fn num_max_i32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - i32::MAX, - NumericBound::Exact(IntWidth::I32), - ) + int_min_or_max::(symbol, var_store, i32::MAX, IntBound::Exact(IntWidth::I32)) } /// Num.minU32: U32 fn num_min_u32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - u32::MIN, - NumericBound::Exact(IntWidth::U32), - ) + int_min_or_max::(symbol, var_store, u32::MIN, IntBound::Exact(IntWidth::U32)) } /// Num.maxU32: U32 fn num_max_u32(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - u32::MAX, - NumericBound::Exact(IntWidth::U32), - ) + int_min_or_max::(symbol, var_store, u32::MAX, IntBound::Exact(IntWidth::U32)) } /// Num.minI64: I64 fn num_min_i64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - i64::MIN, - NumericBound::Exact(IntWidth::I64), - ) + int_min_or_max::(symbol, var_store, i64::MIN, IntBound::Exact(IntWidth::I64)) } /// Num.maxI64: I64 fn num_max_i64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - i64::MAX, - NumericBound::Exact(IntWidth::I64), - ) + int_min_or_max::(symbol, var_store, i64::MAX, IntBound::Exact(IntWidth::I64)) } /// Num.minU64: U64 fn num_min_u64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - u64::MIN, - NumericBound::Exact(IntWidth::U64), - ) + int_min_or_max::(symbol, var_store, u64::MIN, IntBound::Exact(IntWidth::U64)) } /// Num.maxU64: U64 fn num_max_u64(symbol: Symbol, var_store: &mut VarStore) -> Def { - int_min_or_max::( - symbol, - var_store, - u64::MAX, - NumericBound::Exact(IntWidth::U64), - ) + int_min_or_max::(symbol, var_store, u64::MAX, IntBound::Exact(IntWidth::U64)) } /// Num.minI128: I128 @@ -1417,7 +1337,7 @@ fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { symbol, var_store, i128::MIN, - NumericBound::Exact(IntWidth::I128), + IntBound::Exact(IntWidth::I128), ) } @@ -1427,7 +1347,7 @@ fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { symbol, var_store, i128::MAX, - NumericBound::Exact(IntWidth::I128), + IntBound::Exact(IntWidth::I128), ) } @@ -1559,7 +1479,7 @@ fn str_to_num(symbol: Symbol, var_store: &mut VarStore) -> Def { errorcode_var, Variable::UNSIGNED8, 0, - NumericBound::Exact(IntWidth::U8), + IntBound::Exact(IntWidth::U8), ), ), ], @@ -2307,7 +2227,7 @@ fn list_take_first(symbol: Symbol, var_store: &mut VarStore) -> Def { len_var, Variable::NATURAL, 0, - NumericBound::Exact(IntWidth::Nat), + IntBound::Exact(IntWidth::Nat), ); let body = RunLowLevel { @@ -2338,7 +2258,7 @@ fn list_take_last(symbol: Symbol, var_store: &mut VarStore) -> Def { len_var, Variable::NATURAL, 0, - NumericBound::Exact(IntWidth::Nat), + IntBound::Exact(IntWidth::Nat), ); let bool_var = var_store.fresh(); @@ -2453,7 +2373,7 @@ fn list_intersperse(symbol: Symbol, var_store: &mut VarStore) -> Def { int_var, Variable::NATURAL, 0, - NumericBound::Exact(IntWidth::Nat), + IntBound::Exact(IntWidth::Nat), ); // \acc, elem -> acc |> List.append sep |> List.append elem @@ -2538,7 +2458,7 @@ fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def { index_var, Variable::NATURAL, 0, - NumericBound::Exact(IntWidth::Nat), + IntBound::Exact(IntWidth::Nat), ); let clos = Closure(ClosureData { @@ -2703,7 +2623,7 @@ fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def { (list_var, Var(Symbol::ARG_1)), ( index_var, - int::(num_var, num_precision_var, 0, num_no_bound()), + int::(num_var, num_precision_var, 0, int_no_bound()), ), ], ret_var: list_var, @@ -2803,7 +2723,7 @@ fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def { ), ( arg_var, - int::(num_var, num_precision_var, 1, num_no_bound()), + int::(num_var, num_precision_var, 1, int_no_bound()), ), ], ret_var: len_var, @@ -3004,7 +2924,7 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def { args: vec![ ( len_var, - int::(num_var, num_precision_var, 0, num_no_bound()), + int::(num_var, num_precision_var, 0, int_no_bound()), ), ( len_var, @@ -3042,7 +2962,7 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def { num_var, num_precision_var, 0, - num_no_bound(), + int_no_bound(), ), ), ], @@ -3145,7 +3065,7 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def { args: vec![ ( len_var, - int::(num_var, num_precision_var, 0, num_no_bound()), + int::(num_var, num_precision_var, 0, int_no_bound()), ), ( len_var, @@ -3183,7 +3103,7 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def { num_var, num_precision_var, 0, - num_no_bound(), + int_no_bound(), ), ), ], @@ -4076,7 +3996,7 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def { (num_var, Var(Symbol::ARG_2)), ( num_var, - float(unbound_zero_var, precision_var, 0.0, num_no_bound()), + float(unbound_zero_var, precision_var, 0.0, float_no_bound()), ), ], ret_var: bool_var, @@ -4146,7 +4066,7 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def { unbound_zero_var, unbound_zero_precision_var, 0, - num_no_bound(), + int_no_bound(), ), ), ], @@ -4217,7 +4137,7 @@ fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def { unbound_zero_var, unbound_zero_precision_var, 0, - num_no_bound(), + int_no_bound(), ), ), ], @@ -4290,7 +4210,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def { args: vec![ ( len_var, - int::(zero_var, zero_precision_var, 0, num_no_bound()), + int::(zero_var, zero_precision_var, 0, int_no_bound()), ), ( len_var, @@ -4317,7 +4237,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def { (list_var, Var(Symbol::ARG_1)), ( len_var, - int::(zero_var, zero_precision_var, 0, num_no_bound()), + int::(zero_var, zero_precision_var, 0, int_no_bound()), ), ], ret_var: list_elem_var, @@ -4377,7 +4297,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def { args: vec![ ( len_var, - int::(num_var, num_precision_var, 0, num_no_bound()), + int::(num_var, num_precision_var, 0, int_no_bound()), ), ( len_var, @@ -4423,7 +4343,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def { num_var, num_precision_var, 1, - num_no_bound(), + int_no_bound(), ), ), ], @@ -5144,12 +5064,7 @@ fn defn_help( } #[inline(always)] -fn int_min_or_max( - symbol: Symbol, - var_store: &mut VarStore, - i: I128, - bound: NumericBound, -) -> Def +fn int_min_or_max(symbol: Symbol, var_store: &mut VarStore, i: I128, bound: IntBound) -> Def where I128: Into, { @@ -5178,17 +5093,20 @@ where } } -fn num_no_bound() -> NumericBound { +fn num_no_bound() -> NumericBound { NumericBound::None } +fn int_no_bound() -> IntBound { + IntBound::None +} + +fn float_no_bound() -> FloatBound { + FloatBound::None +} + #[inline(always)] -fn int( - num_var: Variable, - precision_var: Variable, - i: I128, - bound: NumericBound, -) -> Expr +fn int(num_var: Variable, precision_var: Variable, i: I128, bound: IntBound) -> Expr where I128: Into, { @@ -5203,12 +5121,7 @@ where } #[inline(always)] -fn float( - num_var: Variable, - precision_var: Variable, - f: f64, - bound: NumericBound, -) -> Expr { +fn float(num_var: Variable, precision_var: Variable, f: f64, bound: FloatBound) -> Expr { Float( num_var, precision_var, @@ -5219,7 +5132,7 @@ fn float( } #[inline(always)] -fn num>(num_var: Variable, i: I, bound: NumericBound) -> Expr { +fn num>(num_var: Variable, i: I, bound: NumericBound) -> Expr { let i = i.into(); Num( num_var, diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index 03b758320f..c56168c827 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -27,6 +27,14 @@ pub enum Constraint { Let(Box), And(Vec), Present(Type, PresenceConstraint), + + /// `EqBoundedRange(Ts, U, ...)` means there must be at least one `T` in the *ordered* range `Ts` + /// that unifies (via `Eq`) with `U`. + /// + /// This is only used for integers, where we may see e.g. the number literal `-1` and know it + /// has the bounded range `[I8, I16, I32, I64, I128]`, at least one of which must unify with + /// the type the number literal is used as. + EqBoundedRange(Type, Expected>, Category, Region), } #[derive(Debug, Clone, PartialEq)] @@ -87,6 +95,7 @@ impl Constraint { } Constraint::And(cs) => cs.iter().any(|c| c.contains_save_the_environment()), Constraint::Present(_, _) => false, + Constraint::EqBoundedRange(_, _, _, _) => false, } } } @@ -171,5 +180,11 @@ fn validate_help(constraint: &Constraint, declared: &Declared, accum: &mut Varia } } } + Constraint::EqBoundedRange(typ, one_of, _, _) => { + subtract(declared, &typ.variables_detail(), accum); + for typ in one_of.get_type_ref() { + subtract(declared, &typ.variables_detail(), accum); + } + } } } diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 3d4954e5ad..86579c248f 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -4,7 +4,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, FloatWidth, IntWidth, NumWidth, NumericBound, + int_expr_from_result, num_expr_from_result, FloatBound, IntBound, NumericBound, }; use crate::pattern::{canonicalize_pattern, Pattern}; use crate::procedure::References; @@ -67,17 +67,11 @@ 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, NumericBound), // Int and Float store a variable to generate better error messages - Int( - Variable, - Variable, - Box, - IntValue, - NumericBound, - ), - Float(Variable, Variable, Box, f64, NumericBound), + Int(Variable, Variable, Box, IntValue, IntBound), + Float(Variable, Variable, Box, f64, FloatBound), Str(Box), List { elem_var: Variable, diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index 847d729466..e93ca3eedc 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -50,7 +50,7 @@ pub fn num_expr_from_result( #[inline(always)] pub fn int_expr_from_result( var_store: &mut VarStore, - result: Result<(&str, IntValue, NumericBound), (&str, IntErrorKind)>, + result: Result<(&str, IntValue, IntBound), (&str, IntErrorKind)>, region: Region, base: Base, env: &mut Env, @@ -77,7 +77,7 @@ pub fn int_expr_from_result( #[inline(always)] pub fn float_expr_from_result( var_store: &mut VarStore, - result: Result<(&str, f64, NumericBound), (&str, FloatErrorKind)>, + result: Result<(&str, f64, FloatBound), (&str, FloatErrorKind)>, region: Region, env: &mut Env, ) -> Expr { @@ -101,8 +101,8 @@ pub fn float_expr_from_result( } pub enum ParsedNumResult { - Int(IntValue, NumericBound), - Float(f64, NumericBound), + Int(IntValue, IntBound), + Float(f64, FloatBound), UnknownNum(IntValue), } @@ -115,15 +115,13 @@ pub fn finish_parsing_num(raw: &str) -> Result ParsedNumResult::UnknownNum(num), - NumericBound::Exact(NumWidth::Int(iw)) => { - ParsedNumResult::Int(num, NumericBound::Exact(iw)) - } - NumericBound::Exact(NumWidth::Float(fw)) => { + NumericBound::Int(ib) => ParsedNumResult::Int(num, ib), + NumericBound::Float(fb) => { let num = match num { IntValue::I128(n) => n as f64, IntValue::U128(n) => n as f64, }; - ParsedNumResult::Float(num, NumericBound::Exact(fw)) + ParsedNumResult::Float(num, fb) } }) } @@ -133,7 +131,7 @@ pub fn finish_parsing_base( raw: &str, base: Base, is_negative: bool, -) -> Result<(IntValue, NumericBound), (&str, IntErrorKind)> { +) -> Result<(IntValue, IntBound), (&str, IntErrorKind)> { let radix = match base { Base::Hex => 16, Base::Decimal => 10, @@ -149,9 +147,9 @@ pub fn finish_parsing_base( }) .and_then(|(n, bound)| { let bound = match bound { - NumericBound::None => NumericBound::None, - NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(iw), - NumericBound::Exact(NumWidth::Float(_)) => return Err(IntErrorKind::FloatSuffix), + NumericBound::None => IntBound::None, + NumericBound::Int(ib) => ib, + NumericBound::Float(_) => return Err(IntErrorKind::FloatSuffix), }; Ok((n, bound)) }) @@ -159,15 +157,13 @@ pub fn finish_parsing_base( } #[inline(always)] -pub fn finish_parsing_float( - raw: &str, -) -> Result<(f64, NumericBound), (&str, FloatErrorKind)> { +pub fn finish_parsing_float(raw: &str) -> Result<(f64, FloatBound), (&str, FloatErrorKind)> { let (opt_bound, raw_without_suffix) = parse_literal_suffix(raw); let bound = match opt_bound { - None => NumericBound::None, - Some(NumWidth::Float(fw)) => NumericBound::Exact(fw), - Some(NumWidth::Int(_)) => return Err((raw, FloatErrorKind::IntSuffix)), + None => FloatBound::None, + Some(ParsedWidth::Float(fw)) => FloatBound::Exact(fw), + Some(ParsedWidth::Int(_)) => return Err((raw, FloatErrorKind::IntSuffix)), }; // Ignore underscores. @@ -184,7 +180,13 @@ pub fn finish_parsing_float( } } -fn parse_literal_suffix(num_str: &str) -> (Option, &str) { +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum ParsedWidth { + Int(IntWidth), + Float(FloatWidth), +} + +fn parse_literal_suffix(num_str: &str) -> (Option, &str) { macro_rules! parse_num_suffix { ($($suffix:expr, $width:expr)*) => {$( if num_str.ends_with($suffix) { @@ -194,20 +196,20 @@ fn parse_literal_suffix(num_str: &str) -> (Option, &str) { } parse_num_suffix! { - "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) + "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) + "dec", ParsedWidth::Float(FloatWidth::Dec) + "f32", ParsedWidth::Float(FloatWidth::F32) + "f64", ParsedWidth::Float(FloatWidth::F64) } (None, num_str) @@ -221,10 +223,7 @@ fn parse_literal_suffix(num_str: &str) -> (Option, &str) { /// the LEGAL_DETAILS file in the root directory of this distribution. /// /// Thanks to the Rust project and its contributors! -fn from_str_radix( - src: &str, - radix: u32, -) -> Result<(IntValue, NumericBound), IntErrorKind> { +fn from_str_radix(src: &str, radix: u32) -> Result<(IntValue, NumericBound), IntErrorKind> { use self::IntErrorKind::*; assert!( @@ -268,19 +267,31 @@ fn from_str_radix( match opt_exact_bound { None => { - // TODO: use the lower bound - Ok((result, NumericBound::None)) + // There's no exact bound, but we do have a lower bound. + let sign_demand = if is_negative { + SignDemand::Signed + } else { + SignDemand::NoDemand + }; + Ok(( + result, + IntBound::AtLeast { + sign: sign_demand, + width: lower_bound, + } + .into(), + )) } - Some(bound @ NumWidth::Float(_)) => { + Some(ParsedWidth::Float(fw)) => { // For now, assume floats can represent all integers // TODO: this is somewhat incorrect, revisit - Ok((result, NumericBound::Exact(bound))) + Ok((result, FloatBound::Exact(fw).into())) } - Some(NumWidth::Int(exact_width)) => { + Some(ParsedWidth::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)))) + Ok((result, IntBound::Exact(exact_width).into())) } 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 @@ -474,19 +485,47 @@ pub enum FloatWidth { } #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum NumWidth { - Int(IntWidth), - Float(FloatWidth), +pub enum SignDemand { + /// Can be signed or unsigned. + NoDemand, + /// Must be signed. + Signed, } -/// Describes a bound on the width of a numeric literal. +/// Describes a bound on the width of an integer. #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum NumericBound -where - W: Copy, -{ +pub enum IntBound { /// There is no bound on the width. None, - /// Must have exactly the width `W`. - Exact(W), + /// 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, + Int(IntBound), + Float(FloatBound), +} + +impl From for NumericBound { + #[inline(always)] + fn from(ib: IntBound) -> Self { + Self::Int(ib) + } +} + +impl From for NumericBound { + #[inline(always)] + fn from(fb: FloatBound) -> Self { + Self::Float(fb) + } } diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index ac85b53306..2e574796c2 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -1,7 +1,7 @@ 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, FloatWidth, IntWidth, NumWidth, + finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatBound, IntBound, NumericBound, ParsedNumResult, }; use crate::scope::Scope; @@ -29,15 +29,9 @@ pub enum Pattern { ext_var: Variable, destructs: Vec>, }, - NumLiteral(Variable, Box, IntValue, NumericBound), - IntLiteral( - Variable, - Variable, - Box, - IntValue, - NumericBound, - ), - FloatLiteral(Variable, Variable, Box, f64, NumericBound), + NumLiteral(Variable, Box, IntValue, NumericBound), + IntLiteral(Variable, Variable, Box, IntValue, IntBound), + FloatLiteral(Variable, Variable, Box, f64, FloatBound), StrLiteral(Box), Underscore, diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 020d5b4564..e72010899a 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -1,7 +1,7 @@ use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::LetConstraint; use roc_can::expected::Expected::{self, *}; -use roc_can::num::{FloatWidth, IntWidth, NumWidth, NumericBound}; +use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumericBound, SignDemand}; use roc_collections::all::SendMap; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; @@ -28,13 +28,31 @@ pub fn add_numeric_bound_constr( } } +pub fn add_numeric_range_constr( + constrs: &mut Vec, + num_type: Type, + bound: impl TypedNumericBound, + region: Region, + category: Category, +) { + let range = bound.bounded_range(); + if !range.is_empty() { + constrs.push(EqBoundedRange( + num_type, + Expected::ForReason(Reason::NumericLiteralSuffix, range, region), + category, + region, + )); + } +} + #[inline(always)] pub fn int_literal( num_var: Variable, precision_var: Variable, expected: Expected, region: Region, - bound: NumericBound, + bound: IntBound, ) -> Constraint { let num_type = Variable(num_var); let reason = Reason::IntLiteral; @@ -50,8 +68,15 @@ pub fn int_literal( Category::Int, region, ), - Eq(num_type, expected, Category::Int, region), + Eq(num_type, expected.clone(), Category::Int, region), ]); + add_numeric_range_constr( + &mut constrs, + expected.get_type(), + bound, + region, + Category::Int, + ); exists(vec![num_var], And(constrs)) } @@ -62,7 +87,7 @@ pub fn float_literal( precision_var: Variable, expected: Expected, region: Region, - bound: NumericBound, + bound: FloatBound, ) -> Constraint { let num_type = Variable(num_var); let reason = Reason::FloatLiteral; @@ -93,7 +118,7 @@ pub fn num_literal( num_var: Variable, expected: Expected, region: Region, - bound: NumericBound, + bound: NumericBound, ) -> Constraint { let num_type = crate::builtins::num_num(Type::Variable(num_var)); @@ -290,13 +315,15 @@ pub trait TypedNumericBound { /// Get a concrete type for this number, if one exists. /// Returns `None` e.g. if the bound is open, like `Int *`. fn concrete_num_type(&self) -> Option; + + fn bounded_range(&self) -> Vec; } -impl TypedNumericBound for NumericBound { +impl TypedNumericBound for IntBound { fn concrete_num_type(&self) -> Option { match self { - NumericBound::None => None, - NumericBound::Exact(w) => Some(match w { + IntBound::None | IntBound::AtLeast { .. } => None, + IntBound::Exact(w) => Some(match w { IntWidth::U8 => num_u8(), IntWidth::U16 => num_u16(), IntWidth::U32 => num_u32(), @@ -311,29 +338,80 @@ impl TypedNumericBound for NumericBound { }), } } + + fn bounded_range(&self) -> Vec { + match self { + IntBound::None => vec![], + IntBound::Exact(_) => vec![], + 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)| Type::Variable(*var)) + .collect() + } + } + } } -impl TypedNumericBound for NumericBound { +impl TypedNumericBound for FloatBound { fn concrete_num_type(&self) -> Option { match self { - NumericBound::None => None, - NumericBound::Exact(w) => Some(match w { + FloatBound::None => None, + FloatBound::Exact(w) => Some(match w { FloatWidth::Dec => num_dec(), FloatWidth::F32 => num_f32(), FloatWidth::F64 => num_f64(), }), } } -} -impl TypedNumericBound for NumericBound { - fn concrete_num_type(&self) -> Option { + fn bounded_range(&self) -> Vec { match self { - NumericBound::None => None, - NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(*iw).concrete_num_type(), - NumericBound::Exact(NumWidth::Float(fw)) => { - NumericBound::Exact(*fw).concrete_num_type() - } + FloatBound::None => vec![], + FloatBound::Exact(_) => vec![], + } + } +} + +impl TypedNumericBound for NumericBound { + fn concrete_num_type(&self) -> Option { + match self { + NumericBound::None => None, + NumericBound::Int(ib) => ib.concrete_num_type(), + NumericBound::Float(fb) => fb.concrete_num_type(), + } + } + + fn bounded_range(&self) -> Vec { + match self { + NumericBound::None => vec![], + NumericBound::Int(ib) => ib.bounded_range(), + NumericBound::Float(fb) => fb.bounded_range(), } } } diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index edb027a235..c1e9bc7e19 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -689,6 +689,51 @@ fn solve( } } } + EqBoundedRange(typ, expect_one_of, category, region) => { + let actual = type_to_var(subs, rank, pools, cached_aliases, typ); + + let mut it = expect_one_of.get_type_ref().iter().peekable(); + + while let Some(expected) = it.next() { + let expected = type_to_var(subs, rank, pools, cached_aliases, expected); + let snapshot = subs.snapshot(); + match unify(subs, actual, expected, Mode::Eq) { + Success(vars) => { + introduce(subs, rank, pools, &vars); + + return state; + } + Failure(..) if it.peek().is_some() => { + subs.rollback_to(snapshot); + + continue; + } + Failure(vars, actual_type, expected_type) => { + // This is the last type we could have tried and failed; record the error. + introduce(subs, rank, pools, &vars); + + let problem = TypeError::BadExpr( + *region, + category.clone(), + actual_type, + expect_one_of.clone().replace(expected_type), + ); + + problems.push(problem); + + return state; + } + BadType(vars, problem) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::BadType(problem)); + + return state; + } + } + } + unreachable!() + } } } diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 90ff591ae0..3a7ad4dd87 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -862,9 +862,9 @@ mod test_reporting { 2│> 2 if 1 -> 0x0 3│ _ -> 0x1 - Right now it’s a number of type: + Right now it’s an integer of type: - Num a + Int a But I need every `if` guard condition to evaluate to a Bool—either `True` or `False`. @@ -896,7 +896,7 @@ mod test_reporting { but the `then` branch has the type: - Num a + Int a I need all branches in an `if` to have the same type! "# @@ -927,7 +927,7 @@ mod test_reporting { But all the previous branches have type: - Num a + Int a I need all branches in an `if` to have the same type! "# @@ -993,7 +993,7 @@ mod test_reporting { However, the preceding elements in the list all have the type: - Num a + Int a I need every element in a list to have the same type! "# @@ -1424,7 +1424,7 @@ mod test_reporting { But the expression between `when` and `is` has the type: - Num a + Int a "# ), ) @@ -1455,7 +1455,7 @@ mod test_reporting { But all the previous branches match: - Num a + Int a "# ), ) @@ -1485,7 +1485,7 @@ mod test_reporting { But the expression between `when` and `is` has the type: - { foo : Num a } + { foo : Int a } "# ), ) @@ -1605,13 +1605,13 @@ mod test_reporting { 2│ {} | 1 -> 3 ^^^^^^ - The first pattern is trying to match numbers: + The first pattern is trying to match integers: - Num a + Int a But the expression between `when` and `is` has the type: - { foo : Num a } + { foo : Int a } "# ), ) @@ -1637,9 +1637,9 @@ mod test_reporting { 1│ (Foo x) = 42 ^^ - It is a number of type: + It is an integer of type: - Num a + Int a But you are trying to use it as: @@ -2162,8 +2162,8 @@ mod test_reporting { This is usually a typo. Here are the `x` fields that are most similar: - { fo : Num b - , bar : Num a + { fo : Int b + , bar : Int a } So maybe `.foo` should be `.fo`? @@ -2229,10 +2229,10 @@ mod test_reporting { This is usually a typo. Here are the `x` fields that are most similar: - { fo : Num c - , foobar : Num d - , bar : Num a - , baz : Num b + { fo : Int c + , foobar : Int d + , bar : Int a + , baz : Int b , ... } @@ -2327,7 +2327,7 @@ mod test_reporting { But `add` needs the 2nd argument to be: - Num a + Num (Integer a) "# ), ) @@ -3360,8 +3360,8 @@ mod test_reporting { This `ACons` global tag application has the type: - [ ACons Num (Integer Signed64) [ BCons (Num a) [ ACons Str [ BNil - ]b ]c ]d, ANil ] + [ ACons Int Signed64 [ BCons (Int a) [ ACons Str [ BNil ]b ]c ]d, + ANil ] But the type annotation on `x` says it should be: @@ -4973,7 +4973,7 @@ mod test_reporting { This `insert` call produces: - Dict Str (Num a) + Dict Str (Int a) But the type annotation on `myDict` says it should be: @@ -5635,7 +5635,7 @@ mod test_reporting { but the `then` branch has the type: - Num a + Int a I need all branches in an `if` to have the same type! "# @@ -6276,7 +6276,7 @@ I need all branches in an `if` to have the same type! This `map` call produces: - List [ Foo Num a ] + List [ Foo Int a ] But the type annotation on `x` says it should be: @@ -6526,11 +6526,11 @@ I need all branches in an `if` to have the same type! This argument is an anonymous function of type: - Num a -> Num a + Num (Integer a) -> Num (Integer a) But `map` needs the 2nd argument to be: - Str -> Num a + Str -> Num (Integer a) "# ), ) @@ -6632,6 +6632,21 @@ I need all branches in an `if` to have the same type! But the type annotation on `mult` says it should be: F64 + + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 2nd argument to `mult` is not what I expect: + + 4│ mult 0 0 + ^ + + This argument is an integer of type: + + Int a + + But `mult` needs the 2nd argument to be: + + F64 "# ), ) @@ -6680,6 +6695,21 @@ I need all branches in an `if` to have the same type! But the type annotation on `mult` says it should be: F64 + + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + The 2nd argument to `mult` is not what I expect: + + 4│ mult 0 0 + ^ + + This argument is an integer of type: + + Int a + + But `mult` needs the 2nd argument to be: + + F64 "# ), ) @@ -7031,9 +7061,9 @@ I need all branches in an `if` to have the same type! 5│ f = \c -> c 6 ^ - This argument is a number of type: + This argument is an integer of type: - Num a + Int a But `c` needs the 1st argument to be: @@ -7041,7 +7071,7 @@ I need all branches in an `if` to have the same type! Tip: The type annotation uses the type variable `a` to say that this definition can produce any type of value. But in the body I see that - it will only produce a `Num` value of a single specific type. Maybe + it will only produce a `Int` value of a single specific type. Maybe change the type annotation to be more specific? Maybe change the code to be more general? "# @@ -7848,4 +7878,24 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn list_get_negative_number() { + report_problem_as( + "List.get [1, 2, 3] -1", + 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. + "# + ), + ) + } }