Merge pull request #3402 from rtfeldman/promote-large-numbers

Promote large number layouts when they don't fit in I64s
This commit is contained in:
Ayaz 2022-07-06 08:50:46 -04:00 committed by GitHub
commit d07c273542
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 930 additions and 611 deletions

View file

@ -1461,7 +1461,7 @@ fn adjust_rank_content(
rank rank
} }
RangedNumber(typ, _vars) => adjust_rank(subs, young_mark, visit_mark, group_rank, *typ), RangedNumber(_vars) => group_rank,
} }
} }
@ -1637,9 +1637,7 @@ fn instantiate_rigids_help(
} }
} }
RangedNumber(typ, _vars) => { RangedNumber(_vars) => {}
instantiate_rigids_help(subs, max_rank, pools, typ);
}
} }
var var
@ -1946,9 +1944,8 @@ fn deep_copy_var_help(
copy copy
} }
RangedNumber(typ, vars) => { RangedNumber(vars) => {
let new_real_type = deep_copy_var_help(subs, max_rank, pools, typ); let new_content = RangedNumber(vars);
let new_content = RangedNumber(new_real_type, vars);
subs.set(copy, make_descriptor(new_content)); subs.set(copy, make_descriptor(new_content));

View file

@ -586,7 +586,7 @@ fn add_type_help<'a>(
add_type_help(env, layout, *real_var, Some(*name), types) add_type_help(env, layout, *real_var, Some(*name), types)
} }
} }
Content::RangedNumber(_, _) => todo!(), Content::RangedNumber(_) => todo!(),
Content::Error => todo!(), Content::Error => todo!(),
Content::RecursionVar { structure, .. } => { Content::RecursionVar { structure, .. } => {
let type_id = types.add(RocType::RecursivePointer(TypeId::PENDING), layout); let type_id = types.add(RocType::RecursivePointer(TypeId::PENDING), layout);

View file

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

View file

@ -63,7 +63,7 @@ impl Output {
} }
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq, Copy)]
pub enum IntValue { pub enum IntValue {
I128([u8; 16]), I128([u8; 16]),
U128([u8; 16]), U128([u8; 16]),

View file

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

View file

@ -1,7 +1,7 @@
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use roc_can::constraint::{Constraint, Constraints}; use roc_can::constraint::{Constraint, Constraints};
use roc_can::expected::Expected::{self, *}; use roc_can::expected::Expected::{self, *};
use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumBound, SignDemand}; use roc_can::num::{FloatBound, FloatWidth, IntBound, IntLitWidth, NumBound, SignDemand};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::Region; use roc_region::all::Region;
use roc_types::num::NumericRange; use roc_types::num::NumericRange;
@ -10,47 +10,54 @@ use roc_types::types::Type::{self, *};
use roc_types::types::{AliasKind, Category}; use roc_types::types::{AliasKind, Category};
use roc_types::types::{OptAbleType, Reason}; use roc_types::types::{OptAbleType, Reason};
#[must_use]
#[inline(always)] #[inline(always)]
pub fn add_numeric_bound_constr( pub fn add_numeric_bound_constr(
constraints: &mut Constraints, constraints: &mut Constraints,
num_constraints: &mut impl Extend<Constraint>, num_constraints: &mut impl Extend<Constraint>,
num_type: Type, num_var: Variable,
precision_var: Variable,
bound: impl TypedNumericBound, bound: impl TypedNumericBound,
region: Region, region: Region,
category: Category, category: Category,
) -> Type { ) -> Type {
let range = bound.numeric_bound(); 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 { match range {
NumericBound::None => { NumericBound::None => {
// no additional constraints // no additional constraints, just a Num *
total_num_type num_num(Variable(num_var))
} }
NumericBound::FloatExact(width) => { NumericBound::FloatExact(width) => {
let actual_type = Variable(float_width_to_variable(width)); let actual_type = Variable(float_width_to_variable(width));
let expected = Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region); let expected = Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region);
let because_suffix = let because_suffix =
constraints.equal_types(total_num_type.clone(), expected, category, region); constraints.equal_types(Variable(num_var), expected, category, region);
num_constraints.extend([because_suffix]); num_constraints.extend([because_suffix]);
total_num_type Variable(num_var)
} }
NumericBound::IntExact(width) => { NumericBound::IntExact(width) => {
let actual_type = Variable(int_width_to_variable(width)); let actual_type = Variable(int_lit_width_to_variable(width));
let expected = Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region); let expected = Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region);
let because_suffix = let because_suffix =
constraints.equal_types(total_num_type.clone(), expected, category, region); constraints.equal_types(Variable(num_var), expected, category, region);
num_constraints.extend([because_suffix]); num_constraints.extend([because_suffix]);
total_num_type Variable(num_var)
}
NumericBound::Range(range) => {
let actual_type = Variable(precision_var);
let expected = Expected::NoExpectation(RangedNumber(range));
let constr = constraints.equal_types(actual_type, expected, category, region);
num_constraints.extend([constr]);
num_num(Variable(num_var))
} }
NumericBound::Range(range) => RangedNumber(Box::new(total_num_type), range),
} }
} }
@ -70,7 +77,8 @@ pub fn int_literal(
let num_type = add_numeric_bound_constr( let num_type = add_numeric_bound_constr(
constraints, constraints,
&mut constrs, &mut constrs,
Variable(num_var), num_var,
precision_var,
bound, bound,
region, region,
Category::Num, Category::Num,
@ -106,7 +114,8 @@ pub fn float_literal(
let num_type = add_numeric_bound_constr( let num_type = add_numeric_bound_constr(
constraints, constraints,
&mut constrs, &mut constrs,
Variable(num_var), num_var,
precision_var,
bound, bound,
region, region,
Category::Float, Category::Float,
@ -134,13 +143,12 @@ pub fn num_literal(
region: Region, region: Region,
bound: NumBound, bound: NumBound,
) -> Constraint { ) -> Constraint {
let open_number_type = crate::builtins::num_num(Type::Variable(num_var));
let mut constrs = ArrayVec::<_, 2>::new(); let mut constrs = ArrayVec::<_, 2>::new();
let num_type = add_numeric_bound_constr( let num_type = add_numeric_bound_constr(
constraints, constraints,
&mut constrs, &mut constrs,
open_number_type, num_var,
num_var,
bound, bound,
region, region,
Category::Num, Category::Num,
@ -330,6 +338,6 @@ impl TypedNumericBound for NumBound {
pub enum NumericBound { pub enum NumericBound {
None, None,
FloatExact(FloatWidth), FloatExact(FloatWidth),
IntExact(IntWidth), IntExact(IntLitWidth),
Range(NumericRange), Range(NumericRange),
} }

View file

@ -226,15 +226,14 @@ pub fn constrain_pattern(
); );
} }
&NumLiteral(var, _, _, bound) => { &NumLiteral(precision_var, _, _, bound) => {
state.vars.push(var); state.vars.push(precision_var);
let num_type = builtins::num_num(Type::Variable(var));
let num_type = builtins::add_numeric_bound_constr( let num_type = builtins::add_numeric_bound_constr(
constraints, constraints,
&mut state.constraints, &mut state.constraints,
num_type, precision_var,
precision_var,
bound, bound,
region, region,
Category::Num, Category::Num,
@ -248,13 +247,14 @@ pub fn constrain_pattern(
)); ));
} }
&IntLiteral(num_var, precision_var, _, _, bound) => { &IntLiteral(num_precision_var, precision_var, _, _, bound) => {
// First constraint on the free num var; this improves the resolved type quality in // First constraint on the free num var; this improves the resolved type quality in
// case the bound is an alias. // case the bound is an alias.
let num_type = builtins::add_numeric_bound_constr( let num_type = builtins::add_numeric_bound_constr(
constraints, constraints,
&mut state.constraints, &mut state.constraints,
Type::Variable(num_var), num_precision_var,
num_precision_var,
bound, bound,
region, region,
Category::Int, Category::Int,
@ -264,7 +264,7 @@ pub fn constrain_pattern(
let int_type = builtins::num_int(Type::Variable(precision_var)); let int_type = builtins::num_int(Type::Variable(precision_var));
state.constraints.push(constraints.equal_types( state.constraints.push(constraints.equal_types(
num_type, // TODO check me if something breaks! num_type.clone(), // TODO check me if something breaks!
Expected::NoExpectation(int_type), Expected::NoExpectation(int_type),
Category::Int, Category::Int,
region, region,
@ -272,20 +272,21 @@ pub fn constrain_pattern(
// Also constrain the pattern against the num var, again to reuse aliases if they're present. // Also constrain the pattern against the num var, again to reuse aliases if they're present.
state.constraints.push(constraints.equal_pattern_types( state.constraints.push(constraints.equal_pattern_types(
Type::Variable(num_var), num_type,
expected, expected,
PatternCategory::Int, PatternCategory::Int,
region, region,
)); ));
} }
&FloatLiteral(num_var, precision_var, _, _, bound) => { &FloatLiteral(num_precision_var, precision_var, _, _, bound) => {
// First constraint on the free num var; this improves the resolved type quality in // First constraint on the free num var; this improves the resolved type quality in
// case the bound is an alias. // case the bound is an alias.
let num_type = builtins::add_numeric_bound_constr( let num_type = builtins::add_numeric_bound_constr(
constraints, constraints,
&mut state.constraints, &mut state.constraints,
Type::Variable(num_var), num_precision_var,
num_precision_var,
bound, bound,
region, region,
Category::Float, Category::Float,

View file

@ -151,7 +151,7 @@ impl FlatEncodable {
// by the backend, and the backend treats opaques like structural aliases. // by the backend, and the backend treats opaques like structural aliases.
_ => Self::from_var(subs, real_var), _ => Self::from_var(subs, real_var),
}, },
Content::RangedNumber(real_var, _) => Self::from_var(subs, real_var), Content::RangedNumber(_) => Err(Underivable),
// //
Content::RecursionVar { .. } => Err(Underivable), Content::RecursionVar { .. } => Err(Underivable),
Content::Error => Err(Underivable), Content::Error => Err(Underivable),

View file

@ -53,10 +53,8 @@ pub fn deep_copy_type_vars_into_expr<'a>(
let go_help = |e: &Expr| help(subs, e, substitutions); let go_help = |e: &Expr| help(subs, e, substitutions);
match expr { match expr {
Num(var, str, val, bound) => Num(sub!(*var), str.clone(), val.clone(), *bound), Num(var, str, val, bound) => Num(sub!(*var), str.clone(), *val, *bound),
Int(v1, v2, str, val, bound) => { Int(v1, v2, str, val, bound) => Int(sub!(*v1), sub!(*v2), str.clone(), *val, *bound),
Int(sub!(*v1), sub!(*v2), str.clone(), val.clone(), *bound)
}
Float(v1, v2, str, val, bound) => { Float(v1, v2, str, val, bound) => {
Float(sub!(*v1), sub!(*v2), str.clone(), *val, *bound) Float(sub!(*v1), sub!(*v2), str.clone(), *val, *bound)
} }
@ -663,10 +661,8 @@ fn deep_copy_type_vars<'a>(
}) })
} }
RangedNumber(typ, range) => { RangedNumber(range) => {
let new_typ = descend_var!(typ); perform_clone!(RangedNumber(range))
perform_clone!(RangedNumber(new_typ, range))
} }
Error => Error, Error => Error,
}; };

View file

@ -17,7 +17,7 @@ use roc_debug_flags::{
ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE, ROC_PRINT_IR_AFTER_SPECIALIZATION, ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE, ROC_PRINT_IR_AFTER_SPECIALIZATION,
}; };
use roc_derive_key::GlobalDerivedSymbols; use roc_derive_key::GlobalDerivedSymbols;
use roc_error_macros::todo_abilities; use roc_error_macros::{internal_error, todo_abilities};
use roc_exhaustive::{Ctor, CtorName, Guard, RenderAs, TagId}; use roc_exhaustive::{Ctor, CtorName, Guard, RenderAs, TagId};
use roc_late_solve::{resolve_ability_specialization, AbilitiesView, Resolved, UnificationFailed}; use roc_late_solve::{resolve_ability_specialization, AbilitiesView, Resolved, UnificationFailed};
use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
@ -3610,66 +3610,22 @@ fn specialize_naked_symbol<'a>(
) )
} }
fn try_make_literal<'a>( fn try_make_literal<'a>(can_expr: &roc_can::expr::Expr, layout: Layout<'a>) -> Option<Literal<'a>> {
env: &mut Env<'a, '_>,
can_expr: &roc_can::expr::Expr,
) -> Option<Literal<'a>> {
use roc_can::expr::Expr::*; use roc_can::expr::Expr::*;
match can_expr { match can_expr {
Int(_, precision, _, int, _bound) => { Int(_, _, int_str, int, _bound) => {
match num_argument_to_int_or_float(env.subs, env.target_info, *precision, false) { Some(make_num_literal(layout, int_str, IntOrFloatValue::Int(*int)).to_expr_literal())
IntOrFloat::Int(_) => Some(match *int {
IntValue::I128(n) => Literal::Int(n),
IntValue::U128(n) => Literal::U128(n),
}),
_ => unreachable!("unexpected float precision for integer"),
}
} }
Float(_, precision, float_str, float, _bound) => { Float(_, _, float_str, float, _bound) => Some(
match num_argument_to_int_or_float(env.subs, env.target_info, *precision, true) { make_num_literal(layout, float_str, IntOrFloatValue::Float(*float)).to_expr_literal(),
IntOrFloat::Float(_) => Some(Literal::Float(*float)), ),
IntOrFloat::DecimalFloatType => {
let dec = match RocDec::from_str(float_str) {
Some(d) => d,
None => panic!(
r"Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message",
float_str
),
};
Some(Literal::Decimal(dec.to_ne_bytes()))
}
_ => unreachable!("unexpected float precision for integer"),
}
}
// TODO investigate lifetime trouble // TODO investigate lifetime trouble
// Str(string) => Some(Literal::Str(env.arena.alloc(string))), // Str(string) => Some(Literal::Str(env.arena.alloc(string))),
Num(var, num_str, num, _bound) => { Num(_, num_str, num, _bound) => {
// first figure out what kind of number this is Some(make_num_literal(layout, num_str, IntOrFloatValue::Int(*num)).to_expr_literal())
match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) {
IntOrFloat::Int(_) => Some(match *num {
IntValue::I128(n) => Literal::Int(n),
IntValue::U128(n) => Literal::U128(n),
}),
IntOrFloat::Float(_) => Some(match *num {
IntValue::I128(n) => Literal::Float(i128::from_ne_bytes(n) as f64),
IntValue::U128(n) => Literal::Float(u128::from_ne_bytes(n) as f64),
}),
IntOrFloat::DecimalFloatType => {
let dec = match RocDec::from_str(num_str) {
Some(d) => d,
None => panic!(
r"Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message",
num_str
),
};
Some(Literal::Decimal(dec.to_ne_bytes()))
}
}
} }
_ => None, _ => None,
} }
@ -3689,44 +3645,35 @@ pub fn with_hole<'a>(
let arena = env.arena; let arena = env.arena;
match can_expr { match can_expr {
Int(_, precision, _, int, _bound) => { Int(_, _, int_str, int, _bound) => assign_num_literal_expr(
match num_argument_to_int_or_float(env.subs, env.target_info, precision, false) { env,
IntOrFloat::Int(precision) => Stmt::Let( layout_cache,
assigned, assigned,
Expr::Literal(match int { variable,
IntValue::I128(n) => Literal::Int(n), &int_str,
IntValue::U128(n) => Literal::U128(n), IntOrFloatValue::Int(int),
}), hole,
Layout::Builtin(Builtin::Int(precision)), ),
hole,
),
_ => unreachable!("unexpected float precision for integer"),
}
}
Float(_, precision, float_str, float, _bound) => { Float(_, _, float_str, float, _bound) => assign_num_literal_expr(
match num_argument_to_int_or_float(env.subs, env.target_info, precision, true) { env,
IntOrFloat::Float(precision) => Stmt::Let( layout_cache,
assigned, assigned,
Expr::Literal(Literal::Float(float)), variable,
Layout::Builtin(Builtin::Float(precision)), &float_str,
hole, IntOrFloatValue::Float(float),
), hole,
IntOrFloat::DecimalFloatType => { ),
let dec = match RocDec::from_str(&float_str) {
Some(d) => d, Num(_, num_str, num, _bound) => assign_num_literal_expr(
None => panic!("Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message", float_str), env,
}; layout_cache,
Stmt::Let( assigned,
assigned, variable,
Expr::Literal(Literal::Decimal(dec.to_ne_bytes())), &num_str,
Layout::Builtin(Builtin::Decimal), IntOrFloatValue::Int(num),
hole, hole,
) ),
}
_ => unreachable!("unexpected float precision for integer"),
}
}
Str(string) => Stmt::Let( Str(string) => Stmt::Let(
assigned, assigned,
@ -3741,42 +3688,6 @@ pub fn with_hole<'a>(
Layout::int_width(IntWidth::I32), Layout::int_width(IntWidth::I32),
hole, hole,
), ),
Num(var, num_str, num, _bound) => {
// first figure out what kind of number this is
match num_argument_to_int_or_float(env.subs, env.target_info, var, false) {
IntOrFloat::Int(precision) => Stmt::Let(
assigned,
Expr::Literal(match num {
IntValue::I128(n) => Literal::Int(n),
IntValue::U128(n) => Literal::U128(n),
}),
Layout::int_width(precision),
hole,
),
IntOrFloat::Float(precision) => Stmt::Let(
assigned,
Expr::Literal(match num {
IntValue::I128(n) => Literal::Float(i128::from_ne_bytes(n) as f64),
IntValue::U128(n) => Literal::Float(u128::from_ne_bytes(n) as f64),
}),
Layout::float_width(precision),
hole,
),
IntOrFloat::DecimalFloatType => {
let dec = match RocDec::from_str(&num_str) {
Some(d) => d,
None => panic!("Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message", num_str),
};
Stmt::Let(
assigned,
Expr::Literal(Literal::Decimal(dec.to_ne_bytes())),
Layout::Builtin(Builtin::Decimal),
hole,
)
}
}
}
LetNonRec(def, cont) => from_can_let( LetNonRec(def, cont) => from_can_let(
env, env,
procs, procs,
@ -4281,8 +4192,12 @@ pub fn with_hole<'a>(
let mut symbol_exprs = Vec::with_capacity_in(loc_elems.len(), env.arena); let mut symbol_exprs = Vec::with_capacity_in(loc_elems.len(), env.arena);
let elem_layout = layout_cache
.from_var(env.arena, elem_var, env.subs)
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));
for arg_expr in loc_elems.into_iter() { for arg_expr in loc_elems.into_iter() {
if let Some(literal) = try_make_literal(env, &arg_expr.value) { if let Some(literal) = try_make_literal(&arg_expr.value, elem_layout) {
elements.push(ListLiteralElement::Literal(literal)); elements.push(ListLiteralElement::Literal(literal));
} else { } else {
let symbol = possible_reuse_symbol_or_specialize( let symbol = possible_reuse_symbol_or_specialize(
@ -4300,10 +4215,6 @@ pub fn with_hole<'a>(
} }
let arg_symbols = arg_symbols.into_bump_slice(); let arg_symbols = arg_symbols.into_bump_slice();
let elem_layout = layout_cache
.from_var(env.arena, elem_var, env.subs)
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));
let expr = Expr::Array { let expr = Expr::Array {
elem_layout, elem_layout,
elems: elements.into_bump_slice(), elems: elements.into_bump_slice(),
@ -8275,40 +8186,20 @@ fn from_can_pattern_help<'a>(
Underscore => Ok(Pattern::Underscore), Underscore => Ok(Pattern::Underscore),
Identifier(symbol) => Ok(Pattern::Identifier(*symbol)), Identifier(symbol) => Ok(Pattern::Identifier(*symbol)),
AbilityMemberSpecialization { ident, .. } => Ok(Pattern::Identifier(*ident)), AbilityMemberSpecialization { ident, .. } => Ok(Pattern::Identifier(*ident)),
IntLiteral(_, precision_var, _, int, _bound) => { IntLiteral(var, _, int_str, int, _bound) => Ok(make_num_literal_pattern(
match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, false) { env,
IntOrFloat::Int(precision) => match *int { layout_cache,
IntValue::I128(n) | IntValue::U128(n) => Ok(Pattern::IntLiteral(n, precision)), *var,
}, int_str,
other => { IntOrFloatValue::Int(*int),
panic!( )),
"Invalid precision for int pattern: {:?} has {:?}", FloatLiteral(var, _, float_str, float, _bound) => Ok(make_num_literal_pattern(
can_pattern, other env,
) layout_cache,
} *var,
} float_str,
} IntOrFloatValue::Float(*float),
FloatLiteral(_, precision_var, float_str, float, _bound) => { )),
// TODO: Can I reuse num_argument_to_int_or_float here if I pass in true?
match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, true) {
IntOrFloat::Int(_) => {
panic!("Invalid precision for float pattern {:?}", precision_var)
}
IntOrFloat::Float(precision) => {
Ok(Pattern::FloatLiteral(f64::to_bits(*float), precision))
}
IntOrFloat::DecimalFloatType => {
let dec = match RocDec::from_str(float_str) {
Some(d) => d,
None => panic!(
r"Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message",
float_str
),
};
Ok(Pattern::DecimalLiteral(dec.to_ne_bytes()))
}
}
}
StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())), StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())),
SingleQuote(c) => Ok(Pattern::IntLiteral( SingleQuote(c) => Ok(Pattern::IntLiteral(
(*c as i128).to_ne_bytes(), (*c as i128).to_ne_bytes(),
@ -8328,30 +8219,13 @@ fn from_can_pattern_help<'a>(
// TODO(opaques) should be `RuntimeError::OpaqueNotDefined` // TODO(opaques) should be `RuntimeError::OpaqueNotDefined`
Err(RuntimeError::UnsupportedPattern(loc_ident.region)) Err(RuntimeError::UnsupportedPattern(loc_ident.region))
} }
NumLiteral(var, num_str, num, _bound) => { NumLiteral(var, num_str, num, _bound) => Ok(make_num_literal_pattern(
match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) { env,
IntOrFloat::Int(precision) => Ok(match num { layout_cache,
IntValue::I128(num) | IntValue::U128(num) => { *var,
Pattern::IntLiteral(*num, precision) num_str,
} IntOrFloatValue::Int(*num),
}), )),
IntOrFloat::Float(precision) => {
// TODO: this may be lossy
let num = match *num {
IntValue::I128(n) => f64::to_bits(i128::from_ne_bytes(n) as f64),
IntValue::U128(n) => f64::to_bits(u128::from_ne_bytes(n) as f64),
};
Ok(Pattern::FloatLiteral(num, precision))
}
IntOrFloat::DecimalFloatType => {
let dec = match RocDec::from_str(num_str) {
Some(d) => d,
None => panic!("Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message", num_str),
};
Ok(Pattern::DecimalLiteral(dec.to_ne_bytes()))
}
}
}
AppliedTag { AppliedTag {
whole_var, whole_var,
@ -8962,77 +8836,99 @@ fn from_can_record_destruct<'a>(
}) })
} }
#[derive(Debug)] enum IntOrFloatValue {
pub enum IntOrFloat { Int(IntValue),
Int(IntWidth), Float(f64),
Float(FloatWidth),
DecimalFloatType,
} }
/// Given the `a` in `Num a`, determines whether it's an int or a float enum NumLiteral {
pub fn num_argument_to_int_or_float( Int([u8; 16], IntWidth),
subs: &Subs, U128([u8; 16]),
target_info: TargetInfo, Float(f64, FloatWidth),
var: Variable, Decimal([u8; 16]),
known_to_be_float: bool, }
) -> IntOrFloat {
match subs.get_content_without_compacting(var) {
Content::FlexVar(_) | Content::RigidVar(_) if known_to_be_float => {
IntOrFloat::Float(FloatWidth::F64)
}
Content::FlexVar(_) | Content::RigidVar(_) => IntOrFloat::Int(IntWidth::I64), // We default (Num *) to I64
Content::Alias(Symbol::NUM_INTEGER, args, _, _) => { impl NumLiteral {
debug_assert!(args.len() == 1); fn to_expr_literal(&self) -> Literal<'static> {
match *self {
// Recurse on the second argument NumLiteral::Int(n, _) => Literal::Int(n),
let var = subs[args.all_variables().into_iter().next().unwrap()]; NumLiteral::U128(n) => Literal::U128(n),
num_argument_to_int_or_float(subs, target_info, var, false) NumLiteral::Float(n, _) => Literal::Float(n),
} NumLiteral::Decimal(n) => Literal::Decimal(n),
other @ Content::Alias(symbol, args, _, _) => {
if let Some(int_width) = IntWidth::try_from_symbol(*symbol) {
return IntOrFloat::Int(int_width);
}
if let Some(float_width) = FloatWidth::try_from_symbol(*symbol) {
return IntOrFloat::Float(float_width);
}
match *symbol {
Symbol::NUM_FLOATINGPOINT => {
debug_assert!(args.len() == 1);
// Recurse on the second argument
let var = subs[args.all_variables().into_iter().next().unwrap()];
num_argument_to_int_or_float(subs, target_info, var, true)
}
Symbol::NUM_DECIMAL => IntOrFloat::DecimalFloatType,
Symbol::NUM_NAT | Symbol::NUM_NATURAL => {
let int_width = match target_info.ptr_width() {
roc_target::PtrWidth::Bytes4 => IntWidth::U32,
roc_target::PtrWidth::Bytes8 => IntWidth::U64,
};
IntOrFloat::Int(int_width)
}
_ => panic!(
"Unrecognized Num type argument for var {:?} with Content: {:?}",
var, other
),
}
}
other => {
panic!(
"Unrecognized Num type argument for var {:?} with Content: {:?}",
var, other
);
} }
} }
fn to_pattern(&self) -> Pattern<'static> {
match *self {
NumLiteral::Int(n, w) => Pattern::IntLiteral(n, w),
NumLiteral::U128(_) => todo!(),
NumLiteral::Float(n, w) => Pattern::FloatLiteral(f64::to_bits(n), w),
NumLiteral::Decimal(n) => Pattern::DecimalLiteral(n),
}
}
}
fn make_num_literal(layout: Layout<'_>, num_str: &str, num_value: IntOrFloatValue) -> NumLiteral {
match layout {
Layout::Builtin(Builtin::Int(width)) => match num_value {
IntOrFloatValue::Int(IntValue::I128(n)) => NumLiteral::Int(n, width),
IntOrFloatValue::Int(IntValue::U128(n)) => NumLiteral::U128(n),
IntOrFloatValue::Float(..) => {
internal_error!("Float value where int was expected, should have been a type error")
}
},
Layout::Builtin(Builtin::Float(width)) => match num_value {
IntOrFloatValue::Float(n) => NumLiteral::Float(n, width),
IntOrFloatValue::Int(int_value) => match int_value {
IntValue::I128(n) => NumLiteral::Float(i128::from_ne_bytes(n) as f64, width),
IntValue::U128(n) => NumLiteral::Float(u128::from_ne_bytes(n) as f64, width),
},
},
Layout::Builtin(Builtin::Decimal) => {
let dec = match RocDec::from_str(num_str) {
Some(d) => d,
None => internal_error!(
"Invalid decimal for float literal = {}. This should be a type error!",
num_str
),
};
NumLiteral::Decimal(dec.to_ne_bytes())
}
layout => internal_error!(
"Found a non-num layout where a number was expected: {:?}",
layout
),
}
}
fn assign_num_literal_expr<'a>(
env: &mut Env<'a, '_>,
layout_cache: &mut LayoutCache<'a>,
assigned: Symbol,
variable: Variable,
num_str: &str,
num_value: IntOrFloatValue,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
let layout = layout_cache
.from_var(env.arena, variable, env.subs)
.unwrap();
let literal = make_num_literal(layout, num_str, num_value).to_expr_literal();
Stmt::Let(assigned, Expr::Literal(literal), layout, hole)
}
fn make_num_literal_pattern<'a>(
env: &mut Env<'a, '_>,
layout_cache: &mut LayoutCache<'a>,
variable: Variable,
num_str: &str,
num_value: IntOrFloatValue,
) -> Pattern<'a> {
let layout = layout_cache
.from_var(env.arena, variable, env.subs)
.unwrap();
let literal = make_num_literal(layout, num_str, num_value);
literal.to_pattern()
} }
type ToLowLevelCallArguments<'a> = ( type ToLowLevelCallArguments<'a> = (

View file

@ -8,6 +8,7 @@ use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
use roc_problem::can::RuntimeError; use roc_problem::can::RuntimeError;
use roc_target::{PtrWidth, TargetInfo}; use roc_target::{PtrWidth, TargetInfo};
use roc_types::num::NumericRange;
use roc_types::subs::{ use roc_types::subs::{
self, Content, FlatType, Label, RecordFields, Subs, UnionTags, UnsortedUnionLabels, Variable, self, Content, FlatType, Label, RecordFields, Subs, UnionTags, UnsortedUnionLabels, Variable,
}; };
@ -81,7 +82,9 @@ impl<'a> RawFunctionLayout<'a> {
} }
LambdaSet(lset) => Self::layout_from_lambda_set(env, lset), LambdaSet(lset) => Self::layout_from_lambda_set(env, lset),
Structure(flat_type) => Self::layout_from_flat_type(env, flat_type), Structure(flat_type) => Self::layout_from_flat_type(env, flat_type),
RangedNumber(typ, _) => Self::from_var(env, typ), RangedNumber(..) => Ok(Self::ZeroArgumentThunk(Layout::new_help(
env, var, content,
)?)),
// Ints // Ints
Alias(Symbol::NUM_I128, args, _, _) => { Alias(Symbol::NUM_I128, args, _, _) => {
@ -1205,6 +1208,16 @@ pub fn is_unresolved_var(subs: &Subs, var: Variable) -> bool {
) )
} }
#[inline(always)]
pub fn is_any_float_range(subs: &Subs, var: Variable) -> bool {
use {roc_types::num::IntLitWidth::*, Content::*, NumericRange::*};
let content = subs.get_content_without_compacting(var);
matches!(
content,
RangedNumber(NumAtLeastEitherSign(I8) | NumAtLeastSigned(I8)),
)
}
impl<'a> Layout<'a> { impl<'a> Layout<'a> {
pub const VOID: Self = Layout::Union(UnionLayout::NonRecursive(&[])); pub const VOID: Self = Layout::Union(UnionLayout::NonRecursive(&[]));
pub const UNIT: Self = Layout::Struct { pub const UNIT: Self = Layout::Struct {
@ -1252,7 +1265,8 @@ impl<'a> Layout<'a> {
} }
Symbol::NUM_FRAC | Symbol::NUM_FLOATINGPOINT Symbol::NUM_FRAC | Symbol::NUM_FLOATINGPOINT
if is_unresolved_var(env.subs, actual_var) => if is_unresolved_var(env.subs, actual_var)
|| is_any_float_range(env.subs, actual_var) =>
{ {
// default to f64 // default to f64
return Ok(Layout::f64()); return Ok(Layout::f64());
@ -1262,12 +1276,47 @@ impl<'a> Layout<'a> {
} }
} }
RangedNumber(typ, _) => Self::from_var(env, typ), RangedNumber(range) => Self::layout_from_ranged_number(env, range),
Error => Err(LayoutProblem::Erroneous), Error => Err(LayoutProblem::Erroneous),
} }
} }
fn layout_from_ranged_number(
env: &mut Env<'a, '_>,
range: NumericRange,
) -> Result<Self, LayoutProblem> {
use roc_types::num::IntLitWidth;
// If we chose the default int layout then the real var might have been `Num *`, or
// similar. In this case fix-up width if we need to. Choose I64 if the range says
// that the number will fit, otherwise choose the next-largest number layout.
//
// We don't pass the range down because `RangedNumber`s are somewhat rare, they only
// appear due to number literals, so no need to increase parameter list sizes.
let num_layout = match range {
NumericRange::IntAtLeastSigned(w) | NumericRange::NumAtLeastSigned(w) => {
[IntLitWidth::I64, IntLitWidth::I128]
.iter()
.find(|candidate| candidate.is_superset(&w, true))
.expect("if number doesn't fit, should have been a type error")
}
NumericRange::IntAtLeastEitherSign(w) | NumericRange::NumAtLeastEitherSign(w) => [
IntLitWidth::I64,
IntLitWidth::U64,
IntLitWidth::I128,
IntLitWidth::U128,
]
.iter()
.find(|candidate| candidate.is_superset(&w, false))
.expect("if number doesn't fit, should have been a type error"),
};
Ok(Layout::int_literal_width_to_int(
*num_layout,
env.target_info,
))
}
/// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure. /// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure.
/// Panics if given a FlexVar or RigidVar, since those should have been /// Panics if given a FlexVar or RigidVar, since those should have been
/// monomorphized away already! /// monomorphized away already!
@ -1724,6 +1773,32 @@ impl<'a> Layout<'a> {
pub fn default_float() -> Layout<'a> { pub fn default_float() -> Layout<'a> {
Layout::f64() Layout::f64()
} }
pub fn int_literal_width_to_int(
width: roc_types::num::IntLitWidth,
target_info: TargetInfo,
) -> Layout<'a> {
use roc_types::num::IntLitWidth::*;
match width {
U8 => Layout::u8(),
U16 => Layout::u16(),
U32 => Layout::u32(),
U64 => Layout::u64(),
U128 => Layout::u128(),
I8 => Layout::i8(),
I16 => Layout::i16(),
I32 => Layout::i32(),
I64 => Layout::i64(),
I128 => Layout::i128(),
Nat => Layout::usize(target_info),
// f32 int literal bounded by +/- 2^24, so fit it into an i32
F32 => Layout::i32(),
// f64 int literal bounded by +/- 2^53, so fit it into an i32
F64 => Layout::i64(),
// dec int literal bounded by i128, so fit it into an i128
Dec => Layout::i128(),
}
}
} }
impl<'a> Builtin<'a> { impl<'a> Builtin<'a> {

View file

@ -144,7 +144,7 @@ impl FunctionLayout {
Content::LambdaSet(lset) => Self::from_lambda_set(layouts, subs, *lset), Content::LambdaSet(lset) => Self::from_lambda_set(layouts, subs, *lset),
Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type), Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type),
Content::Alias(_, _, actual, _) => Self::from_var_help(layouts, subs, *actual), Content::Alias(_, _, actual, _) => Self::from_var_help(layouts, subs, *actual),
Content::RangedNumber(actual, _) => Self::from_var_help(layouts, subs, *actual), Content::RangedNumber(_) => todo!(),
Content::Error => Err(TypeError(())), Content::Error => Err(TypeError(())),
} }
} }
@ -263,7 +263,7 @@ impl LambdaSet {
Content::LambdaSet(lset) => Self::from_lambda_set(layouts, subs, *lset), Content::LambdaSet(lset) => Self::from_lambda_set(layouts, subs, *lset),
Content::Structure(_flat_type) => unreachable!(), Content::Structure(_flat_type) => unreachable!(),
Content::Alias(_, _, actual, _) => Self::from_var_help(layouts, subs, *actual), Content::Alias(_, _, actual, _) => Self::from_var_help(layouts, subs, *actual),
Content::RangedNumber(actual, _) => Self::from_var_help(layouts, subs, *actual), Content::RangedNumber(_) => todo!(),
Content::Error => Err(TypeError(())), Content::Error => Err(TypeError(())),
} }
} }
@ -686,7 +686,7 @@ impl Layout {
} }
} }
} }
Content::RangedNumber(typ, _) => Self::from_var_help(layouts, subs, *typ), Content::RangedNumber(_) => todo!(),
Content::Error => Err(TypeError(())), Content::Error => Err(TypeError(())),
} }
} }

