diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index c56168c827..03b758320f 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -27,14 +27,6 @@ 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)] @@ -95,7 +87,6 @@ impl Constraint { } Constraint::And(cs) => cs.iter().any(|c| c.contains_save_the_environment()), Constraint::Present(_, _) => false, - Constraint::EqBoundedRange(_, _, _, _) => false, } } } @@ -180,11 +171,5 @@ 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/num.rs b/compiler/can/src/num.rs index e93ca3eedc..c1d8511eb4 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -261,7 +261,7 @@ fn from_str_radix(src: &str, radix: u32) -> Result<(IntValue, NumericBound), Int }; let (lower_bound, is_negative) = match result { - IntValue::I128(num) => (lower_bound_of_int(num), num <= 0), + IntValue::I128(num) => (lower_bound_of_int(num), num < 0), IntValue::U128(_) => (IntWidth::U128, false), }; diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index e72010899a..7867a06cd9 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -11,38 +11,31 @@ use roc_types::types::Category; use roc_types::types::Reason; use roc_types::types::Type::{self, *}; +#[must_use] pub fn add_numeric_bound_constr( constrs: &mut Vec, num_type: Type, bound: impl TypedNumericBound, region: Region, category: Category, -) { - if let Some(typ) = bound.concrete_num_type() { - constrs.push(Eq( - num_type, - Expected::ForReason(Reason::NumericLiteralSuffix, typ, region), - category, - region, - )); - } -} - -pub fn add_numeric_range_constr( - constrs: &mut Vec, - num_type: Type, - bound: impl TypedNumericBound, - region: Region, - category: Category, -) { +) -> Type { let range = bound.bounded_range(); - if !range.is_empty() { - constrs.push(EqBoundedRange( - num_type, - Expected::ForReason(Reason::NumericLiteralSuffix, range, region), - category, - region, - )); + + let total_num_type = num_type; + + match range.len() { + 0 => total_num_type, + 1 => { + let actual_type = Variable(range[0]); + constrs.push(Eq( + total_num_type.clone(), + Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region), + category, + region, + )); + total_num_type + } + _ => RangedNumber(Box::new(total_num_type.clone()), range), } } @@ -54,13 +47,18 @@ pub fn int_literal( region: Region, bound: IntBound, ) -> Constraint { - let num_type = Variable(num_var); let reason = Reason::IntLiteral; let mut constrs = Vec::with_capacity(3); // Always add the bound first; this improves the resolved type quality in case it's an alias // like "U8". - add_numeric_bound_constr(&mut constrs, num_type.clone(), bound, region, Category::Num); + let num_type = add_numeric_bound_constr( + &mut constrs, + Variable(num_var), + bound, + region, + Category::Num, + ); constrs.extend(vec![ Eq( num_type.clone(), @@ -70,13 +68,6 @@ pub fn int_literal( ), 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)) } @@ -89,13 +80,12 @@ pub fn float_literal( region: Region, bound: FloatBound, ) -> Constraint { - let num_type = Variable(num_var); let reason = Reason::FloatLiteral; let mut constrs = Vec::with_capacity(3); - add_numeric_bound_constr( + let num_type = add_numeric_bound_constr( &mut constrs, - num_type.clone(), + Variable(num_var), bound, region, Category::Float, @@ -120,10 +110,11 @@ pub fn num_literal( region: Region, bound: NumericBound, ) -> Constraint { - let num_type = crate::builtins::num_num(Type::Variable(num_var)); + let open_number_type = crate::builtins::num_num(Type::Variable(num_var)); let mut constrs = Vec::with_capacity(3); - add_numeric_bound_constr(&mut constrs, num_type.clone(), bound, region, Category::Num); + let num_type = + add_numeric_bound_constr(&mut constrs, open_number_type, bound, region, Category::Num); constrs.extend(vec![Eq(num_type, expected, Category::Num, region)]); exists(vec![num_var], And(constrs)) @@ -219,56 +210,56 @@ pub fn num_int(range: Type) -> Type { ) } -macro_rules! num_types { - // Represent - // num_u8 ~ U8 : Num Integer Unsigned8 = @Num (@Integer (@Unsigned8)) - // int_u8 ~ Integer Unsigned8 = @Integer (@Unsigned8) - // - // num_f32 ~ F32 : Num FloaingPoint Binary32 = @Num (@FloaingPoint (@Binary32)) - // float_f32 ~ FloatingPoint Binary32 = @FloatingPoint (@Binary32) - // and so on, for all numeric types. - ($($num_fn:ident, $sub_fn:ident, $num_type:ident, $alias:path, $inner_alias:path, $inner_private_tag:path)*) => { - $( - #[inline(always)] - fn $sub_fn() -> Type { - builtin_alias( - $inner_alias, - vec![], - Box::new(Type::TagUnion( - vec![(TagName::Private($inner_private_tag), vec![])], - Box::new(Type::EmptyTagUnion) - )), - ) - } - - #[inline(always)] - fn $num_fn() -> Type { - builtin_alias( - $alias, - vec![], - Box::new($num_type($sub_fn())) - ) - } - )* - } -} - -num_types! { - num_u8, int_u8, num_int, Symbol::NUM_U8, Symbol::NUM_UNSIGNED8, Symbol::NUM_AT_UNSIGNED8 - num_u16, int_u16, num_int, Symbol::NUM_U16, Symbol::NUM_UNSIGNED16, Symbol::NUM_AT_UNSIGNED16 - num_u32, int_u32, num_int, Symbol::NUM_U32, Symbol::NUM_UNSIGNED32, Symbol::NUM_AT_UNSIGNED32 - num_u64, int_u64, num_int, Symbol::NUM_U64, Symbol::NUM_UNSIGNED64, Symbol::NUM_AT_UNSIGNED64 - num_u128, int_u128, num_int, Symbol::NUM_U128, Symbol::NUM_UNSIGNED128, Symbol::NUM_AT_UNSIGNED128 - num_i8, int_i8, num_int, Symbol::NUM_I8, Symbol::NUM_SIGNED8, Symbol::NUM_AT_SIGNED8 - num_i16, int_i16, num_int, Symbol::NUM_I16, Symbol::NUM_SIGNED16, Symbol::NUM_AT_SIGNED16 - num_i32, int_i32, num_int, Symbol::NUM_I32, Symbol::NUM_SIGNED32, Symbol::NUM_AT_SIGNED32 - num_i64, int_i64, num_int, Symbol::NUM_I64, Symbol::NUM_SIGNED64, Symbol::NUM_AT_SIGNED64 - num_i128, int_i128, num_int, Symbol::NUM_I128, Symbol::NUM_SIGNED128, Symbol::NUM_AT_SIGNED128 - num_nat, int_nat, num_int, Symbol::NUM_NAT, Symbol::NUM_NATURAL, Symbol::NUM_AT_NATURAL - num_dec, float_dec, num_float, Symbol::NUM_DEC, Symbol::NUM_DECIMAL, Symbol::NUM_AT_DECIMAL - num_f32, float_f32, num_float, Symbol::NUM_F32, Symbol::NUM_BINARY32, Symbol::NUM_AT_BINARY32 - num_f64, float_f64, num_float, Symbol::NUM_F64, Symbol::NUM_BINARY64, Symbol::NUM_AT_BINARY64 -} +// macro_rules! num_types { +// // Represent +// // num_u8 ~ U8 : Num Integer Unsigned8 = @Num (@Integer (@Unsigned8)) +// // int_u8 ~ Integer Unsigned8 = @Integer (@Unsigned8) +// // +// // num_f32 ~ F32 : Num FloaingPoint Binary32 = @Num (@FloaingPoint (@Binary32)) +// // float_f32 ~ FloatingPoint Binary32 = @FloatingPoint (@Binary32) +// // and so on, for all numeric types. +// ($($num_fn:ident, $sub_fn:ident, $num_type:ident, $alias:path, $inner_alias:path, $inner_private_tag:path)*) => { +// $( +// #[inline(always)] +// fn $sub_fn() -> Type { +// builtin_alias( +// $inner_alias, +// vec![], +// Box::new(Type::TagUnion( +// vec![(TagName::Private($inner_private_tag), vec![])], +// Box::new(Type::EmptyTagUnion) +// )), +// ) +// } +// +// #[inline(always)] +// fn $num_fn() -> Type { +// builtin_alias( +// $alias, +// vec![], +// Box::new($num_type($sub_fn())) +// ) +// } +// )* +// } +// } +// +// num_types! { +// num_u8, int_u8, num_int, Symbol::NUM_U8, Symbol::NUM_UNSIGNED8, Symbol::NUM_AT_UNSIGNED8 +// num_u16, int_u16, num_int, Symbol::NUM_U16, Symbol::NUM_UNSIGNED16, Symbol::NUM_AT_UNSIGNED16 +// num_u32, int_u32, num_int, Symbol::NUM_U32, Symbol::NUM_UNSIGNED32, Symbol::NUM_AT_UNSIGNED32 +// num_u64, int_u64, num_int, Symbol::NUM_U64, Symbol::NUM_UNSIGNED64, Symbol::NUM_AT_UNSIGNED64 +// num_u128, int_u128, num_int, Symbol::NUM_U128, Symbol::NUM_UNSIGNED128, Symbol::NUM_AT_UNSIGNED128 +// num_i8, int_i8, num_int, Symbol::NUM_I8, Symbol::NUM_SIGNED8, Symbol::NUM_AT_SIGNED8 +// num_i16, int_i16, num_int, Symbol::NUM_I16, Symbol::NUM_SIGNED16, Symbol::NUM_AT_SIGNED16 +// num_i32, int_i32, num_int, Symbol::NUM_I32, Symbol::NUM_SIGNED32, Symbol::NUM_AT_SIGNED32 +// num_i64, int_i64, num_int, Symbol::NUM_I64, Symbol::NUM_SIGNED64, Symbol::NUM_AT_SIGNED64 +// num_i128, int_i128, num_int, Symbol::NUM_I128, Symbol::NUM_SIGNED128, Symbol::NUM_AT_SIGNED128 +// num_nat, int_nat, num_int, Symbol::NUM_NAT, Symbol::NUM_NATURAL, Symbol::NUM_AT_NATURAL +// num_dec, float_dec, num_float, Symbol::NUM_DEC, Symbol::NUM_DECIMAL, Symbol::NUM_AT_DECIMAL +// num_f32, float_f32, num_float, Symbol::NUM_F32, Symbol::NUM_BINARY32, Symbol::NUM_AT_BINARY32 +// num_f64, float_f64, num_float, Symbol::NUM_F64, Symbol::NUM_BINARY64, Symbol::NUM_AT_BINARY64 +// } #[inline(always)] pub fn num_signed64() -> Type { @@ -312,37 +303,26 @@ pub fn num_num(typ: Type) -> Type { } 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; + fn bounded_range(&self) -> Vec; } impl TypedNumericBound for IntBound { - fn concrete_num_type(&self) -> Option { - match self { - IntBound::None | IntBound::AtLeast { .. } => None, - IntBound::Exact(w) => Some(match w { - IntWidth::U8 => num_u8(), - IntWidth::U16 => num_u16(), - IntWidth::U32 => num_u32(), - IntWidth::U64 => num_u64(), - IntWidth::U128 => num_u128(), - IntWidth::I8 => num_i8(), - IntWidth::I16 => num_i16(), - IntWidth::I32 => num_i32(), - IntWidth::I64 => num_i64(), - IntWidth::I128 => num_i128(), - IntWidth::Nat => num_nat(), - }), - } - } - - fn bounded_range(&self) -> Vec { + fn bounded_range(&self) -> Vec { match self { IntBound::None => vec![], - IntBound::Exact(_) => vec![], + IntBound::Exact(w) => vec![match w { + IntWidth::U8 => Variable::U8, + IntWidth::U16 => Variable::U16, + IntWidth::U32 => Variable::U32, + IntWidth::U64 => Variable::U64, + IntWidth::U128 => Variable::U128, + IntWidth::I8 => Variable::I8, + IntWidth::I16 => Variable::I16, + IntWidth::I32 => Variable::I32, + IntWidth::I64 => Variable::I64, + IntWidth::I128 => Variable::I128, + IntWidth::Nat => Variable::NAT, + }], IntBound::AtLeast { sign, width } => { let whole_range: &[(IntWidth, Variable)] = match sign { SignDemand::NoDemand => { @@ -371,7 +351,7 @@ impl TypedNumericBound for IntBound { whole_range .iter() .skip_while(|(lower_bound, _)| *lower_bound != *width) - .map(|(_, var)| Type::Variable(*var)) + .map(|(_, var)| *var) .collect() } } @@ -379,35 +359,20 @@ impl TypedNumericBound for IntBound { } impl TypedNumericBound for FloatBound { - fn concrete_num_type(&self) -> Option { - match self { - FloatBound::None => None, - FloatBound::Exact(w) => Some(match w { - FloatWidth::Dec => num_dec(), - FloatWidth::F32 => num_f32(), - FloatWidth::F64 => num_f64(), - }), - } - } - - fn bounded_range(&self) -> Vec { + fn bounded_range(&self) -> Vec { match self { FloatBound::None => vec![], - FloatBound::Exact(_) => vec![], + FloatBound::Exact(w) => vec![match w { + FloatWidth::Dec => Variable::DEC, + FloatWidth::F32 => Variable::F32, + FloatWidth::F64 => Variable::F64, + }], } } } 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 { + fn bounded_range(&self) -> Vec { match self { NumericBound::None => vec![], NumericBound::Int(ib) => ib.bounded_range(), diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 242ff11335..bdd11dfe27 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -183,7 +183,7 @@ pub fn constrain_pattern( let num_type = builtins::num_num(Type::Variable(var)); - builtins::add_numeric_bound_constr( + let num_type = builtins::add_numeric_bound_constr( &mut state.constraints, num_type.clone(), bound, @@ -202,7 +202,7 @@ pub fn constrain_pattern( &IntLiteral(num_var, precision_var, _, _, bound) => { // First constraint on the free num var; this improves the resolved type quality in // case the bound is an alias. - builtins::add_numeric_bound_constr( + let num_type = builtins::add_numeric_bound_constr( &mut state.constraints, Type::Variable(num_var), bound, @@ -214,7 +214,7 @@ pub fn constrain_pattern( let int_type = builtins::num_int(Type::Variable(precision_var)); state.constraints.push(Constraint::Eq( - Type::Variable(num_var), + num_type, // TODO check me if something breaks! Expected::NoExpectation(int_type), Category::Int, region, @@ -232,7 +232,7 @@ pub fn constrain_pattern( &FloatLiteral(num_var, precision_var, _, _, bound) => { // First constraint on the free num var; this improves the resolved type quality in // case the bound is an alias. - builtins::add_numeric_bound_constr( + let num_type = builtins::add_numeric_bound_constr( &mut state.constraints, Type::Variable(num_var), bound, @@ -244,7 +244,7 @@ pub fn constrain_pattern( let float_type = builtins::num_float(Type::Variable(precision_var)); state.constraints.push(Constraint::Eq( - Type::Variable(num_var), + num_type.clone(), // TODO check me if something breaks! Expected::NoExpectation(float_type), Category::Float, region, @@ -254,7 +254,7 @@ pub fn constrain_pattern( state.constraints.push(Constraint::Pattern( region, PatternCategory::Float, - Type::Variable(num_var), + num_type, // TODO check me if something breaks! expected, )); } diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 265ac14d68..edcd676c00 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -66,6 +66,7 @@ impl<'a> RawFunctionLayout<'a> { Self::new_help(env, structure, structure_content.clone()) } Structure(flat_type) => Self::layout_from_flat_type(env, flat_type), + RangedNumber(typ, _) => Self::from_var(env, typ), // Ints Alias(Symbol::NUM_I128, args, _) => { @@ -902,6 +903,8 @@ impl<'a> Layout<'a> { } } + RangedNumber(typ, _) => Self::from_var(env, typ), + Error => Err(LayoutProblem::Erroneous), } } @@ -2562,7 +2565,7 @@ fn layout_from_num_content<'a>( Alias(_, _, _) => { todo!("TODO recursively resolve type aliases in num_from_content"); } - Structure(_) => { + Structure(_) | RangedNumber(..) => { panic!("Invalid Num.Num type application: {:?}", content); } Error => Err(LayoutProblem::Erroneous), diff --git a/compiler/mono/src/layout_soa.rs b/compiler/mono/src/layout_soa.rs index fe37cc385d..295ee07cf1 100644 --- a/compiler/mono/src/layout_soa.rs +++ b/compiler/mono/src/layout_soa.rs @@ -142,6 +142,7 @@ impl FunctionLayout { Content::RecursionVar { .. } => Err(TypeError(())), Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type), Content::Alias(_, _, actual) => Self::from_var_help(layouts, subs, *actual), + Content::RangedNumber(actual, _) => Self::from_var_help(layouts, subs, *actual), Content::Error => Err(TypeError(())), } } @@ -249,6 +250,7 @@ impl LambdaSet { } Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type), Content::Alias(_, _, actual) => Self::from_var_help(layouts, subs, *actual), + Content::RangedNumber(actual, _) => Self::from_var_help(layouts, subs, *actual), Content::Error => Err(TypeError(())), } } @@ -682,6 +684,7 @@ impl Layout { } } } + Content::RangedNumber(typ, _) => Self::from_var_help(layouts, subs, *typ), Content::Error => Err(TypeError(())), } } diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index c1e9bc7e19..c831b776c1 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -72,6 +72,7 @@ pub enum TypeError { CircularType(Region, Symbol, ErrorType), BadType(roc_types::types::Problem), UnexposedLookup(Symbol), + NotInRange(Region, ErrorType, Expected>), } #[derive(Clone, Debug, Default)] @@ -231,6 +232,17 @@ fn solve( problems.push(TypeError::BadType(problem)); + state + } + NotInRange(vars, typ, range) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::NotInRange( + *region, + typ, + expectation.clone().replace(range), + )); + state } } @@ -254,7 +266,7 @@ fn solve( state } - BadType(vars, _problem) => { + BadType(vars, _) | NotInRange(vars, _, _) => { introduce(subs, rank, pools, &vars); // ERROR NOT REPORTED @@ -321,6 +333,17 @@ fn solve( problems.push(TypeError::BadType(problem)); + state + } + NotInRange(vars, typ, range) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::NotInRange( + *region, + typ, + expectation.clone().replace(range), + )); + state } } @@ -391,6 +414,18 @@ fn solve( problems.push(TypeError::BadType(problem)); + state + } + NotInRange(vars, typ, range) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::NotInRange( + *region, + typ, + // TODO expectation.clone().replace(range), + Expected::NoExpectation(range), + )); + state } } @@ -687,52 +722,18 @@ fn solve( state } - } - } - EqBoundedRange(typ, expect_one_of, category, region) => { - let actual = type_to_var(subs, rank, pools, cached_aliases, typ); + NotInRange(vars, typ, range) => { + introduce(subs, rank, pools, &vars); - let mut it = expect_one_of.get_type_ref().iter().peekable(); + problems.push(TypeError::NotInRange( + Region::zero(), + typ, + Expected::NoExpectation(range), + )); - 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; - } + state } } - unreachable!() } } } @@ -816,6 +817,13 @@ fn type_to_variable<'a>( match typ { Variable(var) => *var, + RangedNumber(typ, vars) => { + let ty_var = type_to_variable(subs, rank, pools, arena, typ); + let vars = VariableSubsSlice::insert_into_subs(subs, vars.iter().copied()); + let content = Content::RangedNumber(ty_var, vars); + + register(subs, rank, pools, content) + } Apply(symbol, arguments, _) => { let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { @@ -1636,6 +1644,8 @@ fn adjust_rank_content( rank } + + RangedNumber(typ, _) => adjust_rank(subs, young_mark, visit_mark, group_rank, *typ), } } @@ -1771,6 +1781,11 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) { stack.push(var); } + &RangedNumber(typ, vars) => { + stack.push(typ); + + stack.extend(var_slice!(vars)); + } } } @@ -2027,6 +2042,23 @@ fn deep_copy_var_help( copy } + + RangedNumber(typ, range_vars) => { + let new_type_var = deep_copy_var_help(subs, max_rank, pools, visited, typ); + + let new_vars = SubsSlice::reserve_into_subs(subs, range_vars.len()); + for (target_index, var_index) in (new_vars.indices()).zip(range_vars) { + let var = subs[var_index]; + let copy_var = deep_copy_var_help(subs, max_rank, pools, visited, var); + subs.variables[target_index] = copy_var; + } + + let new_content = RangedNumber(new_type_var, new_vars); + + subs.set(copy, make_descriptor(new_content)); + + copy + } } } diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index d8c5a38d6e..fee93d0253 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -164,7 +164,7 @@ mod solve_expr { #[test] fn int_literal() { - infer_eq("5", "Num *"); + infer_eq("5", "Int *"); } #[test] @@ -334,7 +334,7 @@ mod solve_expr { [42] "# ), - "List (Num *)", + "List (Int *)", ); } @@ -346,7 +346,7 @@ mod solve_expr { [[[ 5 ]]] "# ), - "List (List (List (Num *)))", + "List (List (List (Int *)))", ); } @@ -358,7 +358,7 @@ mod solve_expr { [ 1, 2, 3 ] "# ), - "List (Num *)", + "List (Int *)", ); } @@ -370,7 +370,7 @@ mod solve_expr { [ [ 1 ], [ 2, 3 ] ] "# ), - "List (List (Num *))", + "List (List (Int *))", ); } @@ -518,7 +518,7 @@ mod solve_expr { \_, _ -> 42 "# ), - "*, * -> Num *", + "*, * -> Int *", ); } @@ -689,7 +689,7 @@ mod solve_expr { func "# ), - "*, * -> Num *", + "*, * -> Int *", ); } @@ -753,7 +753,7 @@ mod solve_expr { c "# ), - "Num *", + "Int *", ); } @@ -788,7 +788,7 @@ mod solve_expr { alwaysFive "stuff" "# ), - "Num *", + "Int *", ); } @@ -835,7 +835,7 @@ mod solve_expr { x "# ), - "Num *", + "Int *", ); } @@ -849,7 +849,7 @@ mod solve_expr { enlist 5 "# ), - "List (Num *)", + "List (Int *)", ); } @@ -876,7 +876,7 @@ mod solve_expr { 1 |> (\a -> a) "# ), - "Num *", + "Int *", ); } @@ -890,7 +890,7 @@ mod solve_expr { 1 |> always2 "foo" "# ), - "Num *", + "Int *", ); } @@ -955,7 +955,7 @@ mod solve_expr { apply identity 5 "# ), - "Num *", + "Int *", ); } @@ -984,7 +984,7 @@ mod solve_expr { // flip neverendingInt // "# // ), - // "(Num *, (a -> a)) -> Num *", + // "(Int *, (a -> a)) -> Int *", // ); // } @@ -1058,7 +1058,7 @@ mod solve_expr { // 1 // 2 // "# // ), - // "Num *", + // "Int *", // ); // } @@ -1070,7 +1070,7 @@ mod solve_expr { // 1 + 2 // "# // ), - // "Num *", + // "Int *", // ); // } @@ -1119,7 +1119,7 @@ mod solve_expr { [ alwaysFive "foo", alwaysFive [] ] "# ), - "List (Num *)", + "List (Int *)", ); } @@ -1134,7 +1134,7 @@ mod solve_expr { 24 "# ), - "Num *", + "Int *", ); } @@ -1148,7 +1148,7 @@ mod solve_expr { 3 -> 4 "# ), - "Num *", + "Int *", ); } @@ -1161,17 +1161,17 @@ mod solve_expr { #[test] fn one_field_record() { - infer_eq("{ x: 5 }", "{ x : Num * }"); + infer_eq("{ x: 5 }", "{ x : Int * }"); } #[test] fn two_field_record() { - infer_eq("{ x: 5, y : 3.14 }", "{ x : Num *, y : Float * }"); + infer_eq("{ x: 5, y : 3.14 }", "{ x : Int *, y : Float * }"); } #[test] fn record_literal_accessor() { - infer_eq("{ x: 5, y : 3.14 }.x", "Num *"); + infer_eq("{ x: 5, y : 3.14 }.x", "Int *"); } #[test] @@ -1230,7 +1230,7 @@ mod solve_expr { infer_eq( indoc!( r#" - foo : Num * -> custom + foo : Int * -> custom foo 2 "# @@ -1327,7 +1327,7 @@ mod solve_expr { \Foo -> 42 "# ), - "[ Foo ] -> Num *", + "[ Foo ] -> Int *", ); } @@ -1339,7 +1339,7 @@ mod solve_expr { \@Foo -> 42 "# ), - "[ @Foo ] -> Num *", + "[ @Foo ] -> Int *", ); } @@ -1354,7 +1354,7 @@ mod solve_expr { False -> 0 "# ), - "[ False, True ] -> Num *", + "[ False, True ] -> Int *", ); } @@ -1366,7 +1366,7 @@ mod solve_expr { Foo "happy" 2020 "# ), - "[ Foo Str (Num *) ]*", + "[ Foo Str (Int *) ]*", ); } @@ -1378,7 +1378,7 @@ mod solve_expr { @Foo "happy" 2020 "# ), - "[ @Foo Str (Num *) ]*", + "[ @Foo Str (Int *) ]*", ); } @@ -1407,7 +1407,7 @@ mod solve_expr { { x: 4 } -> 4 "# ), - "Num *", + "Int *", ); } @@ -2347,7 +2347,7 @@ mod solve_expr { { numIdentity, x : numIdentity 42, y } "# ), - "{ numIdentity : Num a -> Num a, x : Num a, y : F64 }", + "{ numIdentity : Num a -> Num a, x : Int *, y : F64 }", ); } @@ -2383,7 +2383,7 @@ mod solve_expr { f "# ), - "Num * -> Num *", + "Int * -> Int *", ); } @@ -2416,7 +2416,7 @@ mod solve_expr { toBit "# ), - "[ False, True ] -> Num *", + "[ False, True ] -> Int *", ); } @@ -2453,7 +2453,7 @@ mod solve_expr { fromBit "# ), - "Num * -> [ False, True ]*", + "Int * -> [ False, True ]*", ); } @@ -2505,7 +2505,7 @@ mod solve_expr { foo { x: 5 } "# ), - "Num *", + "Int *", ); } @@ -2774,7 +2774,7 @@ mod solve_expr { // infer_eq_without_problem( // indoc!( // r#" - // s : Num * + // s : Int * // s = 3.1 // s @@ -3214,7 +3214,7 @@ mod solve_expr { List.get [ 10, 9, 8, 7 ] 1 "# ), - "Result (Num *) [ OutOfBounds ]*", + "Result (Int *) [ OutOfBounds ]*", ); infer_eq_without_problem( @@ -3497,7 +3497,7 @@ mod solve_expr { f "# ), - "{ p : *, q : * }* -> Num *", + "{ p : *, q : * }* -> Int *", ); } @@ -3552,7 +3552,7 @@ mod solve_expr { _ -> 3 "# ), - "Num * -> Num *", + "Int * -> Int *", ); } @@ -3724,7 +3724,8 @@ mod solve_expr { negatePoint { x: 1, y: 2.1, z: 0x3 } "# ), - "{ x : Num a, y : F64, z : Int * }", + // TODO this should be "Int a", FIXME + "{ x : Int *, y : F64, z : Int * }", ); } @@ -3741,7 +3742,8 @@ mod solve_expr { { a, b } "# ), - "{ a : { x : Num a, y : F64, z : c }, b : { blah : Str, x : Num a, y : F64, z : c } }", + // TODO this should be "Int a", FIXME + "{ a : { x : Int *, y : F64, z : c }, b : { blah : Str, x : Int *, y : F64, z : c } }", ); } @@ -3753,7 +3755,7 @@ mod solve_expr { \{ x, y ? 0 } -> x + y "# ), - "{ x : Num a, y ? Num a }* -> Num a", + "{ x : Int a, y ? Int a }* -> Int a", ); } @@ -3767,7 +3769,7 @@ mod solve_expr { x + y "# ), - "Num *", + "Int *", ); } @@ -3781,7 +3783,7 @@ mod solve_expr { { x, y ? 0 } -> x + y "# ), - "{ x : Num a, y ? Num a }* -> Num a", + "{ x : Int a, y ? Int a }* -> Int a", ); } @@ -3946,7 +3948,7 @@ mod solve_expr { g "# ), - "Num a -> Num a", + "Int a -> Int a", ); } @@ -3985,10 +3987,10 @@ mod solve_expr { Foo Bar 1 "# ), - "[ Foo [ Bar ]* (Num *) ]*", + "[ Foo [ Bar ]* (Int *) ]*", ); - infer_eq_without_problem("Foo Bar 1", "[ Foo [ Bar ]* (Num *) ]*"); + infer_eq_without_problem("Foo Bar 1", "[ Foo [ Bar ]* (Int *) ]*"); } #[test] @@ -4680,7 +4682,7 @@ mod solve_expr { x "# ), - "Num *", + "Int *", ); } @@ -4981,7 +4983,7 @@ mod solve_expr { None -> 0 "# ), - "[ None, Some { tag : [ A, B ] }* ] -> Num *", + "[ None, Some { tag : [ A, B ] }* ] -> Int *", ) } @@ -5014,7 +5016,7 @@ mod solve_expr { { x: Red, y ? 5 } -> y "# ), - "{ x : [ Blue, Red ], y ? Num a }* -> Num a", + "{ x : [ Blue, Red ], y ? Int a }* -> Int a", ) } @@ -5027,7 +5029,8 @@ mod solve_expr { \UserId id -> id + 1 "# ), - "[ UserId (Num a) ] -> Num a", + // TODO needs parantheses + "[ UserId Int a ] -> Int a", ) } diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index ead9c32afd..a84eb8b43b 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -206,6 +206,13 @@ fn find_names_needed( // TODO should we also look in the actual variable? // find_names_needed(_actual, subs, roots, root_appearances, names_taken); } + &RangedNumber(typ, vars) => { + find_names_needed(typ, subs, roots, root_appearances, names_taken); + for var_index in vars { + let var = subs[var_index]; + find_names_needed(var, subs, roots, root_appearances, names_taken); + } + } Error | Structure(Erroneous(_)) | Structure(EmptyRecord) | Structure(EmptyTagUnion) => { // Errors and empty records don't need names. } @@ -397,6 +404,13 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa }), } } + RangedNumber(typ, _range_vars) => write_content( + env, + subs.get_content_without_compacting(*typ), + subs, + buf, + parens, + ), Error => buf.push_str(""), } } diff --git a/compiler/types/src/solved_types.rs b/compiler/types/src/solved_types.rs index 2174ace2f0..453e5207c1 100644 --- a/compiler/types/src/solved_types.rs +++ b/compiler/types/src/solved_types.rs @@ -231,6 +231,7 @@ impl SolvedType { } } Variable(var) => Self::from_var(solved_subs.inner(), *var), + RangedNumber(typ, _) => Self::from_type(solved_subs, &typ), } } @@ -284,6 +285,7 @@ impl SolvedType { SolvedType::Alias(*symbol, new_args, solved_lambda_sets, Box::new(aliased_to)) } + RangedNumber(typ, _range_vars) => Self::from_var_help(subs, recursion_vars, *typ), Error => SolvedType::Error, } } diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 26e84320cd..e24503e899 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -371,6 +371,10 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt: write!(f, "Alias({:?}, {:?}, {:?})", name, slice, actual) } + Content::RangedNumber(typ, range) => { + let slice = subs.get_subs_slice(*range); + write!(f, "RangedNumber({:?}, {:?})", typ, slice) + } Content::Error => write!(f, "Error"), } } @@ -588,11 +592,6 @@ define_const_var! { AT_NATURAL, - AT_BINARY32, - AT_BINARY64, - - AT_DECIMAL, - // Signed8 : [ @Signed8 ] :pub SIGNED8, :pub SIGNED16, @@ -608,11 +607,6 @@ define_const_var! { :pub NATURAL, - :pub BINARY32, - :pub BINARY64, - - :pub DECIMAL, - // [ @Integer Signed8 ] AT_INTEGER_SIGNED8, AT_INTEGER_SIGNED16, @@ -688,6 +682,36 @@ define_const_var! { :pub NAT, + // [ @Binary32 ] + AT_BINARY32, + AT_BINARY64, + AT_DECIMAL, + + // Binary32 : [ @Binary32 ] + BINARY32, + BINARY64, + DECIMAL, + + // [ @Float Binary32 ] + AT_FLOAT_BINARY32, + AT_FLOAT_BINARY64, + AT_FLOAT_DECIMAL, + + // Float Binary32 : [ @Float Binary32 ] + FLOAT_BINARY32, + FLOAT_BINARY64, + FLOAT_DECIMAL, + + // [ @Num (Float Binary32) ] + AT_NUM_FLOAT_BINARY32, + AT_NUM_FLOAT_BINARY64, + AT_NUM_FLOAT_DECIMAL, + + // Num (Float Binary32) + NUM_FLOAT_BINARY32, + NUM_FLOAT_BINARY64, + NUM_FLOAT_DECIMAL, + :pub F32, :pub F64, @@ -1034,6 +1058,118 @@ fn define_integer_types(subs: &mut Subs) { ); } +fn float_type( + subs: &mut Subs, + + num_at_binary64: Symbol, + num_binary64: Symbol, + num_f64: Symbol, + + at_binary64: Variable, + binary64: Variable, + + at_float_binary64: Variable, + float_binary64: Variable, + + at_num_float_binary64: Variable, + num_float_binary64: Variable, + + var_f64: Variable, +) { + // define the type Binary64 (which is an alias for [ @Binary64 ]) + { + let tags = UnionTags::insert_into_subs(subs, [(TagName::Private(num_at_binary64), [])]); + + subs.set_content(at_binary64, { + Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) + }); + + subs.set_content(binary64, { + Content::Alias(num_binary64, AliasVariables::default(), at_binary64) + }); + } + + // define the type `Num.Float Num.Binary64` + { + let tags = UnionTags::insert_into_subs( + subs, + [(TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), [binary64])], + ); + subs.set_content(at_float_binary64, { + Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) + }); + + let vars = AliasVariables::insert_into_subs(subs, [binary64], []); + subs.set_content(float_binary64, { + Content::Alias(Symbol::NUM_FLOATINGPOINT, vars, at_binary64) + }); + } + + // define the type `F64: Num.Num (Num.Float Num.Binary64)` + { + let tags = UnionTags::insert_into_subs( + subs, + [(TagName::Private(Symbol::NUM_AT_NUM), [float_binary64])], + ); + subs.set_content(at_num_float_binary64, { + Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) + }); + + let vars = AliasVariables::insert_into_subs(subs, [float_binary64], []); + subs.set_content(num_float_binary64, { + Content::Alias(Symbol::NUM_NUM, vars, at_num_float_binary64) + }); + + subs.set_content(var_f64, { + Content::Alias(num_f64, AliasVariables::default(), num_float_binary64) + }); + } +} + +fn define_float_types(subs: &mut Subs) { + float_type( + subs, + Symbol::NUM_AT_BINARY32, + Symbol::NUM_BINARY32, + Symbol::NUM_F32, + Variable::AT_BINARY32, + Variable::BINARY32, + Variable::AT_FLOAT_BINARY32, + Variable::FLOAT_BINARY32, + Variable::AT_NUM_FLOAT_BINARY32, + Variable::NUM_FLOAT_BINARY32, + Variable::F32, + ); + + float_type( + subs, + Symbol::NUM_AT_BINARY64, + Symbol::NUM_BINARY64, + Symbol::NUM_F64, + Variable::AT_BINARY64, + Variable::BINARY64, + Variable::AT_FLOAT_BINARY64, + Variable::FLOAT_BINARY64, + Variable::AT_NUM_FLOAT_BINARY64, + Variable::NUM_FLOAT_BINARY64, + Variable::F64, + ); + + float_type( + subs, + Symbol::NUM_AT_DECIMAL, + Symbol::NUM_DECIMAL, + Symbol::NUM_DEC, + Variable::AT_DECIMAL, + Variable::DECIMAL, + Variable::AT_FLOAT_DECIMAL, + Variable::FLOAT_DECIMAL, + Variable::AT_NUM_FLOAT_DECIMAL, + Variable::NUM_FLOAT_DECIMAL, + Variable::DEC, + ); +} + impl Subs { pub const RESULT_TAG_NAMES: SubsSlice = SubsSlice::new(0, 2); @@ -1072,6 +1208,7 @@ impl Subs { } define_integer_types(&mut subs); + define_float_types(&mut subs); subs.set_content( Variable::EMPTY_RECORD, @@ -1492,6 +1629,7 @@ pub enum Content { }, Structure(FlatType), Alias(Symbol, AliasVariables, Variable), + RangedNumber(Variable, VariableSubsSlice), Error, } @@ -2244,6 +2382,15 @@ fn occurs( short_circuit_help(subs, root_var, &new_seen, var)?; } + Ok(()) + } + RangedNumber(typ, _range_vars) => { + let mut new_seen = seen.clone(); + new_seen.insert(root_var); + + short_circuit_help(subs, root_var, &new_seen, *typ)?; + // _range_vars excluded because they are not explicitly part of the type. + Ok(()) } } @@ -2433,6 +2580,19 @@ fn explicit_substitute( subs.set_content(in_var, Alias(symbol, args, new_actual)); + in_var + } + RangedNumber(typ, vars) => { + for index in vars.into_iter() { + let var = subs[index]; + let new_var = explicit_substitute(subs, from, to, var, seen); + subs[index] = new_var; + } + + let new_typ = explicit_substitute(subs, from, to, typ, seen); + + subs.set_content(in_var, RangedNumber(new_typ, vars)); + in_var } } @@ -2484,6 +2644,13 @@ fn get_var_names( get_var_names(subs, subs[arg_var], answer) }), + RangedNumber(typ, vars) => { + let taken_names = get_var_names(subs, typ, taken_names); + vars.into_iter().fold(taken_names, |answer, var| { + get_var_names(subs, subs[var], answer) + }) + } + Structure(flat_type) => match flat_type { FlatType::Apply(_, args) => { args.into_iter().fold(taken_names, |answer, arg_var| { @@ -2696,6 +2863,8 @@ fn content_to_err_type( ErrorType::Alias(symbol, err_args, Box::new(err_type)) } + RangedNumber(typ, _) => var_to_err_type(subs, state, typ), + Error => ErrorType::Error, } } @@ -2974,6 +3143,11 @@ fn restore_help(subs: &mut Subs, initial: Variable) { stack.push(*var); } + + RangedNumber(typ, vars) => { + stack.push(*typ); + stack.extend(var_slice(*vars)); + } } } } @@ -3133,6 +3307,10 @@ impl StorageSubs { Self::offset_alias_variables(offsets, *alias_variables), Self::offset_variable(offsets, *actual), ), + RangedNumber(typ, vars) => RangedNumber( + Self::offset_variable(offsets, *typ), + Self::offset_variable_slice(offsets, *vars), + ), Error => Content::Error, } } @@ -3525,6 +3703,23 @@ fn deep_copy_var_to_help<'a>( copy } + + RangedNumber(typ, vars) => { + let new_typ = deep_copy_var_to_help(arena, visited, source, target, max_rank, typ); + + let new_vars = SubsSlice::reserve_into_subs(target, vars.len()); + + for (target_index, var_index) in (new_vars.indices()).zip(vars) { + let var = source[var_index]; + let copy_var = deep_copy_var_to_help(arena, visited, source, target, max_rank, var); + target.variables[target_index] = copy_var; + } + + let new_content = RangedNumber(new_typ, new_vars); + + target.set(copy, make_descriptor(new_content)); + copy + } } } @@ -3598,6 +3793,10 @@ where push_var_slice!(arguments.variables()); stack.push(*real_type_var); } + RangedNumber(typ, vars) => { + stack.push(*typ); + push_var_slice!(*vars); + } Error => {} } } diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index f8e1296bdf..fdf00f17ab 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -196,6 +196,7 @@ pub enum Type { /// Applying a type to some arguments (e.g. Dict.Dict String Int) Apply(Symbol, Vec, Region), Variable(Variable), + RangedNumber(Box, Vec), /// A type error, which will code gen to a runtime error Erroneous(Problem), } @@ -439,6 +440,9 @@ impl fmt::Debug for Type { write!(f, " as <{:?}>", rec) } + Type::RangedNumber(typ, range_vars) => { + write!(f, "Ranged({:?}, {:?})", typ, range_vars) + } } } } @@ -549,6 +553,9 @@ impl Type { arg.substitute(substitutions); } } + RangedNumber(typ, _) => { + typ.substitute(substitutions); + } EmptyRec | EmptyTagUnion | Erroneous(_) => {} } @@ -616,6 +623,7 @@ impl Type { } Ok(()) } + RangedNumber(typ, _) => typ.substitute_alias(rep_symbol, rep_args, actual), EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => Ok(()), } } @@ -653,6 +661,7 @@ impl Type { } Apply(symbol, _, _) if *symbol == rep_symbol => true, Apply(_, args, _) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)), + RangedNumber(typ, _) => typ.contains_symbol(rep_symbol), EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => false, } } @@ -689,6 +698,9 @@ impl Type { } => actual_type.contains_variable(rep_variable), HostExposedAlias { actual, .. } => actual.contains_variable(rep_variable), Apply(_, args, _) => args.iter().any(|arg| arg.contains_variable(rep_variable)), + RangedNumber(typ, vars) => { + typ.contains_variable(rep_variable) || vars.iter().any(|&v| v == rep_variable) + } EmptyRec | EmptyTagUnion | Erroneous(_) => false, } } @@ -845,6 +857,9 @@ impl Type { } } } + RangedNumber(typ, _) => { + typ.instantiate_aliases(region, aliases, var_store, introduced); + } EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {} } } @@ -901,6 +916,9 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet) { Erroneous(Problem::CyclicAlias(alias, _, _)) => { accum.insert(*alias); } + RangedNumber(typ, _) => { + symbols_help(typ, accum); + } EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {} } } @@ -979,6 +997,10 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { } variables_help(actual, accum); } + RangedNumber(typ, vars) => { + variables_help(typ, accum); + accum.extend(vars.iter().copied()); + } Apply(_, args, _) => { for x in args { variables_help(x, accum); @@ -1083,6 +1105,10 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { } variables_help_detailed(actual, accum); } + RangedNumber(typ, vars) => { + variables_help_detailed(typ, accum); + accum.type_variables.extend(vars); + } Apply(_, args, _) => { for x in args { variables_help_detailed(x, accum); @@ -1288,6 +1314,7 @@ pub enum Mismatch { InconsistentIfElse, InconsistentWhenBranches, CanonicalizationProblem, + TypeNotInRange(Variable, Vec), } #[derive(PartialEq, Eq, Clone, Hash)] diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 34693d704d..fc6c313336 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -83,6 +83,7 @@ pub enum Unified { Success(Pool), Failure(Pool, ErrorType, ErrorType), BadType(Pool, roc_types::types::Problem), + NotInRange(Pool, ErrorType, Vec), } type Outcome = Vec; @@ -94,6 +95,13 @@ pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable, mode: Mode) -> Uni if mismatches.is_empty() { Unified::Success(vars) + } else if let Some((typ, range)) = mismatches.iter().find_map(|mis| match mis { + Mismatch::TypeNotInRange(typ, range) => Some((typ, range)), + _ => None, + }) { + let (target_type, _) = subs.var_to_error_type(*typ); + let range_types = range.iter().map(|&v| subs.var_to_error_type(v).0).collect(); + Unified::NotInRange(vars, target_type, range_types) } else { let (type1, mut problems) = subs.var_to_error_type(var1); let (type2, problems2) = subs.var_to_error_type(var2); @@ -175,6 +183,7 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { unify_structure(subs, pool, &ctx, flat_type, &ctx.second_desc.content) } Alias(symbol, args, real_var) => unify_alias(subs, pool, &ctx, *symbol, *args, *real_var), + &RangedNumber(typ, range_vars) => unify_ranged_number(subs, pool, &ctx, typ, range_vars), Error => { // Error propagates. Whatever we're comparing it to doesn't matter! merge(subs, &ctx, Error) @@ -182,6 +191,73 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { } } +#[inline(always)] +fn unify_ranged_number( + subs: &mut Subs, + pool: &mut Pool, + ctx: &Context, + real_var: Variable, + range_vars: VariableSubsSlice, +) -> Outcome { + let other_content = &ctx.second_desc.content; + + let outcome = match other_content { + FlexVar(_) => { + // Ranged number wins + merge(subs, ctx, RangedNumber(real_var, range_vars)) + } + RecursionVar { .. } | RigidVar(..) | Alias(..) | Structure(..) => { + unify_pool(subs, pool, real_var, ctx.second, ctx.mode) + } + &RangedNumber(other_real_var, _other_range_vars) => { + unify_pool(subs, pool, real_var, other_real_var, ctx.mode) + // TODO: check and intersect "other_range_vars" + } + Error => merge(subs, ctx, Error), + }; + + if !outcome.is_empty() { + return outcome; + } + + check_valid_range(subs, pool, ctx.second, range_vars, ctx.mode) +} + +fn check_valid_range( + subs: &mut Subs, + pool: &mut Pool, + var: Variable, + range: VariableSubsSlice, + mode: Mode, +) -> Outcome { + let slice = subs + .get_subs_slice(range) + .iter() + .copied() + .collect::>(); + + let mut it = slice.iter().peekable(); + while let Some(&possible_var) = it.next() { + let snapshot = subs.snapshot(); + let old_pool = pool.clone(); + let outcome = unify_pool(subs, pool, var, possible_var, mode); + if outcome.is_empty() { + // Okay, we matched some type in the range. + subs.rollback_to(snapshot); + *pool = old_pool; + return vec![]; + } else if it.peek().is_some() { + // We failed to match something in the range, but there are still things we can try. + subs.rollback_to(snapshot); + *pool = old_pool; + } else { + subs.commit_snapshot(snapshot); + } + } + + return vec![Mismatch::TypeNotInRange(var, slice)]; +} + #[inline(always)] fn unify_alias( subs: &mut Subs, @@ -231,6 +307,9 @@ fn unify_alias( } } Structure(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), + RangedNumber(other_real_var, _) => { + unify_pool(subs, pool, real_var, *other_real_var, ctx.mode) + } Error => merge(subs, ctx, Error), } } @@ -302,6 +381,7 @@ fn unify_structure( // can't quite figure out why, but it doesn't seem to impact other types. unify_pool(subs, pool, ctx.first, *real_var, Mode::Eq) } + RangedNumber(real_var, _) => unify_pool(subs, pool, ctx.first, *real_var, ctx.mode), Error => merge(subs, ctx, Error), } } @@ -829,6 +909,7 @@ fn unify_tag_union_new( } } +#[derive(Debug)] enum OtherTags2 { Empty, Union( @@ -1222,7 +1303,7 @@ fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content // If the other is flex, rigid wins! merge(subs, ctx, RigidVar(name.clone())) } - RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _) => { + RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _) | RangedNumber(..) => { // Type mismatch! Rigid can only unify with flex, even if the // rigid names are the same. mismatch!("Rigid {:?} with {:?}", ctx.first, &other) @@ -1247,7 +1328,12 @@ fn unify_flex( merge(subs, ctx, FlexVar(opt_name.clone())) } - FlexVar(Some(_)) | RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _) => { + FlexVar(Some(_)) + | RigidVar(_) + | RecursionVar { .. } + | Structure(_) + | Alias(_, _, _) + | RangedNumber(..) => { // TODO special-case boolean here // In all other cases, if left is flex, defer to right. // (This includes using right's name if both are flex and named.) @@ -1306,6 +1392,12 @@ fn unify_recursion( unify_pool(subs, pool, ctx.first, *actual, ctx.mode) } + RangedNumber(..) => mismatch!( + "RecursionVar {:?} with ranged number {:?}", + ctx.first, + &other + ), + Error => merge(subs, ctx, Error), } } diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index 1b147ebb84..68dfa687e0 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -14,6 +14,7 @@ use ven_pretty::DocAllocator; const DUPLICATE_NAME: &str = "DUPLICATE NAME"; const ADD_ANNOTATIONS: &str = r#"Can more type annotations be added? Type annotations always help me give more specific messages, and I think they could help a lot in this case"#; +const TYPE_NOT_IN_RANGE: &str = r#"TYPE NOT IN RANGE"#; pub fn type_problem<'b>( alloc: &'b RocDocAllocator<'b>, @@ -59,6 +60,31 @@ pub fn type_problem<'b>( report(title, doc, filename) } + NotInRange(region, found, expected_range) => { + let mut range_choices = vec![alloc.reflow("It can only be used as a ")]; + let range = expected_range.get_type(); + let last = range.len() - 1; + for (i, choice) in range.into_iter().enumerate() { + if i == last && i == 1 { + range_choices.push(alloc.text(" or ")); + } else if i == last && i > 1 { + range_choices.push(alloc.text(", or ")); + } else if i > 1 { + range_choices.push(alloc.text(", ")); + } + + range_choices.push(to_doc(alloc, Parens::Unnecessary, choice)); + } + let doc = alloc.stack(vec![ + alloc.reflow("This expression is used in an unexpected way:"), + alloc.region(lines.convert_region(region)), + alloc.concat(range_choices), + alloc.text("But it is being used as:"), + to_doc(alloc, Parens::Unnecessary, found), + ]); + + report(TYPE_NOT_IN_RANGE.into(), doc, filename) + } BadType(type_problem) => { use roc_types::types::Problem::*; match type_problem { diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 3a7ad4dd87..95f31c5cad 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -1221,6 +1221,7 @@ mod test_reporting { x "# ), + // TODO FIXME the second error message is incomplete, should be removed indoc!( r#" ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── @@ -1241,6 +1242,20 @@ mod test_reporting { Tip: You can convert between Int and Float using functions like `Num.toFloat` and `Num.round`. + + ── TYPE NOT IN RANGE ─────────────────────────────────────────────────────────── + + This expression is used in an unexpected way: + + 2│ x = if True then 3.14 else 4 + ^ + + It can only be used as a + `I8``U8`, `I16`, `U16`, `I32`, `U32`, `I64`, `Nat`, `U64`, `I128`, or `U128` + + But it is being used as: + + `Int` `*` "# ), ) @@ -3386,7 +3401,9 @@ mod test_reporting { minlit = -170_141_183_460_469_231_731_687_303_715_884_105_728 maxlit = 340_282_366_920_938_463_463_374_607_431_768_211_455 - x + y + h + l + minlit + maxlit + getI128 = \_ -> 1i128 + + x + y + h + l + minlit + (getI128 maxlit) "# ), indoc!( @@ -7882,18 +7899,26 @@ 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 ───────────────────────────────────────────────────── + a = -9_223_372_036_854 + List.get [1,2,3] a + "# + ), + indoc!( + r#" + ── TYPE NOT IN RANGE ─────────────────────────────────────────────────────────── - This integer literal overflows the type indicated by its suffix: + This expression is used in an unexpected way: - 1│ 170_141_183_460_469_231_731_687_303_715_884_105_728i128 - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 2│ List.get [1,2,3] a + ^ - 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. + It can only be used as a `I64` or `I128` + + But it is being used as: + + `Nat` "# ), )