From a0461679ddd1e0547dd0b9aec5967ac3bb7ce9f6 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Thu, 2 Jan 2025 14:26:37 -0600 Subject: [PATCH] Restrict usages of type variables in non-generalized contexts Type variables can only be used on functions (and in number literals as a carve-out for now). In all other cases, a type variable takes on a single, concrete type based on later usages. This check emits errors when this is violated. The implementation is to check the rank of a variable after it could be generalized. If the variable is not generalized but annotated as a type variable, emit an error. --- Cargo.lock | 1 + crates/compiler/builtins/roc/Num.roc | 2 +- crates/compiler/can/src/constraint.rs | 2 +- crates/compiler/debug_flags/src/lib.rs | 16 -- crates/compiler/load/tests/test_reporting.rs | 148 ++++++++++++------ .../compiler/lower_params/src/type_error.rs | 6 +- crates/compiler/solve/src/solve.rs | 65 ++++---- crates/compiler/solve_problem/src/lib.rs | 7 +- crates/compiler/types/Cargo.toml | 1 + crates/compiler/types/src/subs.rs | 34 +++- crates/compiler/types/src/types.rs | 7 +- crates/compiler/unify/src/unify.rs | 4 +- crates/reporting/src/error/type.rs | 51 +++++- 13 files changed, 230 insertions(+), 114 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b432181ebda..5237211da77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3282,6 +3282,7 @@ dependencies = [ name = "roc_types" version = "0.0.1" dependencies = [ + "bitflags 1.3.2", "bumpalo", "roc_collections", "roc_debug_flags", diff --git a/crates/compiler/builtins/roc/Num.roc b/crates/compiler/builtins/roc/Num.roc index 110e4581bb8..8804555bec4 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 7bb2c20ab9b..013ab9aca77 100644 --- a/crates/compiler/load/tests/test_reporting.rs +++ b/crates/compiler/load/tests/test_reporting.rs @@ -1519,18 +1519,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 ^^^^ @@ -1544,14 +1544,14 @@ mod test_reporting { Tip: You can convert between integers and fractions using functions like `Num.toFrac` and `Num.round`. - " + "### ); test_report!( from_annotation_when, indoc!( r" - x : Num.Int * + x : Num.Int _ x = when True is _ -> 3.14 @@ -1559,12 +1559,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 @@ -1579,7 +1579,7 @@ mod test_reporting { Tip: You can convert between integers and fractions using functions like `Num.toFrac` and `Num.round`. - " + "### ); test_report!( @@ -1907,7 +1907,7 @@ mod test_reporting { from_annotation_complex_pattern, indoc!( r" - { x } : { x : Num.Int * } + { x } : { x : Num.Int _ } { x } = { x: 4.0 } x @@ -1918,7 +1918,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 } ^^^^^^^^^^ @@ -2044,18 +2044,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 } ^^^^^^^^^^ @@ -2072,7 +2072,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 @@ -3445,7 +3445,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 } @@ -4186,9 +4186,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 " @@ -11129,10 +11128,10 @@ All branches in an `if` must have the same type! import Decode exposing [decoder] - main = - myDecoder : Decoder (a -> a) fmt where fmt implements DecoderFormatting - myDecoder = decoder + myDecoder : Decoder (_ -> _) _ + myDecoder = decoder + main = myDecoder "# ), @@ -11141,12 +11140,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│ myDecoder = decoder - ^^^^^^^ + 6│ myDecoder = decoder + ^^^^^^^ I can't generate an implementation of the `Decoding` ability for - a -> a + * -> * Note: `Decoding` cannot be generated for functions. "### @@ -11162,10 +11161,10 @@ All branches in an `if` must have the same type! A := {} - main = - myDecoder : Decoder {x : A} fmt where fmt implements DecoderFormatting - myDecoder = decoder + myDecoder : Decoder {x : A} _ + myDecoder = decoder + main = myDecoder "# ), @@ -11174,8 +11173,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│ myDecoder = decoder - ^^^^^^^ + 8│ myDecoder = decoder + ^^^^^^^ I can't generate an implementation of the `Decoding` ability for @@ -11425,11 +11424,10 @@ All branches in an `if` must have the same type! import Decode exposing [decoder] - main = - myDecoder : Decoder {x : Str, y ? Str} fmt where fmt implements DecoderFormatting - myDecoder = decoder + myDecoder : Decoder {x : Str, y ? Str} _ + myDecoder = decoder - myDecoder + main = myDecoder "# ), @r###" @@ -11437,8 +11435,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│ myDecoder = decoder - ^^^^^^^ + 6│ myDecoder = decoder + ^^^^^^^ I can't generate an implementation of the `Decoding` ability for @@ -14047,11 +14045,10 @@ All branches in an `if` must have the same type! import Decode exposing [decoder] - main = - myDecoder : Decoder (U32, Str) fmt where fmt implements DecoderFormatting - myDecoder = decoder + myDecoder : Decoder (U32, Str) _ + myDecoder = decoder - myDecoder + main = myDecoder "# ) ); @@ -14064,11 +14061,10 @@ All branches in an `if` must have the same type! import Decode exposing [decoder] - main = - myDecoder : Decoder (U32, {} -> {}) fmt where fmt implements DecoderFormatting - myDecoder = decoder + myDecoder : Decoder (U32, {} -> {}) _ + myDecoder = decoder - myDecoder + main = myDecoder "# ), @r###" @@ -14076,8 +14072,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│ myDecoder = decoder - ^^^^^^^ + 6│ myDecoder = decoder + ^^^^^^^ I can't generate an implementation of the `Decoding` ability for @@ -15933,4 +15929,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/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_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/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..3a84d7fcc0d 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,24 @@ impl fmt::Debug for Mark { } } -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum ErrorTypeContext { - None, - ExpandRanges, +bitflags! { + pub struct ErrorTypeContext : u8 { + const NONE = 1 << 0; + /// List all number types that satisfy number range constraints. + const EXPAND_RANGES = 1 << 1; + /// Re-write non-generalized types like to inference variables. + const NON_GENERALIZED_AS_INFERRED = 1 << 2; + } +} + +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 +2070,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 +4035,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 +4145,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/unify/src/unify.rs b/crates/compiler/unify/src/unify.rs index d5829a41701..81bc5edfa93 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 05f9aa0553a..8d4513729ae 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 {