View file

@ -1855,7 +1855,7 @@ fn compact_lambda_set<P: Phase>(
| FlexVar(..) | FlexVar(..)
| RecursionVar { .. } | RecursionVar { .. }
| LambdaSet(..) | LambdaSet(..)
| RangedNumber(_, _) => { | RangedNumber(_) => {
internal_error!("unexpected") internal_error!("unexpected")
} }
}; };
@ -2174,9 +2174,8 @@ fn type_to_variable<'a>(
Variable(_) | EmptyRec | EmptyTagUnion => { Variable(_) | EmptyRec | EmptyTagUnion => {
unreachable!("This variant should never be deferred!") unreachable!("This variant should never be deferred!")
} }
RangedNumber(typ, range) => { RangedNumber(range) => {
let ty_var = helper!(typ); let content = Content::RangedNumber(*range);
let content = Content::RangedNumber(ty_var, *range);
register_with_known_var(subs, destination, rank, pools, content) register_with_known_var(subs, destination, rank, pools, content)
} }
@ -3266,7 +3265,7 @@ fn adjust_rank_content(
rank rank
} }
RangedNumber(typ, _) => adjust_rank(subs, young_mark, visit_mark, group_rank, *typ), RangedNumber(_) => group_rank,
} }
} }
@ -3556,8 +3555,8 @@ fn deep_copy_var_help(
); );
} }
RangedNumber(typ, range) => { RangedNumber(range) => {
let new_content = RangedNumber(work!(typ), range); let new_content = RangedNumber(range);
subs.set_content_unchecked(copy, new_content); subs.set_content_unchecked(copy, new_content);
} }

