diff --git a/.cargo/config.toml b/.cargo/config.toml index f57d96eaf7d..08447639700 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -39,7 +39,6 @@ ROC_TRACE_COMPACTION = "0" ROC_PRINT_UNIFICATIONS_DERIVED = "0" ROC_PRINT_MISMATCHES = "0" ROC_PRINT_FIXPOINT_FIXING = "0" -ROC_VERIFY_RIGID_LET_GENERALIZED = "0" ROC_VERIFY_OCCURS_ONE_RECURSION = "0" ROC_CHECK_MONO_IR = "0" ROC_PRINT_IR_AFTER_SPECIALIZATION = "0" diff --git a/Cargo.lock b/Cargo.lock index e0a118699a0..aaffda7e4f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3281,6 +3281,7 @@ dependencies = [ name = "roc_types" version = "0.0.1" dependencies = [ + "bitflags 1.3.2", "bumpalo", "roc_collections", "roc_debug_flags", diff --git a/crates/cli/tests/benchmarks/AStar.roc b/crates/cli/tests/benchmarks/AStar.roc index 63437b57d15..be0ead6ced2 100644 --- a/crates/cli/tests/benchmarks/AStar.roc +++ b/crates/cli/tests/benchmarks/AStar.roc @@ -90,12 +90,12 @@ astar = \cost_fn, move_fn, goal, model -> new_neighbors = Set.difference(neighbors, model_popped.evaluated) - model_with_neighbors : Model position + model_with_neighbors : Model _ model_with_neighbors = model_popped |> &open_set(Set.union(model_popped.open_set, new_neighbors)) - walker : Model position, position -> Model position + walker : Model _, _ -> Model _ walker = \amodel, n -> update_cost(current, n, amodel) model_with_costs = diff --git a/crates/compiler/builtins/roc/Num.roc b/crates/compiler/builtins/roc/Num.roc index 2215f254cf8..9c08ff8b894 100644 --- a/crates/compiler/builtins/roc/Num.roc +++ b/crates/compiler/builtins/roc/Num.roc @@ -532,7 +532,7 @@ pi = 3.14159265358979323846264338327950288419716939937510 ## Circle constant (τ) tau : Frac * -tau = 2 * pi +tau = 6.2831853071795864769252867665590057683943387987502 # ------- Functions ## Convert a number to a [Str]. diff --git a/crates/compiler/can/src/constraint.rs b/crates/compiler/can/src/constraint.rs index 569ea9a07bf..51127f7ee73 100644 --- a/crates/compiler/can/src/constraint.rs +++ b/crates/compiler/can/src/constraint.rs @@ -915,7 +915,7 @@ pub struct DefTypes { pub loc_symbols: Slice<(Symbol, Region)>, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Generalizable(pub bool); #[derive(Debug, Clone)] diff --git a/crates/compiler/debug_flags/src/lib.rs b/crates/compiler/debug_flags/src/lib.rs index 873432d069f..eca3d0325bd 100644 --- a/crates/compiler/debug_flags/src/lib.rs +++ b/crates/compiler/debug_flags/src/lib.rs @@ -95,22 +95,6 @@ flags! { /// Prints all type variables entered for fixpoint-fixing. ROC_PRINT_FIXPOINT_FIXING - /// Verifies that after let-generalization of a def, any rigid variables in the type annotation - /// of the def are indeed generalized. - /// - /// Note that rigids need not always be generalized in a def. For example, they may be - /// constrained by a type from a lower rank, as `b` is in the following def: - /// - /// F a : { foo : a } - /// foo = \arg -> - /// x : F b - /// x = arg - /// x.foo - /// - /// Instead, this flag is useful for checking that in general, introduction is correct, when - /// chainging how defs are constrained. - ROC_VERIFY_RIGID_LET_GENERALIZED - /// Verifies that an `occurs` check indeed only contains non-recursive types that need to be /// fixed-up with one new recursion variable. /// diff --git a/crates/compiler/load/tests/test_reporting.rs b/crates/compiler/load/tests/test_reporting.rs index a01884951f0..73018a84a8e 100644 --- a/crates/compiler/load/tests/test_reporting.rs +++ b/crates/compiler/load/tests/test_reporting.rs @@ -1522,18 +1522,18 @@ mod test_reporting { from_annotation_if, indoc!( r" - x : Num.Int * + x : Num.Int _ x = if Bool.true then 3.14 else 4 x " ), - @r" + @r###" ── TYPE MISMATCH in /code/proj/Main.roc ──────────────────────────────────────── Something is off with the `then` branch of this `if` expression: - 4│ x : Num.Int * + 4│ x : Num.Int _ 5│ x = if Bool.true then 3.14 else 4 ^^^^ @@ -1547,14 +1547,14 @@ mod test_reporting { Tip: You can convert between integers and fractions using functions like `Num.to_frac` and `Num.round`. - " + "### ); test_report!( from_annotation_when, indoc!( r" - x : Num.Int * + x : Num.Int _ x = when True is _ -> 3.14 @@ -1562,12 +1562,12 @@ mod test_reporting { x " ), - @r" + @r###" ── TYPE MISMATCH in /code/proj/Main.roc ──────────────────────────────────────── Something is off with the body of the `x` definition: - 4│ x : Num.Int * + 4│ x : Num.Int _ 5│ x = 6│> when True is 7│> _ -> 3.14 @@ -1582,7 +1582,7 @@ mod test_reporting { Tip: You can convert between integers and fractions using functions like `Num.to_frac` and `Num.round`. - " + "### ); test_report!( @@ -1910,7 +1910,7 @@ mod test_reporting { from_annotation_complex_pattern, indoc!( r" - { x } : { x : Num.Int * } + { x } : { x : Num.Int _ } { x } = { x: 4.0 } x @@ -1921,7 +1921,7 @@ mod test_reporting { Something is off with the body of this definition: - 4│ { x } : { x : Num.Int * } + 4│ { x } : { x : Num.Int _ } 5│ { x } = { x: 4.0 } ^^^^^^^^^^ @@ -2047,18 +2047,18 @@ mod test_reporting { missing_fields, indoc!( r" - x : { a : Num.Int *, b : Num.Frac *, c : Str } + x : { a : Num.Int _, b : Num.Frac _, c : Str } x = { b: 4.0 } x " ), - @r" + @r###" ── TYPE MISMATCH in /code/proj/Main.roc ──────────────────────────────────────── Something is off with the body of the `x` definition: - 4│ x : { a : Num.Int *, b : Num.Frac *, c : Str } + 4│ x : { a : Num.Int _, b : Num.Frac _, c : Str } 5│ x = { b: 4.0 } ^^^^^^^^^^ @@ -2075,7 +2075,7 @@ mod test_reporting { } Tip: Looks like the c and a fields are missing. - " + "### ); // this previously reported the message below, not sure which is better @@ -3448,7 +3448,7 @@ mod test_reporting { x : AList Num.I64 Num.I64 x = ACons 0 (BCons 1 (ACons "foo" BNil )) - y : BList a a + y : BList _ _ y = BNil { x, y } @@ -4189,9 +4189,8 @@ mod test_reporting { RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty] # Create an empty dictionary. - empty : RBTree k v - empty = - Empty + empty : {} -> RBTree k v + empty = \{} -> Empty empty " @@ -11218,10 +11217,10 @@ All branches in an `if` must have the same type! import Decode exposing [decoder] - main = - my_decoder : Decoder (a -> a) fmt where fmt implements DecoderFormatting - my_decoder = decoder + my_decoder : Decoder (_ -> _) _ + my_decoder = decoder + main = my_decoder "# ), @@ -11230,12 +11229,12 @@ All branches in an `if` must have the same type! This expression has a type that does not implement the abilities it's expected to: - 7│ my_decoder = decoder - ^^^^^^^ + 6│ my_decoder = decoder + ^^^^^^^ I can't generate an implementation of the `Decoding` ability for - a -> a + * -> * Note: `Decoding` cannot be generated for functions. " @@ -11251,10 +11250,10 @@ All branches in an `if` must have the same type! A := {} - main = - my_decoder : Decoder {x : A} fmt where fmt implements DecoderFormatting - my_decoder = decoder + my_decoder : Decoder {x : A} _ + my_decoder = decoder + main = my_decoder "# ), @@ -11263,8 +11262,8 @@ All branches in an `if` must have the same type! This expression has a type that does not implement the abilities it's expected to: - 9│ my_decoder = decoder - ^^^^^^^ + 8│ my_decoder = decoder + ^^^^^^^ I can't generate an implementation of the `Decoding` ability for @@ -11514,11 +11513,10 @@ All branches in an `if` must have the same type! import Decode exposing [decoder] - main = - my_decoder : Decoder {x : Str, y ? Str} fmt where fmt implements DecoderFormatting - my_decoder = decoder + my_decoder : Decoder {x : Str, y ? Str} _ + my_decoder = decoder - my_decoder + main = my_decoder "# ), @r" @@ -11526,8 +11524,8 @@ All branches in an `if` must have the same type! This expression has a type that does not implement the abilities it's expected to: - 7│ my_decoder = decoder - ^^^^^^^ + 6│ my_decoder = decoder + ^^^^^^^ I can't generate an implementation of the `Decoding` ability for @@ -14111,11 +14109,10 @@ All branches in an `if` must have the same type! import Decode exposing [decoder] - main = - my_decoder : Decoder (U32, Str) fmt where fmt implements DecoderFormatting - my_decoder = decoder + my_decoder : Decoder (U32, Str) _ + my_decoder = decoder - my_decoder + main = my_decoder "# ) ); @@ -14128,11 +14125,10 @@ All branches in an `if` must have the same type! import Decode exposing [decoder] - main = - my_decoder : Decoder (U32, {} -> {}) fmt where fmt implements DecoderFormatting - my_decoder = decoder + my_decoder : Decoder (U32, {} -> {}) _ + my_decoder = decoder - my_decoder + main = my_decoder "# ), @r" @@ -14140,8 +14136,8 @@ All branches in an `if` must have the same type! This expression has a type that does not implement the abilities it's expected to: - 7│ my_decoder = decoder - ^^^^^^^ + 6│ my_decoder = decoder + ^^^^^^^ I can't generate an implementation of the `Decoding` ability for @@ -15997,4 +15993,66 @@ All branches in an `if` must have the same type! Str -> {} "# ); + + test_report!( + invalid_generic_literal, + indoc!( + r#" + module [v] + + v : * + v = 1 + "# + ), + @r###" + ── TYPE MISMATCH in /code/proj/Main.roc ──────────────────────────────────────── + + Something is off with the body of the `v` definition: + + 3│ v : * + 4│ v = 1 + ^ + + The body is a number of type: + + Num * + + But the type annotation on `v` says it should be: + + * + + Tip: The type annotation uses the type variable `*` to say that this + definition can produce any type of value. But in the body I see that + it will only produce a `Num` value of a single specific type. Maybe + change the type annotation to be more specific? Maybe change the code + to be more general? + "### + ); + + test_report!( + invalid_generic_literal_list, + indoc!( + r#" + module [v] + + v : List * + v = [] + "# + ), + @r###" + ── TYPE VARIABLE IS NOT GENERIC in /code/proj/Main.roc ───────────────────────── + + This type variable has a single type: + + 3│ v : List * + ^ + + Type variables tell me that they can be used with any type, but they + can only be used with functions. All other values have exactly one + type. + + Hint: If you would like the type to be inferred for you, use an + underscore _ instead. + "### + ); } diff --git a/crates/compiler/load_internal/tests/fixtures/build/app_with_deps/Primary.roc b/crates/compiler/load_internal/tests/fixtures/build/app_with_deps/Primary.roc index 6512dab3f35..c8a85af3539 100644 --- a/crates/compiler/load_internal/tests/fixtures/build/app_with_deps/Primary.roc +++ b/crates/compiler/load_internal/tests/fixtures/build/app_with_deps/Primary.roc @@ -24,7 +24,7 @@ succeed = \x -> Identity(x) with_default = Res.with_default -yay : Res.Res {} err +yay : Res.Res {} _ yay = ok = Ok("foo") diff --git a/crates/compiler/load_internal/tests/fixtures/build/module_with_deps/Primary.roc b/crates/compiler/load_internal/tests/fixtures/build/module_with_deps/Primary.roc index f47f972474c..509c07cea37 100644 --- a/crates/compiler/load_internal/tests/fixtures/build/module_with_deps/Primary.roc +++ b/crates/compiler/load_internal/tests/fixtures/build/module_with_deps/Primary.roc @@ -24,7 +24,7 @@ succeed = \x -> Identity(x) with_default = Res.with_default -yay : Res.Res {} err +yay : Res.Res {} _ yay = ok = Ok("foo") diff --git a/crates/compiler/load_internal/tests/test_load.rs b/crates/compiler/load_internal/tests/test_load.rs index 133a473231b..5386afccf37 100644 --- a/crates/compiler/load_internal/tests/test_load.rs +++ b/crates/compiler/load_internal/tests/test_load.rs @@ -271,11 +271,13 @@ fn load_fixture( ); } - assert!(loaded_module - .type_problems - .remove(&home) - .unwrap_or_default() - .is_empty()); + assert_eq!( + loaded_module + .type_problems + .remove(&home) + .unwrap_or_default(), + Vec::new() + ); let expected_name = loaded_module .interns @@ -433,11 +435,13 @@ fn module_with_deps() { loaded_module.can_problems.remove(&home).unwrap_or_default(), Vec::new() ); - assert!(loaded_module - .type_problems - .remove(&home) - .unwrap_or_default() - .is_empty(),); + assert_eq!( + loaded_module + .type_problems + .remove(&home) + .unwrap_or_default(), + Vec::new() + ); let mut def_count = 0; let declarations = loaded_module.declarations_by_id.remove(&home).unwrap(); diff --git a/crates/compiler/lower_params/src/type_error.rs b/crates/compiler/lower_params/src/type_error.rs index 538f14b34f4..a5e5c6a1df1 100644 --- a/crates/compiler/lower_params/src/type_error.rs +++ b/crates/compiler/lower_params/src/type_error.rs @@ -105,7 +105,8 @@ pub fn remove_module_param_arguments( | TypeError::ExpectedEffectful(_, _) | TypeError::UnsuffixedEffectfulFunction(_, _) | TypeError::SuffixedPureFunction(_, _) - | TypeError::InvalidTryTarget(_, _, _) => {} + | TypeError::InvalidTryTarget(_, _, _) + | TypeError::TypeIsNotGeneralized(..) => {} } } } @@ -213,6 +214,7 @@ fn drop_last_argument(err_type: &mut ErrorType) { | ErrorType::Alias(_, _, _, _) | ErrorType::Range(_) | ErrorType::Error - | ErrorType::EffectfulFunc => {} + | ErrorType::EffectfulFunc + | ErrorType::InferenceVar => {} } } diff --git a/crates/compiler/solve/src/solve.rs b/crates/compiler/solve/src/solve.rs index 20f3e00d666..aabe4547f69 100644 --- a/crates/compiler/solve/src/solve.rs +++ b/crates/compiler/solve/src/solve.rs @@ -15,15 +15,12 @@ use bumpalo::Bump; use roc_can::abilities::{AbilitiesStore, MemberSpecializationInfo}; use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::{ - Cycle, FxCallConstraint, FxSuffixConstraint, FxSuffixKind, LetConstraint, OpportunisticResolve, - TryTargetConstraint, + Cycle, FxCallConstraint, FxSuffixConstraint, FxSuffixKind, Generalizable, LetConstraint, + OpportunisticResolve, TryTargetConstraint, }; use roc_can::expected::{Expected, PExpected}; use roc_can::module::ModuleParams; use roc_collections::{VecMap, VecSet}; -use roc_debug_flags::dbg_do; -#[cfg(debug_assertions)] -use roc_debug_flags::ROC_VERIFY_RIGID_LET_GENERALIZED; use roc_error_macros::internal_error; use roc_module::ident::IdentSuffix; use roc_module::symbol::{ModuleId, Symbol}; @@ -32,8 +29,8 @@ use roc_region::all::{Loc, Region}; use roc_solve_problem::TypeError; use roc_solve_schema::UnificationMode; use roc_types::subs::{ - self, Content, FlatType, GetSubsSlice, Mark, OptVariable, Rank, Subs, TagExt, UlsOfVar, - Variable, + self, Content, ErrorTypeContext, FlatType, GetSubsSlice, Mark, OptVariable, Rank, Subs, TagExt, + UlsOfVar, Variable, }; use roc_types::types::{Category, Polarity, Reason, RecordField, Type, TypeExtension, Types, Uls}; use roc_unify::unify::{ @@ -356,29 +353,13 @@ fn solve( generalize(env, young_mark, visit_mark, rank.next()); debug_assert!(env.pools.get(rank.next()).is_empty(), "variables left over in let-binding scope, but they should all be in a lower scope or generalized now"); - // check that things went well - dbg_do!(ROC_VERIFY_RIGID_LET_GENERALIZED, { - let rigid_vars = &env.constraints[let_con.rigid_vars]; - - // NOTE the `subs.redundant` check does not come from elm. - // It's unclear whether this is a bug with our implementation - // (something is redundant that shouldn't be) - // or that it just never came up in elm. - let mut it = rigid_vars - .iter() - .filter(|loc_var| { - let var = loc_var.value; - !env.subs.redundant(var) && env.subs.get_rank(var) != Rank::GENERALIZED - }) - .peekable(); - - if it.peek().is_some() { - let failing: Vec<_> = it.collect(); - println!("Rigids {:?}", &rigid_vars); - println!("Failing {failing:?}"); - debug_assert!(false); - } - }); + let named_variables = &env.constraints[let_con.rigid_vars]; + check_named_variables_are_generalized( + env, + problems, + named_variables, + let_con.generalizable, + ); let mut new_scope = scope.clone(); for (symbol, loc_var) in local_def_vars.iter() { @@ -1636,6 +1617,30 @@ fn solve( state } +fn check_named_variables_are_generalized( + env: &mut InferenceEnv<'_>, + problems: &mut Vec, + named_variables: &[Loc], + generalizable: Generalizable, +) { + for loc_var in named_variables { + let is_generalized = env.subs.get_rank(loc_var.value) == Rank::GENERALIZED; + if !is_generalized { + // TODO: should be OF_PATTERN if on the LHS of a function, otherwise OF_VALUE. + let polarity = Polarity::OF_VALUE; + let ctx = ErrorTypeContext::NON_GENERALIZED_AS_INFERRED; + let error_type = env + .subs + .var_to_error_type_contextual(loc_var.value, ctx, polarity); + problems.push(TypeError::TypeIsNotGeneralized( + loc_var.region, + error_type, + generalizable, + )); + } + } +} + fn solve_suffix_fx( env: &mut InferenceEnv<'_>, problems: &mut Vec, diff --git a/crates/compiler/solve/tests/solve_expr.rs b/crates/compiler/solve/tests/solve_expr.rs index 95972c63cf2..8549a14a841 100644 --- a/crates/compiler/solve/tests/solve_expr.rs +++ b/crates/compiler/solve/tests/solve_expr.rs @@ -2510,13 +2510,15 @@ mod solve_expr { infer_eq_without_problem( indoc!( r" - empty : [Cons a (ConsList a), Nil] as ConsList a - empty = Nil + ConsList a : [Cons a (ConsList a), Nil] - empty - " + empty : ConsList _ + empty = Nil + + empty + " ), - "ConsList a", + "ConsList *", ); } @@ -3742,7 +3744,7 @@ mod solve_expr { indoc!( r" \rec -> - { x, y } : { x : I64, y ? Bool }* + { x, y } : { x : I64, y ? Bool }_ { x, y ? Bool.false } = rec { x, y } @@ -3909,26 +3911,6 @@ mod solve_expr { ); } - #[test] - fn double_named_rigids() { - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [main] to "./platform" - - - main : List x - main = - empty : List x - empty = [] - - empty - "# - ), - "List x", - ); - } - #[test] fn double_tag_application() { infer_eq_without_problem( diff --git a/crates/compiler/solve_problem/src/lib.rs b/crates/compiler/solve_problem/src/lib.rs index b02e81282c1..0fd0f9585f9 100644 --- a/crates/compiler/solve_problem/src/lib.rs +++ b/crates/compiler/solve_problem/src/lib.rs @@ -1,7 +1,7 @@ //! Provides types to describe problems that can occur during solving. use std::{path::PathBuf, str::Utf8Error}; -use roc_can::constraint::{ExpectEffectfulReason, FxSuffixKind}; +use roc_can::constraint::{ExpectEffectfulReason, FxSuffixKind, Generalizable}; use roc_can::expr::TryKind; use roc_can::{ constraint::FxCallKind, @@ -50,6 +50,7 @@ pub enum TypeError { UnsuffixedEffectfulFunction(Region, FxSuffixKind), SuffixedPureFunction(Region, FxSuffixKind), InvalidTryTarget(Region, ErrorType, TryKind), + TypeIsNotGeneralized(Region, ErrorType, Generalizable), } impl TypeError { @@ -80,6 +81,7 @@ impl TypeError { TypeError::UnsuffixedEffectfulFunction(_, _) => Warning, TypeError::SuffixedPureFunction(_, _) => Warning, TypeError::InvalidTryTarget(_, _, _) => RuntimeError, + TypeError::TypeIsNotGeneralized(..) => RuntimeError, } } @@ -101,7 +103,8 @@ impl TypeError { | TypeError::ExpectedEffectful(region, _) | TypeError::UnsuffixedEffectfulFunction(region, _) | TypeError::SuffixedPureFunction(region, _) - | TypeError::InvalidTryTarget(region, _, _) => Some(*region), + | TypeError::InvalidTryTarget(region, _, _) + | TypeError::TypeIsNotGeneralized(region, _, _) => Some(*region), TypeError::UnfulfilledAbility(ab, ..) => ab.region(), TypeError::Exhaustive(e) => Some(e.region()), TypeError::CircularDef(c) => c.first().map(|ce| ce.symbol_region), diff --git a/crates/compiler/test_gen/src/gen_primitives.rs b/crates/compiler/test_gen/src/gen_primitives.rs index d4334138f05..c08afeb834e 100644 --- a/crates/compiler/test_gen/src/gen_primitives.rs +++ b/crates/compiler/test_gen/src/gen_primitives.rs @@ -662,7 +662,7 @@ fn linked_list_len_1() { LinkedList a : [Nil, Cons a (LinkedList a)] - one : LinkedList (Int *) + one : LinkedList (Int _) one = Cons 1 Nil length : LinkedList a -> Int * @@ -690,7 +690,7 @@ fn linked_list_len_twice_1() { LinkedList a : [Nil, Cons a (LinkedList a)] - one : LinkedList (Int *) + one : LinkedList (Int _) one = Cons 1 Nil length : LinkedList a -> Int * @@ -718,7 +718,7 @@ fn linked_list_len_3() { LinkedList a : [Nil, Cons a (LinkedList a)] - three : LinkedList (Int *) + three : LinkedList (Int _) three = Cons 3 (Cons 2 (Cons 1 Nil)) length : LinkedList a -> Int * @@ -747,7 +747,7 @@ fn linked_list_sum_num_a() { LinkedList a : [Nil, Cons a (LinkedList a)] - three : LinkedList (Int *) + three : LinkedList (Int _) three = Cons 3 (Cons 2 (Cons 1 Nil)) @@ -776,7 +776,7 @@ fn linked_list_sum_int() { LinkedList a : [Nil, Cons a (LinkedList a)] - zero : LinkedList (Int *) + zero : LinkedList (Int _) zero = Nil sum : LinkedList (Int a) -> Int a @@ -804,7 +804,7 @@ fn linked_list_map() { LinkedList a : [Nil, Cons a (LinkedList a)] - three : LinkedList (Int *) + three : LinkedList (Int _) three = Cons 3 (Cons 2 (Cons 1 Nil)) sum : LinkedList (Num a) -> Num a @@ -836,7 +836,7 @@ fn when_nested_maybe() { r" Maybe a : [Nothing, Just a] - x : Maybe (Maybe (Int a)) + x : Maybe (Maybe (Int _)) x = Just (Just 41) when x is @@ -853,7 +853,7 @@ fn when_nested_maybe() { r" Maybe a : [Nothing, Just a] - x : Maybe (Maybe (Int *)) + x : Maybe (Maybe (Int _)) x = Just Nothing when x is @@ -871,7 +871,7 @@ fn when_nested_maybe() { r" Maybe a : [Nothing, Just a] - x : Maybe (Maybe (Int *)) + x : Maybe (Maybe (Int _)) x = Nothing when x is @@ -1402,7 +1402,7 @@ fn recursive_function_with_rigid() { else 1 + foo { count: state.count - 1, x: state.x } - main : Int * + main : Int _ main = foo { count: 3, x: {} } "# @@ -1517,7 +1517,7 @@ fn rbtree_balance_3() { balance = \key, left -> Node key left Empty - main : RedBlackTree (Int *) + main : RedBlackTree (Int _) main = balance 0 Empty "# @@ -1696,7 +1696,7 @@ fn nested_pattern_match_two_ways() { _ -> 3 _ -> 3 - main : Int * + main : Int _ main = when balance Nil is _ -> 3 @@ -1719,7 +1719,7 @@ fn nested_pattern_match_two_ways() { Cons 1 (Cons 1 _) -> 3 _ -> 3 - main : Int * + main : Int _ main = when balance Nil is _ -> 3 @@ -1751,7 +1751,7 @@ fn linked_list_guarded_double_pattern_match() { _ -> 3 _ -> 3 - main : Int * + main : Int _ main = when balance Nil is _ -> 3 @@ -1778,7 +1778,7 @@ fn linked_list_double_pattern_match() { Cons _ (Cons x _) -> x _ -> 0 - main : Int * + main : Int _ main = foo (Cons 1 (Cons 32 Nil)) "# @@ -1886,7 +1886,7 @@ fn wildcard_rigid() { @Effect inner - main : MyTask {} (Frac *) + main : MyTask {} (Frac _) main = always {} "# ), diff --git a/crates/compiler/test_gen/src/gen_result.rs b/crates/compiler/test_gen/src/gen_result.rs index de4fd7daf1f..35360609f0f 100644 --- a/crates/compiler/test_gen/src/gen_result.rs +++ b/crates/compiler/test_gen/src/gen_result.rs @@ -135,7 +135,7 @@ fn err_type_var_annotation() { assert_evals_to!( indoc!( r" - ok : Result I64 * + ok : Result I64 _ ok = Ok 3 Result.map_ok ok (\x -> x + 1) diff --git a/crates/compiler/test_gen/src/gen_tags.rs b/crates/compiler/test_gen/src/gen_tags.rs index 716803a3d27..9f76393d2c1 100644 --- a/crates/compiler/test_gen/src/gen_tags.rs +++ b/crates/compiler/test_gen/src/gen_tags.rs @@ -1075,7 +1075,7 @@ fn applied_tag_function_result() { assert_evals_to!( indoc!( r#" - x : List (Result Str *) + x : List (Result Str _) x = List.map ["a", "b"] Ok List.keep_oks x (\y -> y) diff --git a/crates/compiler/types/Cargo.toml b/crates/compiler/types/Cargo.toml index 3a9b4905e1a..f8e5e0a816e 100644 --- a/crates/compiler/types/Cargo.toml +++ b/crates/compiler/types/Cargo.toml @@ -22,3 +22,4 @@ bumpalo.workspace = true static_assertions.workspace = true soa.workspace = true +bitflags.workspace = true diff --git a/crates/compiler/types/src/subs.rs b/crates/compiler/types/src/subs.rs index f350c2f6090..e93428a2955 100644 --- a/crates/compiler/types/src/subs.rs +++ b/crates/compiler/types/src/subs.rs @@ -4,6 +4,7 @@ use crate::types::{ Polarity, RecordField, RecordFieldsError, TupleElemsError, TypeExt, Uls, }; use crate::unification_table::{self, UnificationTable}; +use bitflags::bitflags; use roc_collections::all::{FnvMap, ImMap, ImSet, MutSet, SendMap}; use roc_collections::{VecMap, VecSet}; use roc_error_macros::internal_error; @@ -50,10 +51,23 @@ impl fmt::Debug for Mark { } } -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum ErrorTypeContext { - None, - ExpandRanges, +bitflags! { + pub struct ErrorTypeContext : u8 { + /// List all number types that satisfy number range constraints. + const EXPAND_RANGES = 1 << 0; + /// Re-write non-generalized types like to inference variables. + const NON_GENERALIZED_AS_INFERRED = 1 << 1; + } +} + +impl ErrorTypeContext { + fn expand_ranges(&self) -> bool { + self.contains(Self::EXPAND_RANGES) + } + + fn non_generalized_as_inferred(&self) -> bool { + self.contains(Self::NON_GENERALIZED_AS_INFERRED) + } } struct ErrorTypeState { @@ -2055,7 +2069,7 @@ impl Subs { } pub fn var_to_error_type(&mut self, var: Variable, observed_pol: Polarity) -> ErrorType { - self.var_to_error_type_contextual(var, ErrorTypeContext::None, observed_pol) + self.var_to_error_type_contextual(var, ErrorTypeContext::empty(), observed_pol) } pub fn var_to_error_type_contextual( @@ -4020,6 +4034,13 @@ fn content_to_err_type( match content { Structure(flat_type) => flat_type_to_err_type(subs, state, flat_type, pol), + RigidVar(..) | RigidAbleVar(..) + if state.context.non_generalized_as_inferred() + && subs.get_rank(var) != Rank::GENERALIZED => + { + ErrorType::InferenceVar + } + FlexVar(opt_name) => { let name = match opt_name { Some(name_index) => subs.field_names[name_index.index()].clone(), @@ -4123,7 +4144,7 @@ fn content_to_err_type( } RangedNumber(range) => { - if state.context == ErrorTypeContext::ExpandRanges { + if state.context.expand_ranges() { let mut types = Vec::new(); for var in range.variable_slice() { types.push(var_to_err_type(subs, state, *var, pol)); diff --git a/crates/compiler/types/src/types.rs b/crates/compiler/types/src/types.rs index ce413149623..6f63a800199 100644 --- a/crates/compiler/types/src/types.rs +++ b/crates/compiler/types/src/types.rs @@ -3679,6 +3679,7 @@ pub enum ErrorType { /// If the name was auto-generated, it will start with a `#`. FlexVar(Lowercase), RigidVar(Lowercase), + InferenceVar, EffectfulFunc, /// If the name was auto-generated, it will start with a `#`. FlexAbleVar(Lowercase, AbilitySet), @@ -3733,6 +3734,7 @@ impl ErrorType { FlexVar(v) | RigidVar(v) | FlexAbleVar(v, _) | RigidAbleVar(v, _) => { taken.insert(v.clone()); } + InferenceVar => {} Record(fields, ext) => { fields .iter() @@ -3912,13 +3914,14 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens: Infinite => buf.push('∞'), Error => buf.push('?'), FlexVar(name) | RigidVar(name) => buf.push_str(name.as_str()), - FlexAbleVar(name, symbol) | RigidAbleVar(name, symbol) => { + InferenceVar => buf.push('_'), + FlexAbleVar(name, abilities) | RigidAbleVar(name, abilities) => { let write_parens = parens == Parens::InTypeParam; if write_parens { buf.push('('); } buf.push_str(name.as_str()); - write!(buf, "{} {:?}", roc_parse::keyword::IMPLEMENTS, symbol).unwrap(); + write!(buf, "{} {:?}", roc_parse::keyword::IMPLEMENTS, abilities).unwrap(); if write_parens { buf.push(')'); } diff --git a/crates/compiler/uitest/tests/solve/export_rigid_to_lower_rank.txt b/crates/compiler/uitest/tests/solve/export_rigid_to_lower_rank.txt deleted file mode 100644 index d444bce244d..00000000000 --- a/crates/compiler/uitest/tests/solve/export_rigid_to_lower_rank.txt +++ /dev/null @@ -1,9 +0,0 @@ -app "test" provides [foo] to "./platform" - -F a : { foo : a } - -foo = \arg -> -#^^^{-1} F b -[[foo(0)]]-> b - x : F b - x = arg - x.foo diff --git a/crates/compiler/unify/src/unify.rs b/crates/compiler/unify/src/unify.rs index 2b4e1dea286..6e7a780ce01 100644 --- a/crates/compiler/unify/src/unify.rs +++ b/crates/compiler/unify/src/unify.rs @@ -356,9 +356,9 @@ fn unify_help( } } else { let error_context = if mismatches.contains(&Mismatch::TypeNotInRange) { - ErrorTypeContext::ExpandRanges + ErrorTypeContext::EXPAND_RANGES } else { - ErrorTypeContext::None + ErrorTypeContext::empty() }; let type1 = env.var_to_error_type_contextual(var1, error_context, observed_pol); diff --git a/crates/reporting/src/error/type.rs b/crates/reporting/src/error/type.rs index 6c9831f9c7e..098ef9c8462 100644 --- a/crates/reporting/src/error/type.rs +++ b/crates/reporting/src/error/type.rs @@ -513,6 +513,38 @@ pub fn type_problem<'b>( severity, }) } + TypeIsNotGeneralized(region, actual_type, generalizable) => { + let doc = alloc.stack([ + alloc.reflow("This type variable has a single type:"), + alloc.region(lines.convert_region(region), severity), + if !generalizable.0 { + alloc.concat([ + alloc.reflow("Type variables tell me that they can be used with any type, but they can only be used with functions. All other values have exactly one type."), + ]) + } else { + alloc.concat([ + alloc.reflow("Type variables tell me that they can be used as any type."), + alloc.reflow("But, I found that this type can only be used as this single type:"), + alloc.type_block(to_doc(alloc, Parens::Unnecessary, actual_type).0) + ]) + }, + alloc.concat([ + alloc.hint(""), + alloc.reflow( + "If you would like the type to be inferred for you, use an underscore ", + ), + alloc.type_str("_"), + alloc.reflow(" instead."), + ]), + ]); + + Some(Report { + title: "TYPE VARIABLE IS NOT GENERIC".to_string(), + filename, + doc, + severity, + }) + } } } @@ -656,9 +688,9 @@ fn underivable_hint<'b>( }, ]))), NotDerivableContext::UnboundVar => { - let v = match typ { - ErrorType::FlexVar(v) => v, - ErrorType::RigidVar(v) => v, + let formatted_var = match typ { + ErrorType::FlexVar(v) | ErrorType::RigidVar(v) => alloc.type_variable(v.clone()), + ErrorType::InferenceVar => alloc.type_str("_"), _ => internal_error!("unbound variable context only applicable for variables"), }; @@ -671,7 +703,7 @@ fn underivable_hint<'b>( alloc.inline_type_block(alloc.concat([ alloc.keyword(roc_parse::keyword::WHERE), alloc.space(), - alloc.type_variable(v.clone()), + formatted_var, alloc.space(), alloc.keyword(roc_parse::keyword::IMPLEMENTS), alloc.space(), @@ -2868,6 +2900,7 @@ fn to_doc_help<'b>( ), Infinite => alloc.text("∞"), Error => alloc.text("?"), + InferenceVar => alloc.text("_"), FlexVar(lowercase) if is_generated_name(&lowercase) => { let &usages = gen_usages @@ -3082,6 +3115,7 @@ fn count_generated_name_usages<'a>( RigidVar(name) | RigidAbleVar(name, _) => { debug_assert!(!is_generated_name(name)); } + InferenceVar => {} EffectfulFunc => {} Type(_, tys) => { stack.extend(tys.iter().map(|t| (t, only_unseen))); @@ -3752,6 +3786,7 @@ fn should_show_diff(t1: &ErrorType, t2: &ErrorType) -> bool { // If either is flex, it will unify to the other type; no diff is needed. false } + (InferenceVar, InferenceVar) => false, (FlexAbleVar(v1, _set1), FlexAbleVar(v2, _set2)) | (RigidAbleVar(v1, _set1), RigidAbleVar(v2, _set2)) => { #[cfg(debug_assertions)] @@ -3944,7 +3979,9 @@ fn should_show_diff(t1: &ErrorType, t2: &ErrorType) -> bool { | (Function(_, _, _, _), _) | (_, Function(_, _, _, _)) | (EffectfulFunc, _) - | (_, EffectfulFunc) => true, + | (_, EffectfulFunc) + | (InferenceVar, _) + | (_, InferenceVar) => true, } } @@ -4985,7 +5022,7 @@ fn type_problem_to_pretty<'b>( }; match tipe { - Infinite | Error | FlexVar(_) | EffectfulFunc => alloc.nil(), + Infinite | Error | FlexVar(_) | InferenceVar | EffectfulFunc => alloc.nil(), FlexAbleVar(_, other_abilities) => { rigid_able_vs_different_flex_able(x, abilities, other_abilities) } @@ -5058,7 +5095,7 @@ fn type_problem_to_pretty<'b>( }; match tipe { - Infinite | Error | FlexVar(_) => alloc.nil(), + Infinite | Error | FlexVar(_) | InferenceVar => alloc.nil(), FlexAbleVar(_, abilities) => { let mut abilities = abilities.into_sorted_iter(); let msg = if abilities.len() == 1 {