View file

@ -1572,7 +1572,7 @@ mod solve_expr {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
Foo "happy" 2020 Foo "happy" 12
"# "#
), ),
"[Foo Str (Num *)]*", "[Foo Str (Num *)]*",
@ -2531,7 +2531,7 @@ mod solve_expr {
{ numIdentity, x : numIdentity 42, y } { numIdentity, x : numIdentity 42, y }
"# "#
), ),
"{ numIdentity : Num a -> Num a, x : Num b, y : Float * }", "{ numIdentity : Num a -> Num a, x : Num *, y : Float * }",
); );
} }
@ -3951,7 +3951,7 @@ mod solve_expr {
negatePoint { x: 1, y: 2.1, z: 0x3 } negatePoint { x: 1, y: 2.1, z: 0x3 }
"# "#
), ),
"{ x : Num a, y : Float *, z : Int * }", "{ x : Num *, y : Float *, z : Int * }",
); );
} }
@ -3968,7 +3968,7 @@ mod solve_expr {
{ a, b } { a, b }
"# "#
), ),
"{ a : { x : Num a, y : Float *, z : c }, b : { blah : Str, x : Num b, y : Float *, z : d } }", "{ a : { x : Num *, y : Float *, z : c }, b : { blah : Str, x : Num *, y : Float *, z : a } }",
); );
} }

View file

@ -3530,3 +3530,48 @@ fn round_to_u32() {
u32 u32
); );
} }
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn promote_u64_number_layout() {
assert_evals_to!(
indoc!(
r#"
9999999999999999999 + 1
"#
),
10000000000000000000,
u64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn promote_i128_number_layout() {
assert_evals_to!(
indoc!(
r#"
{
a: 18446744073709551616 + 1,
b: -9223372036854775809 + 1,
}
"#
),
(18446744073709551617, -9223372036854775808),
(i128, i128)
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn promote_u128_number_layout() {
assert_evals_to!(
indoc!(
r#"
170141183460469231731687303715884105728 + 1
"#
),
170141183460469231731687303715884105729,
u128
);
}

View file

@ -173,7 +173,7 @@ fn when_two_element_tag_first() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
x : [A (Int *), B (Int *)] x : [A (Int a), B (Int a)]
x = A 0x2 x = A 0x2
when x is when x is
@ -192,7 +192,7 @@ fn when_two_element_tag_second() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
x : [A (Int *), B (Int *)] x : [A (Int a), B (Int a)]
x = B 0x3 x = B 0x3
when x is when x is

View file

@ -0,0 +1,13 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.189 : I128 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.189;
procedure Test.0 ():
let Test.6 : I128 = 18446744073709551616i64;
let Test.7 : I128 = 1i64;
let Test.2 : I128 = CallByName Num.19 Test.6 Test.7;
let Test.4 : I128 = -9223372036854775809i64;
let Test.5 : I128 = 1i64;
let Test.3 : I128 = CallByName Num.19 Test.4 Test.5;
let Test.1 : {I128, I128} = Struct {Test.2, Test.3};
ret Test.1;

View file

@ -0,0 +1,9 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : U128 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
procedure Test.0 ():
let Test.2 : U128 = 170141183460469231731687303715884105728u128;
let Test.3 : U128 = 1i64;
let Test.1 : U128 = CallByName Num.19 Test.2 Test.3;
ret Test.1;

View file

@ -0,0 +1,9 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
procedure Test.0 ():
let Test.2 : U64 = 9999999999999999999i64;
let Test.3 : U64 = 1i64;
let Test.1 : U64 = CallByName Num.19 Test.2 Test.3;
ret Test.1;

View file

@ -1652,3 +1652,33 @@ fn lambda_set_niche_same_layout_different_constructor() {
"# "#
) )
} }
#[mono_test]
fn choose_u64_layout() {
indoc!(
r#"
9999999999999999999 + 1
"#
)
}
#[mono_test]
fn choose_i128_layout() {
indoc!(
r#"
{
a: 18446744073709551616 + 1,
b: -9223372036854775809 + 1,
}
"#
)
}
#[mono_test]
fn choose_u128_layout() {
indoc!(
r#"
170141183460469231731687303715884105728 + 1
"#
)
}

View file

@ -1,54 +1,22 @@
use crate::subs::Variable; use crate::subs::Variable;
use roc_module::symbol::Symbol;
/// A bound placed on a number because of its literal value. /// 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 /// e.g. `-5` cannot be unsigned, and 300 does not fit in a U8
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NumericRange { pub enum NumericRange {
IntAtLeastSigned(IntWidth), IntAtLeastSigned(IntLitWidth),
IntAtLeastEitherSign(IntWidth), IntAtLeastEitherSign(IntLitWidth),
NumAtLeastSigned(IntWidth), NumAtLeastSigned(IntLitWidth),
NumAtLeastEitherSign(IntWidth), NumAtLeastEitherSign(IntLitWidth),
} }
impl NumericRange { impl NumericRange {
pub fn contains_symbol(&self, symbol: Symbol) -> Option<bool> { pub fn contains_float_width(&self, _width: FloatWidth) -> 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_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 // we don't currently check the float width
true true
} }
fn contains_int_width(&self, width: IntWidth) -> bool { pub fn contains_int_width(&self, width: IntLitWidth) -> bool {
use NumericRange::*; use NumericRange::*;
let (range_signedness, at_least_width) = match self { let (range_signedness, at_least_width) = match self {
@ -68,35 +36,88 @@ impl NumericRange {
width.signedness_and_width().1 >= at_least_width.signedness_and_width().1 width.signedness_and_width().1 >= at_least_width.signedness_and_width().1
} }
fn width(&self) -> IntLitWidth {
use NumericRange::*;
match self {
IntAtLeastSigned(w)
| IntAtLeastEitherSign(w)
| NumAtLeastSigned(w)
| NumAtLeastEitherSign(w) => *w,
}
}
/// Returns the intersection of `self` and `other`, i.e. the greatest lower bound of both, or
/// `None` if there is no common lower bound.
pub fn intersection(&self, other: &Self) -> Option<Self> {
use NumericRange::*;
let (left, right) = (self.width(), other.width());
let (constructor, is_negative): (fn(IntLitWidth) -> NumericRange, _) = match (self, other) {
// Matching against a signed int, the intersection must also be a signed int
(IntAtLeastSigned(_), _) | (_, IntAtLeastSigned(_)) => (IntAtLeastSigned, true),
// It's a signed number, but also an int, so the intersection must be a signed int
(NumAtLeastSigned(_), IntAtLeastEitherSign(_))
| (IntAtLeastEitherSign(_), NumAtLeastSigned(_)) => (IntAtLeastSigned, true),
// It's a signed number
(NumAtLeastSigned(_), NumAtLeastSigned(_) | NumAtLeastEitherSign(_))
| (NumAtLeastEitherSign(_), NumAtLeastSigned(_)) => (NumAtLeastSigned, true),
// Otherwise we must be an int, signed or unsigned
(IntAtLeastEitherSign(_), IntAtLeastEitherSign(_) | NumAtLeastEitherSign(_))
| (NumAtLeastEitherSign(_), IntAtLeastEitherSign(_)) => (IntAtLeastEitherSign, false),
// Otherwise we must be a num, signed or unsigned
(NumAtLeastEitherSign(_), NumAtLeastEitherSign(_)) => (NumAtLeastEitherSign, false),
};
// If the intersection must be signed but one of the lower bounds isn't signed, then there
// is no intersection.
if is_negative && (!left.is_signed() || !right.is_signed()) {
None
}
// Otherwise, find the greatest lower bound depending on the signed-ness.
else if left.is_superset(&right, is_negative) {
Some(constructor(left))
} else if right.is_superset(&left, is_negative) {
Some(constructor(right))
} else {
None
}
}
pub fn variable_slice(&self) -> &'static [Variable] { pub fn variable_slice(&self) -> &'static [Variable] {
use NumericRange::*; use NumericRange::*;
match self { match self {
IntAtLeastSigned(width) => { IntAtLeastSigned(width) => {
let target = int_width_to_variable(*width); let target = int_lit_width_to_variable(*width);
let start = SIGNED_VARIABLES.iter().position(|v| *v == target).unwrap(); let start = SIGNED_INT_VARIABLES
let end = SIGNED_VARIABLES.len() - 3; .iter()
.position(|v| *v == target)
.unwrap();
&SIGNED_VARIABLES[start..end] &SIGNED_INT_VARIABLES[start..]
} }
IntAtLeastEitherSign(width) => { IntAtLeastEitherSign(width) => {
let target = int_width_to_variable(*width); let target = int_lit_width_to_variable(*width);
let start = ALL_VARIABLES.iter().position(|v| *v == target).unwrap(); let start = ALL_INT_VARIABLES.iter().position(|v| *v == target).unwrap();
let end = ALL_VARIABLES.len() - 3;
&ALL_VARIABLES[start..end] &ALL_INT_VARIABLES[start..]
} }
NumAtLeastSigned(width) => { NumAtLeastSigned(width) => {
let target = int_width_to_variable(*width); let target = int_lit_width_to_variable(*width);
let start = SIGNED_VARIABLES.iter().position(|v| *v == target).unwrap(); let start = SIGNED_INT_OR_FLOAT_VARIABLES
.iter()
.position(|v| *v == target)
.unwrap();
&SIGNED_VARIABLES[start..] &SIGNED_INT_OR_FLOAT_VARIABLES[start..]
} }
NumAtLeastEitherSign(width) => { NumAtLeastEitherSign(width) => {
let target = int_width_to_variable(*width); let target = int_lit_width_to_variable(*width);
let start = ALL_VARIABLES.iter().position(|v| *v == target).unwrap(); let start = ALL_INT_OR_FLOAT_VARIABLES
.iter()
.position(|v| *v == target)
.unwrap();
&ALL_VARIABLES[start..] &ALL_INT_OR_FLOAT_VARIABLES[start..]
} }
} }
} }
@ -109,7 +130,7 @@ enum IntSignedness {
} }
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum IntWidth { pub enum IntLitWidth {
U8, U8,
U16, U16,
U32, U32,
@ -121,13 +142,21 @@ pub enum IntWidth {
I64, I64,
I128, I128,
Nat, Nat,
// An int literal can be promoted to an f32/f64/Dec if appropriate. The respective widths for
// integers that can be stored in these float types without losing precision are:
// f32: +/- 2^24
// f64: +/- 2^53
// dec: Int128::MAX/Int128::MIN
F32,
F64,
Dec,
} }
impl IntWidth { impl IntLitWidth {
/// Returns the `IntSignedness` and bit width of a variant. /// Returns the `IntSignedness` and bit width of a variant.
fn signedness_and_width(&self) -> (IntSignedness, u32) { fn signedness_and_width(&self) -> (IntSignedness, u32) {
use IntLitWidth::*;
use IntSignedness::*; use IntSignedness::*;
use IntWidth::*;
match self { match self {
U8 => (Unsigned, 8), U8 => (Unsigned, 8),
U16 => (Unsigned, 16), U16 => (Unsigned, 16),
@ -139,13 +168,20 @@ impl IntWidth {
I32 => (Signed, 32), I32 => (Signed, 32),
I64 => (Signed, 64), I64 => (Signed, 64),
I128 => (Signed, 128), I128 => (Signed, 128),
// TODO: this is platform specific! // TODO: Nat is platform specific!
Nat => (Unsigned, 64), Nat => (Unsigned, 64),
F32 => (Signed, 24),
F64 => (Signed, 53),
Dec => (Signed, 128),
} }
} }
fn is_signed(&self) -> bool {
self.signedness_and_width().0 == IntSignedness::Signed
}
pub fn type_str(&self) -> &'static str { pub fn type_str(&self) -> &'static str {
use IntWidth::*; use IntLitWidth::*;
match self { match self {
U8 => "U8", U8 => "U8",
U16 => "U16", U16 => "U16",
@ -158,11 +194,14 @@ impl IntWidth {
I64 => "I64", I64 => "I64",
I128 => "I128", I128 => "I128",
Nat => "Nat", Nat => "Nat",
F32 => "F32",
F64 => "F64",
Dec => "Dec",
} }
} }
pub fn max_value(&self) -> u128 { pub fn max_value(&self) -> u128 {
use IntWidth::*; use IntLitWidth::*;
match self { match self {
U8 => u8::MAX as u128, U8 => u8::MAX as u128,
U16 => u16::MAX as u128, U16 => u16::MAX as u128,
@ -176,11 +215,17 @@ impl IntWidth {
I128 => i128::MAX as u128, I128 => i128::MAX as u128,
// TODO: this is platform specific! // TODO: this is platform specific!
Nat => u64::MAX as u128, Nat => u64::MAX as u128,
// Max int value without losing precision: 2^24
F32 => 16_777_216,
// Max int value without losing precision: 2^53
F64 => 9_007_199_254_740_992,
// Max int value without losing precision: I128::MAX
Dec => i128::MAX as u128,
} }
} }
pub fn min_value(&self) -> i128 { pub fn min_value(&self) -> i128 {
use IntWidth::*; use IntLitWidth::*;
match self { match self {
U8 | U16 | U32 | U64 | U128 | Nat => 0, U8 | U16 | U32 | U64 | U128 | Nat => 0,
I8 => i8::MIN as i128, I8 => i8::MIN as i128,
@ -188,6 +233,12 @@ impl IntWidth {
I32 => i32::MIN as i128, I32 => i32::MIN as i128,
I64 => i64::MIN as i128, I64 => i64::MIN as i128,
I128 => i128::MIN, I128 => i128::MIN,
// Min int value without losing precision: -2^24
F32 => -16_777_216,
// Min int value without losing precision: -2^53
F64 => -9_007_199_254_740_992,
// Min int value without losing precision: I128::MIN
Dec => i128::MIN,
} }
} }
@ -253,9 +304,12 @@ pub enum IntBound {
/// There is no bound on the width. /// There is no bound on the width.
None, None,
/// Must have an exact width. /// Must have an exact width.
Exact(IntWidth), Exact(IntLitWidth),
/// Must have a certain sign and a minimum width. /// Must have a certain sign and a minimum width.
AtLeast { sign: SignDemand, width: IntWidth }, AtLeast {
sign: SignDemand,
width: IntLitWidth,
},
} }
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
@ -270,23 +324,26 @@ pub enum NumBound {
/// Must be an integer of a certain size, or any float. /// Must be an integer of a certain size, or any float.
AtLeastIntOrFloat { AtLeastIntOrFloat {
sign: SignDemand, sign: SignDemand,
width: IntWidth, width: IntLitWidth,
}, },
} }
pub const fn int_width_to_variable(w: IntWidth) -> Variable { pub const fn int_lit_width_to_variable(w: IntLitWidth) -> Variable {
match w { match w {
IntWidth::U8 => Variable::U8, IntLitWidth::U8 => Variable::U8,
IntWidth::U16 => Variable::U16, IntLitWidth::U16 => Variable::U16,
IntWidth::U32 => Variable::U32, IntLitWidth::U32 => Variable::U32,
IntWidth::U64 => Variable::U64, IntLitWidth::U64 => Variable::U64,
IntWidth::U128 => Variable::U128, IntLitWidth::U128 => Variable::U128,
IntWidth::I8 => Variable::I8, IntLitWidth::I8 => Variable::I8,
IntWidth::I16 => Variable::I16, IntLitWidth::I16 => Variable::I16,
IntWidth::I32 => Variable::I32, IntLitWidth::I32 => Variable::I32,
IntWidth::I64 => Variable::I64, IntLitWidth::I64 => Variable::I64,
IntWidth::I128 => Variable::I128, IntLitWidth::I128 => Variable::I128,
IntWidth::Nat => Variable::NAT, IntLitWidth::Nat => Variable::NAT,
IntLitWidth::F32 => Variable::F32,
IntLitWidth::F64 => Variable::F64,
IntLitWidth::Dec => Variable::DEC,
} }
} }
@ -298,7 +355,35 @@ pub const fn float_width_to_variable(w: FloatWidth) -> Variable {
} }
} }
const ALL_VARIABLES: &[Variable] = &[ const ALL_INT_OR_FLOAT_VARIABLES: &[Variable] = &[
Variable::I8,
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 platform
Variable::U64,
Variable::I128,
Variable::DEC,
Variable::U128,
];
const SIGNED_INT_OR_FLOAT_VARIABLES: &[Variable] = &[
Variable::I8,
Variable::I16,
Variable::F32,
Variable::I32,
Variable::F64,
Variable::I64,
Variable::I128,
Variable::DEC,
];
const ALL_INT_VARIABLES: &[Variable] = &[
Variable::I8, Variable::I8,
Variable::U8, Variable::U8,
Variable::I16, Variable::I16,
@ -306,22 +391,16 @@ const ALL_VARIABLES: &[Variable] = &[
Variable::I32, Variable::I32,
Variable::U32, Variable::U32,
Variable::I64, Variable::I64,
Variable::NAT, // FIXME: Nat's order here depends on the platfor, Variable::NAT, // FIXME: Nat's order here depends on the platform
Variable::U64, Variable::U64,
Variable::I128, Variable::I128,
Variable::U128, Variable::U128,
Variable::F32,
Variable::F64,
Variable::DEC,
]; ];
const SIGNED_VARIABLES: &[Variable] = &[ const SIGNED_INT_VARIABLES: &[Variable] = &[
Variable::I8, Variable::I8,
Variable::I16, Variable::I16,
Variable::I32, Variable::I32,
Variable::I64, Variable::I64,
Variable::I128, Variable::I128,
Variable::F32,
Variable::F64,
Variable::DEC,
]; ];

View file

@ -381,9 +381,10 @@ fn find_names_needed(
); );
} }
} }
&RangedNumber(typ, _) => { RangedNumber(_) => {
subs.set_content(variable, FlexVar(None));
find_names_needed( find_names_needed(
typ, variable,
subs, subs,
roots, roots,
root_appearances, root_appearances,
@ -644,7 +645,7 @@ fn write_content<'a>(
subs, subs,
buf, buf,
parens, parens,
false, write_parens,
); );
} }
Symbol::NUM_FLOATINGPOINT => write_float( Symbol::NUM_FLOATINGPOINT => write_float(
@ -783,14 +784,23 @@ fn write_content<'a>(
buf.push(']'); buf.push(']');
} }
RangedNumber(typ, _range_vars) => write_content( RangedNumber(range) => {
env, buf.push_str("Range(");
ctx, for (i, &var) in range.variable_slice().iter().enumerate() {
subs.get_content_without_compacting(*typ), if i > 0 {
subs, buf.push_str(", ");
buf, }
parens, write_content(
), env,
ctx,
subs.get_content_without_compacting(var),
subs,
buf,
Parens::Unnecessary,
);
}
buf.push(')');
}
Error => buf.push_str("<type mismatch>"), Error => buf.push_str("<type mismatch>"),
} }
} }
@ -829,21 +839,23 @@ fn write_integer<'a>(
macro_rules! derive_num_writes { macro_rules! derive_num_writes {
($($lit:expr, $tag:path)*) => { ($($lit:expr, $tag:path)*) => {
write_parens!( match content {
write_parens, $(
buf, &Alias($tag, _, _, _) => {
match content { buf.push_str($lit)
$( },
&Alias($tag, _, _, _) => { )*
buf.push_str($lit) actual => {
}, write_parens!(
)* write_parens,
actual => { buf,
buf.push_str("Int "); {
write_content(env, ctx, actual, subs, buf, parens); buf.push_str("Int ");
} write_content(env, ctx, actual, subs, buf, parens);
}
)
} }
) }
} }
} }

View file

@ -841,8 +841,8 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt:
} }
write!(f, ")") write!(f, ")")
} }
Content::RangedNumber(typ, range) => { Content::RangedNumber(range) => {
write!(f, "RangedNumber({:?}, {:?})", typ, range) write!(f, "RangedNumber( {:?})", range)
} }
Content::Error => write!(f, "Error"), Content::Error => write!(f, "Error"),
} }
@ -2202,7 +2202,7 @@ pub enum Content {
LambdaSet(LambdaSet), LambdaSet(LambdaSet),
Structure(FlatType), Structure(FlatType),
Alias(Symbol, AliasVariables, Variable, AliasKind), Alias(Symbol, AliasVariables, Variable, AliasKind),
RangedNumber(Variable, crate::num::NumericRange), RangedNumber(crate::num::NumericRange),
Error, Error,
} }
@ -3150,15 +3150,7 @@ fn occurs(
occurs_union(subs, root_var, &new_seen, include_recursion_var, solved) occurs_union(subs, root_var, &new_seen, include_recursion_var, solved)
} }
RangedNumber(typ, _range_vars) => { RangedNumber(_range_vars) => Ok(()),
let mut new_seen = seen.to_owned();
new_seen.push(root_var);
short_circuit_help(subs, root_var, &new_seen, *typ, include_recursion_var)?;
// _range_vars excluded because they are not explicitly part of the type.
Ok(())
}
} }
} }
} }
@ -3345,10 +3337,8 @@ fn explicit_substitute(
in_var in_var
} }
RangedNumber(typ, range) => { RangedNumber(range) => {
let new_typ = explicit_substitute(subs, from, to, typ, seen); subs.set_content(in_var, RangedNumber(range));
subs.set_content(in_var, RangedNumber(new_typ, range));
in_var in_var
} }
@ -3462,7 +3452,7 @@ fn get_var_names(
taken_names taken_names
} }
RangedNumber(typ, _) => get_var_names(subs, typ, taken_names), RangedNumber(_) => taken_names,
Structure(flat_type) => match flat_type { Structure(flat_type) => match flat_type {
FlatType::Apply(_, args) => { FlatType::Apply(_, args) => {
@ -3685,6 +3675,13 @@ fn content_to_err_type(
Alias(symbol, args, aliased_to, kind) => { Alias(symbol, args, aliased_to, kind) => {
let err_type = var_to_err_type(subs, state, aliased_to); let err_type = var_to_err_type(subs, state, aliased_to);
// Lift RangedNumber up if needed.
if let (Symbol::NUM_INT | Symbol::NUM_NUM | Symbol::NUM_INTEGER, ErrorType::Range(_)) =
(symbol, &err_type)
{
return err_type;
}
let mut err_args = Vec::with_capacity(args.len()); let mut err_args = Vec::with_capacity(args.len());
for var_index in args.into_iter() { for var_index in args.into_iter() {
@ -3703,17 +3700,18 @@ fn content_to_err_type(
ErrorType::Error ErrorType::Error
} }
RangedNumber(typ, range) => { RangedNumber(range) => {
let err_type = var_to_err_type(subs, state, typ);
if state.context == ErrorTypeContext::ExpandRanges { if state.context == ErrorTypeContext::ExpandRanges {
let mut types = Vec::new(); let mut types = Vec::new();
for var in range.variable_slice() { for var in range.variable_slice() {
types.push(var_to_err_type(subs, state, *var)); types.push(var_to_err_type(subs, state, *var));
} }
ErrorType::Range(Box::new(err_type), types) ErrorType::Range(types)
} else { } else {
err_type let content = FlexVar(None);
subs.set_content(var, content);
subs.set_mark(var, Mark::NONE);
var_to_err_type(subs, state, var)
} }
} }
@ -3968,6 +3966,9 @@ impl StorageSubs {
pub fn as_inner_mut(&mut self) -> &mut Subs { pub fn as_inner_mut(&mut self) -> &mut Subs {
&mut self.subs &mut self.subs
} }
pub fn as_inner(&self) -> &Subs {
&self.subs
}
pub fn extend_with_variable(&mut self, source: &mut Subs, variable: Variable) -> Variable { pub fn extend_with_variable(&mut self, source: &mut Subs, variable: Variable) -> Variable {
storage_copy_var_to(source, &mut self.subs, variable) storage_copy_var_to(source, &mut self.subs, variable)
@ -4160,7 +4161,7 @@ impl StorageSubs {
recursion_var: recursion_var.map(|v| Self::offset_variable(offsets, v)), recursion_var: recursion_var.map(|v| Self::offset_variable(offsets, v)),
unspecialized: Self::offset_uls_slice(offsets, *unspecialized), unspecialized: Self::offset_uls_slice(offsets, *unspecialized),
}), }),
RangedNumber(typ, range) => RangedNumber(Self::offset_variable(offsets, *typ), *range), RangedNumber(range) => RangedNumber(*range),
Error => Content::Error, Error => Content::Error,
} }
} }
@ -4601,11 +4602,8 @@ fn storage_copy_var_to_help(env: &mut StorageCopyVarToEnv<'_>, var: Variable) ->
copy copy
} }
RangedNumber(typ, range) => { RangedNumber(range) => {
let new_typ = storage_copy_var_to_help(env, typ); let new_content = RangedNumber(range);
let new_content = RangedNumber(new_typ, range);
env.target.set(copy, make_descriptor(new_content)); env.target.set(copy, make_descriptor(new_content));
copy copy
} }
@ -4729,7 +4727,7 @@ fn is_registered(content: &Content) -> bool {
Content::Structure(_) Content::Structure(_)
| Content::RecursionVar { .. } | Content::RecursionVar { .. }
| Content::Alias(_, _, _, _) | Content::Alias(_, _, _, _)
| Content::RangedNumber(_, _) | Content::RangedNumber(_)
| Content::Error | Content::Error
| Content::LambdaSet(_) => true, | Content::LambdaSet(_) => true,
} }
@ -5067,10 +5065,8 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl
copy copy
} }
RangedNumber(typ, range) => { RangedNumber(range) => {
let new_typ = copy_import_to_help(env, max_rank, typ); let new_content = RangedNumber(range);
let new_content = RangedNumber(new_typ, range);
env.target.set(copy, make_descriptor(new_content)); env.target.set(copy, make_descriptor(new_content));
copy copy
@ -5232,9 +5228,7 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) {
stack.push(*var); stack.push(*var);
} }
} }
&RangedNumber(typ, _) => { &RangedNumber(_) => {}
stack.push(typ);
}
} }
} }

View file

@ -261,7 +261,7 @@ pub enum Type {
/// Applying a type to some arguments (e.g. Dict.Dict String Int) /// Applying a type to some arguments (e.g. Dict.Dict String Int)
Apply(Symbol, Vec<Type>, Region), Apply(Symbol, Vec<Type>, Region),
Variable(Variable), Variable(Variable),
RangedNumber(Box<Type>, NumericRange), RangedNumber(NumericRange),
/// A type error, which will code gen to a runtime error /// A type error, which will code gen to a runtime error
Erroneous(Problem), Erroneous(Problem),
} }
@ -349,7 +349,7 @@ impl Clone for Type {
} }
Self::Apply(arg0, arg1, arg2) => Self::Apply(*arg0, arg1.clone(), *arg2), Self::Apply(arg0, arg1, arg2) => Self::Apply(*arg0, arg1.clone(), *arg2),
Self::Variable(arg0) => Self::Variable(*arg0), Self::Variable(arg0) => Self::Variable(*arg0),
Self::RangedNumber(arg0, arg1) => Self::RangedNumber(arg0.clone(), *arg1), Self::RangedNumber(arg1) => Self::RangedNumber(*arg1),
Self::Erroneous(arg0) => Self::Erroneous(arg0.clone()), Self::Erroneous(arg0) => Self::Erroneous(arg0.clone()),
} }
} }
@ -652,8 +652,8 @@ impl fmt::Debug for Type {
write!(f, " as <{:?}>", rec) write!(f, " as <{:?}>", rec)
} }
Type::RangedNumber(typ, range_vars) => { Type::RangedNumber(range_vars) => {
write!(f, "Ranged({:?}, {:?})", typ, range_vars) write!(f, "Ranged({:?})", range_vars)
} }
Type::UnspecializedLambdaSet(uls) => { Type::UnspecializedLambdaSet(uls) => {
write!(f, "{:?}", uls) write!(f, "{:?}", uls)
@ -794,9 +794,7 @@ impl Type {
Apply(_, args, _) => { Apply(_, args, _) => {
stack.extend(args); stack.extend(args);
} }
RangedNumber(typ, _) => { RangedNumber(_) => {}
stack.push(typ);
}
UnspecializedLambdaSet(Uls(v, _, _)) => { UnspecializedLambdaSet(Uls(v, _, _)) => {
debug_assert!( debug_assert!(
substitutions.get(v).is_none(), substitutions.get(v).is_none(),
@ -911,9 +909,7 @@ impl Type {
Apply(_, args, _) => { Apply(_, args, _) => {
stack.extend(args); stack.extend(args);
} }
RangedNumber(typ, _) => { RangedNumber(_) => {}
stack.push(typ);
}
UnspecializedLambdaSet(Uls(v, _, _)) => { UnspecializedLambdaSet(Uls(v, _, _)) => {
debug_assert!( debug_assert!(
substitutions.get(v).is_none(), substitutions.get(v).is_none(),
@ -1016,7 +1012,7 @@ impl Type {
} }
Ok(()) Ok(())
} }
RangedNumber(typ, _) => typ.substitute_alias(rep_symbol, rep_args, actual), RangedNumber(_) => Ok(()),
UnspecializedLambdaSet(..) => Ok(()), UnspecializedLambdaSet(..) => Ok(()),
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => Ok(()), EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => Ok(()),
} }
@ -1073,7 +1069,7 @@ impl Type {
} }
Apply(symbol, _, _) if *symbol == rep_symbol => true, Apply(symbol, _, _) if *symbol == rep_symbol => true,
Apply(_, args, _) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)), Apply(_, args, _) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)),
RangedNumber(typ, _) => typ.contains_symbol(rep_symbol), RangedNumber(_) => false,
UnspecializedLambdaSet(Uls(_, sym, _)) => *sym == rep_symbol, UnspecializedLambdaSet(Uls(_, sym, _)) => *sym == rep_symbol,
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => false, EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => false,
} }
@ -1124,7 +1120,7 @@ impl Type {
} => actual_type.contains_variable(rep_variable), } => actual_type.contains_variable(rep_variable),
HostExposedAlias { actual, .. } => actual.contains_variable(rep_variable), HostExposedAlias { actual, .. } => actual.contains_variable(rep_variable),
Apply(_, args, _) => args.iter().any(|arg| arg.contains_variable(rep_variable)), Apply(_, args, _) => args.iter().any(|arg| arg.contains_variable(rep_variable)),
RangedNumber(typ, _) => typ.contains_variable(rep_variable), RangedNumber(_) => false,
EmptyRec | EmptyTagUnion | Erroneous(_) => false, EmptyRec | EmptyTagUnion | Erroneous(_) => false,
} }
} }
@ -1387,9 +1383,7 @@ impl Type {
} }
} }
} }
RangedNumber(typ, _) => { RangedNumber(_) => {}
typ.instantiate_aliases(region, aliases, var_store, new_lambda_set_variables);
}
UnspecializedLambdaSet(..) => {} UnspecializedLambdaSet(..) => {}
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {} EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {}
} }
@ -1525,9 +1519,7 @@ fn symbols_help(initial: &Type) -> Vec<Symbol> {
Erroneous(Problem::CyclicAlias(alias, _, _)) => { Erroneous(Problem::CyclicAlias(alias, _, _)) => {
output.push(*alias); output.push(*alias);
} }
RangedNumber(typ, _) => { RangedNumber(_) => {}
stack.push(typ);
}
UnspecializedLambdaSet(Uls(_, _sym, _)) => { UnspecializedLambdaSet(Uls(_, _sym, _)) => {
// ignore the member symbol because unspecialized lambda sets are internal-only // ignore the member symbol because unspecialized lambda sets are internal-only
} }
@ -1647,9 +1639,7 @@ fn variables_help(tipe: &Type, accum: &mut ImSet<Variable>) {
} }
variables_help(actual, accum); variables_help(actual, accum);
} }
RangedNumber(typ, _) => { RangedNumber(_) => {}
variables_help(typ, accum);
}
Apply(_, args, _) => { Apply(_, args, _) => {
for x in args { for x in args {
variables_help(x, accum); variables_help(x, accum);
@ -1790,9 +1780,7 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
} }
variables_help_detailed(actual, accum); variables_help_detailed(actual, accum);
} }
RangedNumber(typ, _) => { RangedNumber(_) => {}
variables_help_detailed(typ, accum);
}
Apply(_, args, _) => { Apply(_, args, _) => {
for x in args { for x in args {
variables_help_detailed(x, accum); variables_help_detailed(x, accum);
@ -2089,7 +2077,7 @@ pub enum ErrorType {
RecursiveTagUnion(Box<ErrorType>, SendMap<TagName, Vec<ErrorType>>, TypeExt), RecursiveTagUnion(Box<ErrorType>, SendMap<TagName, Vec<ErrorType>>, TypeExt),
Function(Vec<ErrorType>, Box<ErrorType>, Box<ErrorType>), Function(Vec<ErrorType>, Box<ErrorType>, Box<ErrorType>),
Alias(Symbol, Vec<ErrorType>, Box<ErrorType>, AliasKind), Alias(Symbol, Vec<ErrorType>, Box<ErrorType>, AliasKind),
Range(Box<ErrorType>, Vec<ErrorType>), Range(Vec<ErrorType>),
Error, Error,
} }
@ -2145,8 +2133,7 @@ impl ErrorType {
}); });
t.add_names(taken); t.add_names(taken);
} }
Range(typ, ts) => { Range(ts) => {
typ.add_names(taken);
ts.iter().for_each(|t| { ts.iter().for_each(|t| {
t.add_names(taken); t.add_names(taken);
}); });
@ -2472,8 +2459,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
write_debug_error_type_help(*rec, buf, Parens::Unnecessary); write_debug_error_type_help(*rec, buf, Parens::Unnecessary);
} }
Range(typ, types) => { Range(types) => {
write_debug_error_type_help(*typ, buf, parens);
buf.push('<'); buf.push('<');
let mut it = types.into_iter().peekable(); let mut it = types.into_iter().peekable();
@ -2825,9 +2811,7 @@ fn instantiate_lambda_sets_as_unspecialized(
stack.extend(args.iter_mut().rev()); stack.extend(args.iter_mut().rev());
} }
Type::Variable(_) => {} Type::Variable(_) => {}
Type::RangedNumber(t, _) => { Type::RangedNumber(_) => {}
stack.push(t);
}
Type::Erroneous(_) => {} Type::Erroneous(_) => {}
} }
} }

View file

@ -6,7 +6,7 @@ use roc_debug_flags::{ROC_PRINT_MISMATCHES, ROC_PRINT_UNIFICATIONS};
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_types::num::NumericRange; use roc_types::num::{FloatWidth, IntLitWidth, NumericRange};
use roc_types::subs::Content::{self, *}; use roc_types::subs::Content::{self, *};
use roc_types::subs::{ use roc_types::subs::{
AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, LambdaSet, Mark, AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, LambdaSet, Mark,
@ -475,7 +475,7 @@ fn unify_context<M: MetaCollector>(subs: &mut Subs, pool: &mut Pool, ctx: Contex
unify_opaque(subs, pool, &ctx, *symbol, *args, *real_var) unify_opaque(subs, pool, &ctx, *symbol, *args, *real_var)
} }
LambdaSet(lset) => unify_lambda_set(subs, pool, &ctx, *lset, &ctx.second_desc.content), LambdaSet(lset) => unify_lambda_set(subs, pool, &ctx, *lset, &ctx.second_desc.content),
&RangedNumber(typ, range_vars) => unify_ranged_number(subs, pool, &ctx, typ, range_vars), &RangedNumber(range_vars) => unify_ranged_number(subs, pool, &ctx, range_vars),
Error => { Error => {
// Error propagates. Whatever we're comparing it to doesn't matter! // Error propagates. Whatever we're comparing it to doesn't matter!
merge(subs, &ctx, Error) merge(subs, &ctx, Error)
@ -488,85 +488,171 @@ fn unify_context<M: MetaCollector>(subs: &mut Subs, pool: &mut Pool, ctx: Contex
result result
} }
fn not_in_range_mismatch<M: MetaCollector>() -> Outcome<M> {
Outcome {
mismatches: vec![Mismatch::TypeNotInRange],
must_implement_ability: Default::default(),
lambda_sets_to_specialize: Default::default(),
extra_metadata: Default::default(),
}
}
#[inline(always)] #[inline(always)]
fn unify_ranged_number<M: MetaCollector>( fn unify_ranged_number<M: MetaCollector>(
subs: &mut Subs, subs: &mut Subs,
pool: &mut Pool, pool: &mut Pool,
ctx: &Context, ctx: &Context,
real_var: Variable,
range_vars: NumericRange, range_vars: NumericRange,
) -> Outcome<M> { ) -> Outcome<M> {
let other_content = &ctx.second_desc.content; let other_content = &ctx.second_desc.content;
let outcome = match other_content { match other_content {
FlexVar(_) => { FlexVar(_) => {
// Ranged number wins // Ranged number wins
merge(subs, ctx, RangedNumber(real_var, range_vars)) merge(subs, ctx, RangedNumber(range_vars))
} }
RecursionVar { .. } RigidVar(name) => {
| RigidVar(..) // Int a vs Int <range>, the rigid wins
| Alias(..) merge(subs, ctx, RigidVar(*name))
| Structure(..)
| RigidAbleVar(..)
| FlexAbleVar(..) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode),
&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, ctx.first, other_range_vars)
} else {
outcome
}
// TODO: We should probably check that "range_vars" and "other_range_vars" intersect
} }
RecursionVar { .. } | Alias(..) | Structure(..) | RigidAbleVar(..) | FlexAbleVar(..) => {
check_and_merge_valid_range(subs, pool, ctx, ctx.first, range_vars, ctx.second)
}
&RangedNumber(other_range_vars) => match range_vars.intersection(&other_range_vars) {
Some(range) => merge(subs, ctx, RangedNumber(range)),
None => not_in_range_mismatch(),
},
LambdaSet(..) => mismatch!(), LambdaSet(..) => mismatch!(),
Error => merge(subs, ctx, Error), Error => merge(subs, ctx, Error),
};
if !outcome.mismatches.is_empty() {
return outcome;
} }
check_valid_range(subs, ctx.second, range_vars)
} }
fn check_valid_range<M: MetaCollector>( fn check_and_merge_valid_range<M: MetaCollector>(
subs: &mut Subs, subs: &mut Subs,
var: Variable, pool: &mut Pool,
ctx: &Context,
range_var: Variable,
range: NumericRange, range: NumericRange,
var: Variable,
) -> Outcome<M> { ) -> Outcome<M> {
let content = subs.get_content_without_compacting(var); use Content::*;
let content = *subs.get_content_without_compacting(var);
match content { macro_rules! merge_if {
&Content::Alias(symbol, _, actual, _) => { ($cond:expr) => {
match range.contains_symbol(symbol) { if $cond {
None => { merge(subs, ctx, content)
// symbol not recognized; go into the alias } else {
return check_valid_range(subs, actual, range); not_in_range_mismatch()
}
Some(false) => {
let outcome = Outcome {
mismatches: vec![Mismatch::TypeNotInRange],
must_implement_ability: Default::default(),
lambda_sets_to_specialize: Default::default(),
extra_metadata: 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::default() match content {
RangedNumber(other_range) => match range.intersection(&other_range) {
Some(r) => {
if r == range {
merge(subs, ctx, RangedNumber(range))
} else {
merge(subs, ctx, RangedNumber(other_range))
}
}
None => not_in_range_mismatch(),
},
Alias(symbol, args, _real_var, kind) => match symbol {
Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => {
merge_if!(range.contains_int_width(IntLitWidth::I8))
}
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => {
merge_if!(range.contains_int_width(IntLitWidth::U8))
}
Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => {
merge_if!(range.contains_int_width(IntLitWidth::I16))
}
Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => {
merge_if!(range.contains_int_width(IntLitWidth::U16))
}
Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => {
merge_if!(range.contains_int_width(IntLitWidth::I32))
}
Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => {
merge_if!(range.contains_int_width(IntLitWidth::U32))
}
Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => {
merge_if!(range.contains_int_width(IntLitWidth::I64))
}
Symbol::NUM_NAT | Symbol::NUM_NATURAL => {
merge_if!(range.contains_int_width(IntLitWidth::Nat))
}
Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => {
merge_if!(range.contains_int_width(IntLitWidth::U64))
}
Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => {
merge_if!(range.contains_int_width(IntLitWidth::I128))
}
Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => {
merge_if!(range.contains_int_width(IntLitWidth::U128))
}
Symbol::NUM_DEC | Symbol::NUM_DECIMAL => {
merge_if!(range.contains_float_width(FloatWidth::Dec))
}
Symbol::NUM_F32 | Symbol::NUM_BINARY32 => {
merge_if!(range.contains_float_width(FloatWidth::F32))
}
Symbol::NUM_F64 | Symbol::NUM_BINARY64 => {
merge_if!(range.contains_float_width(FloatWidth::F64))
}
Symbol::NUM_FRAC | Symbol::NUM_FLOATINGPOINT => match range {
NumericRange::IntAtLeastSigned(_) | NumericRange::IntAtLeastEitherSign(_) => {
mismatch!()
}
NumericRange::NumAtLeastSigned(_) | NumericRange::NumAtLeastEitherSign(_) => {
debug_assert_eq!(args.len(), 1);
let arg = subs.get_subs_slice(args.all_variables())[0];
let new_range_var = wrap_range_var(subs, symbol, range_var, kind);
unify_pool(subs, pool, new_range_var, arg, ctx.mode)
}
},
Symbol::NUM_NUM => {
debug_assert_eq!(args.len(), 1);
let arg = subs.get_subs_slice(args.all_variables())[0];
let new_range_var = wrap_range_var(subs, symbol, range_var, kind);
unify_pool(subs, pool, new_range_var, arg, ctx.mode)
}
Symbol::NUM_INT | Symbol::NUM_INTEGER => {
debug_assert_eq!(args.len(), 1);
let arg = subs.get_subs_slice(args.all_variables())[0];
let new_range_var = wrap_range_var(subs, symbol, range_var, kind);
unify_pool(subs, pool, new_range_var, arg, ctx.mode)
}
_ => mismatch!(),
},
_ => mismatch!(),
}
}
/// Push a number range var down into a number type, so as to preserve type hierarchy structure.
/// For example when we have Num (Int a) ~ Num (NumericRange <U128>), we want to produce
/// Num (Int (NumericRange <U128>))
/// on the right (which this function does) and then unify
/// Num (Int a) ~ Num (Int (NumericRange <U128>))
fn wrap_range_var(
subs: &mut Subs,
symbol: Symbol,
range_var: Variable,
alias_kind: AliasKind,
) -> Variable {
let range_desc = subs.get(range_var);
let new_range_var = subs.fresh(range_desc);
let var_slice = AliasVariables::insert_into_subs(subs, [new_range_var], []);
subs.set_content(
range_var,
Alias(symbol, var_slice, new_range_var, alias_kind),
);
new_range_var
} }
#[inline(always)] #[inline(always)]
@ -660,13 +746,8 @@ fn unify_alias<M: MetaCollector>(
} }
} }
Structure(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), Structure(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode),
RangedNumber(other_real_var, other_range_vars) => { RangedNumber(other_range_vars) => {
let outcome = unify_pool(subs, pool, real_var, *other_real_var, ctx.mode); check_and_merge_valid_range(subs, pool, ctx, ctx.second, *other_range_vars, ctx.first)
if outcome.mismatches.is_empty() {
check_valid_range(subs, real_var, *other_range_vars)
} else {
outcome
}
} }
LambdaSet(..) => mismatch!("cannot unify alias {:?} with lambda set {:?}: lambda sets should never be directly behind an alias!", ctx.first, other_content), LambdaSet(..) => mismatch!("cannot unify alias {:?} with lambda set {:?}: lambda sets should never be directly behind an alias!", ctx.first, other_content),
Error => merge(subs, ctx, Error), Error => merge(subs, ctx, Error),
@ -723,15 +804,11 @@ fn unify_opaque<M: MetaCollector>(
mismatch!("{:?}", symbol) mismatch!("{:?}", symbol)
} }
} }
RangedNumber(other_real_var, other_range_vars) => { RangedNumber(other_range_vars) => {
// This opaque might be a number, check if it unifies with the target ranged number var. // 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); check_and_merge_valid_range(subs, pool, ctx, ctx.second, *other_range_vars, ctx.first)
if outcome.mismatches.is_empty() {
check_valid_range(subs, ctx.first, *other_range_vars)
} else {
outcome
}
} }
Error => merge(subs, ctx, Error),
// _other has an underscore because it's unused in --release builds // _other has an underscore because it's unused in --release builds
_other => { _other => {
// The type on the left is an opaque, but the one on the right is not! // The type on the left is an opaque, but the one on the right is not!
@ -866,13 +943,8 @@ fn unify_structure<M: MetaCollector>(
// other // other
) )
} }
RangedNumber(other_real_var, other_range_vars) => { RangedNumber(other_range_vars) => {
let outcome = unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode); check_and_merge_valid_range(subs, pool, ctx, ctx.second, *other_range_vars, ctx.first)
if outcome.mismatches.is_empty() {
check_valid_range(subs, ctx.first, *other_range_vars)
} else {
outcome
}
} }
Error => merge(subs, ctx, Error), Error => merge(subs, ctx, Error),
} }
@ -2247,13 +2319,16 @@ fn unify_rigid<M: MetaCollector>(
"Rigid {:?} with FlexAble {:?}", ctx.first, other "Rigid {:?} with FlexAble {:?}", ctx.first, other
) )
} }
RangedNumber(..) => {
// Int a vs Int <range>, the rigid wins
merge(subs, ctx, RigidVar(*name))
}
RigidVar(_) RigidVar(_)
| RigidAbleVar(..) | RigidAbleVar(..)
| RecursionVar { .. } | RecursionVar { .. }
| Structure(_) | Structure(_)
| Alias(..) | Alias(..)
| RangedNumber(..)
| LambdaSet(..) => { | LambdaSet(..) => {
// Type mismatch! Rigid can only unify with flex, even if the // Type mismatch! Rigid can only unify with flex, even if the
// rigid names are the same. // rigid names are the same.

View file

@ -3168,7 +3168,7 @@ pub mod test_ed_update {
assert_type_tooltips_clean( assert_type_tooltips_clean(
ovec!["val = [ [ 0, 1, \"2\" ], [ 3, 4, 5 ┃] ]"], ovec!["val = [ [ 0, 1, \"2\" ], [ 3, 4, 5 ┃] ]"],
ovec!["List (Num *)", "List <type mismatch>"], ovec!["List (Num *)", "List (List <type mismatch>)"],
)?; )?;
Ok(()) Ok(())

View file

@ -301,7 +301,7 @@ fn nested_num_list() {
fn nested_int_list() { fn nested_int_list() {
expect_success( expect_success(
r#"[[[4, 3, 2], [1, 0x0]], [[]], []]"#, r#"[[[4, 3, 2], [1, 0x0]], [[]], []]"#,
r#"[[[4, 3, 2], [1, 0]], [[]], []] : List (List (List Int *))"#, r#"[[[4, 3, 2], [1, 0]], [[]], []] : List (List (List (Int *)))"#,
); );
} }

View file

@ -2347,13 +2347,12 @@ fn to_doc_help<'b>(
) )
} }
Range(typ, range_types) => { Range(range_types) => {
let typ = to_doc_help(ctx, alloc, parens, *typ);
let range_types = range_types let range_types = range_types
.into_iter() .into_iter()
.map(|arg| to_doc_help(ctx, alloc, Parens::Unnecessary, arg)) .map(|arg| to_doc_help(ctx, alloc, Parens::Unnecessary, arg))
.collect(); .collect();
report_text::range(alloc, typ, range_types) report_text::range(alloc, range_types)
} }
} }
} }
@ -2407,6 +2406,27 @@ fn error_type_to_doc<'b>(
type_with_able_vars(alloc, typ, able_vars) type_with_able_vars(alloc, typ, able_vars)
} }
fn compact_builtin_aliases(typ: ErrorType) -> ErrorType {
use ErrorType::*;
match typ {
Alias(Symbol::NUM_NUM, mut args, real, kind) => {
debug_assert!(args.len() == 1);
let type_arg = args.remove(0);
match type_arg {
Alias(Symbol::NUM_FLOATINGPOINT, inner_args, real, _) => {
Alias(Symbol::NUM_FRAC, inner_args, real, AliasKind::Structural)
}
Alias(Symbol::NUM_INTEGER, inner_args, real, _) => {
Alias(Symbol::NUM_INT, inner_args, real, AliasKind::Structural)
}
_ => Alias(Symbol::NUM_NUM, vec![type_arg], real, kind),
}
}
typ => typ,
}
}
fn to_diff<'b>( fn to_diff<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
parens: Parens, parens: Parens,
@ -2415,6 +2435,11 @@ fn to_diff<'b>(
) -> Diff<RocDocBuilder<'b>> { ) -> Diff<RocDocBuilder<'b>> {
use ErrorType::*; use ErrorType::*;
let (type1, type2) = (
compact_builtin_aliases(type1),
compact_builtin_aliases(type2),
);
// TODO remove clone // TODO remove clone
match (type1.clone(), type2.clone()) { match (type1.clone(), type2.clone()) {
(Error, Error) | (Infinite, Infinite) => same(alloc, parens, type1), (Error, Error) | (Infinite, Infinite) => same(alloc, parens, type1),
@ -3318,7 +3343,6 @@ mod report_text {
pub fn range<'b>( pub fn range<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
_encompassing_type: RocDocBuilder<'b>,
ranged_types: Vec<RocDocBuilder<'b>>, ranged_types: Vec<RocDocBuilder<'b>>,
) -> RocDocBuilder<'b> { ) -> RocDocBuilder<'b> {
let mut doc = Vec::with_capacity(ranged_types.len() * 2); let mut doc = Vec::with_capacity(ranged_types.len() * 2);

View file

@ -3375,6 +3375,21 @@ mod test_reporting {
-170_141_183_460_469_231_731_687_303_715_884_105_728. -170_141_183_460_469_231_731_687_303_715_884_105_728.
Tip: Learn more about number literals at TODO Tip: Learn more about number literals at TODO
TYPE MISMATCH /code/proj/Main.roc
The 2nd argument to `add` is not what I expect:
14 x + y + h + l + minlit + maxlit
^^^^^^
This `maxlit` value is a:
U128
But `add` needs the 2nd argument to be:
I128 or Dec
"### "###
); );
@ -7197,7 +7212,7 @@ All branches in an `if` must have the same type!
This argument is a number of type: This argument is a number of type:
I8, I16, I32, I64, I128, F32, F64, or Dec I8, I16, F32, I32, F64, I64, I128, or Dec
But `get` needs the 2nd argument to be: But `get` needs the 2nd argument to be:
@ -7223,7 +7238,7 @@ All branches in an `if` must have the same type!
This `a` value is a: This `a` value is a:
I64, I128, F32, F64, or Dec F64, I64, I128, or Dec
But `get` needs the 2nd argument to be: But `get` needs the 2nd argument to be:
@ -7250,7 +7265,7 @@ All branches in an `if` must have the same type!
This `b` value is a: This `b` value is a:
I64, I128, F32, F64, or Dec F64, I64, I128, or Dec
But `get` needs the 2nd argument to be: But `get` needs the 2nd argument to be:
@ -7278,7 +7293,7 @@ All branches in an `if` must have the same type!
The `when` condition is a number of type: The `when` condition is a number of type:
I8, I16, I32, I64, I128, F32, F64, or Dec I8, I16, F32, I32, F64, I64, I128, or Dec
But the branch patterns have type: But the branch patterns have type:
@ -9436,4 +9451,54 @@ All branches in an `if` must have the same type!
@r###" @r###"
"### "###
); );
test_report!(
int_literals_cannot_fit_in_same_type,
indoc!(
r#"
0x80000000000000000000000000000000 == -0x80000000000000000000000000000000
"#
),
@r###"
TYPE MISMATCH /code/proj/Main.roc
The 2nd argument to `isEq` is not what I expect:
4 0x80000000000000000000000000000000 == -0x80000000000000000000000000000000
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This argument is an integer of type:
I128
But `isEq` needs the 2nd argument to be:
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
"###
);
} }