Add more context to derivability errors when they happen

This commit is contained in:
Ayaz Hafiz 2022-08-09 08:44:20 -07:00
parent d2b9cc056f
commit 55fe1df995
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
5 changed files with 225 additions and 111 deletions

1
Cargo.lock generated
View file

@ -4037,6 +4037,7 @@ dependencies = [
"roc_collections", "roc_collections",
"roc_constrain", "roc_constrain",
"roc_derive", "roc_derive",
"roc_error_macros",
"roc_exhaustive", "roc_exhaustive",
"roc_fmt", "roc_fmt",
"roc_load", "roc_load",

View file

@ -4,7 +4,7 @@ use roc_collections::{VecMap, VecSet};
use roc_error_macros::{internal_error, todo_abilities}; use roc_error_macros::{internal_error, todo_abilities};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_solve_problem::{TypeError, UnderivableReason, Unfulfilled}; use roc_solve_problem::{NotDerivableContext, TypeError, UnderivableReason, Unfulfilled};
use roc_types::num::NumericRange; use roc_types::num::NumericRange;
use roc_types::subs::{ use roc_types::subs::{
instantiate_rigids, Content, FlatType, GetSubsSlice, Rank, RecordFields, Subs, Variable, instantiate_rigids, Content, FlatType, GetSubsSlice, Rank, RecordFields, Subs, Variable,
@ -278,11 +278,14 @@ impl ObligationCache {
// can derive! // can derive!
None None
} }
Some(Err(DerivableError::NotDerivable(failure_var))) => Some(if failure_var == var { Some(Err(NotDerivable {
UnderivableReason::SurfaceNotDerivable var: failure_var,
context,
})) => Some(if failure_var == var {
UnderivableReason::SurfaceNotDerivable(context)
} else { } else {
let (error_type, _skeletons) = subs.var_to_error_type(failure_var); let (error_type, _skeletons) = subs.var_to_error_type(failure_var);
UnderivableReason::NestedNotDerivable(error_type) UnderivableReason::NestedNotDerivable(error_type, context)
}), }),
None => Some(UnderivableReason::NotABuiltin), None => Some(UnderivableReason::NotABuiltin),
}; };
@ -430,8 +433,9 @@ fn is_builtin_number_alias(symbol: Symbol) -> bool {
) )
} }
enum DerivableError { struct NotDerivable {
NotDerivable(Variable), var: Variable,
context: NotDerivableContext,
} }
struct Descend(bool); struct Descend(bool);
@ -445,36 +449,51 @@ trait DerivableVisitor {
} }
#[inline(always)] #[inline(always)]
fn visit_flex_able(var: Variable, ability: Symbol) -> Result<(), DerivableError> { fn visit_flex_able(var: Variable, ability: Symbol) -> Result<(), NotDerivable> {
if ability != Self::ABILITY { if ability != Self::ABILITY {
Err(DerivableError::NotDerivable(var)) Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
} else { } else {
Ok(()) Ok(())
} }
} }
#[inline(always)] #[inline(always)]
fn visit_rigid_able(var: Variable, ability: Symbol) -> Result<(), DerivableError> { fn visit_rigid_able(var: Variable, ability: Symbol) -> Result<(), NotDerivable> {
if ability != Self::ABILITY { if ability != Self::ABILITY {
Err(DerivableError::NotDerivable(var)) Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
} else { } else {
Ok(()) Ok(())
} }
} }
#[inline(always)] #[inline(always)]
fn visit_recursion(var: Variable) -> Result<Descend, DerivableError> { fn visit_recursion(var: Variable) -> Result<Descend, NotDerivable> {
Err(DerivableError::NotDerivable(var)) Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
} }
#[inline(always)] #[inline(always)]
fn visit_apply(var: Variable, _symbol: Symbol) -> Result<Descend, DerivableError> { fn visit_apply(var: Variable, _symbol: Symbol) -> Result<Descend, NotDerivable> {
Err(DerivableError::NotDerivable(var)) Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
} }
#[inline(always)] #[inline(always)]
fn visit_func(var: Variable) -> Result<Descend, DerivableError> { fn visit_func(var: Variable) -> Result<Descend, NotDerivable> {
Err(DerivableError::NotDerivable(var)) Err(NotDerivable {
var,
context: NotDerivableContext::Function,
})
} }
#[inline(always)] #[inline(always)]
@ -482,43 +501,67 @@ trait DerivableVisitor {
_subs: &Subs, _subs: &Subs,
var: Variable, var: Variable,
_fields: RecordFields, _fields: RecordFields,
) -> Result<Descend, DerivableError> { ) -> Result<Descend, NotDerivable> {
Err(DerivableError::NotDerivable(var)) Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
} }
#[inline(always)] #[inline(always)]
fn visit_tag_union(var: Variable) -> Result<Descend, DerivableError> { fn visit_tag_union(var: Variable) -> Result<Descend, NotDerivable> {
Err(DerivableError::NotDerivable(var)) Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
} }
#[inline(always)] #[inline(always)]
fn visit_recursive_tag_union(var: Variable) -> Result<Descend, DerivableError> { fn visit_recursive_tag_union(var: Variable) -> Result<Descend, NotDerivable> {
Err(DerivableError::NotDerivable(var)) Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
} }
#[inline(always)] #[inline(always)]
fn visit_function_or_tag_union(var: Variable) -> Result<Descend, DerivableError> { fn visit_function_or_tag_union(var: Variable) -> Result<Descend, NotDerivable> {
Err(DerivableError::NotDerivable(var)) Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
} }
#[inline(always)] #[inline(always)]
fn visit_empty_record(var: Variable) -> Result<(), DerivableError> { fn visit_empty_record(var: Variable) -> Result<(), NotDerivable> {
Err(DerivableError::NotDerivable(var)) Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
} }
#[inline(always)] #[inline(always)]
fn visit_empty_tag_union(var: Variable) -> Result<(), DerivableError> { fn visit_empty_tag_union(var: Variable) -> Result<(), NotDerivable> {
Err(DerivableError::NotDerivable(var)) Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
} }
#[inline(always)] #[inline(always)]
fn visit_alias(var: Variable, _symbol: Symbol) -> Result<Descend, DerivableError> { fn visit_alias(var: Variable, _symbol: Symbol) -> Result<Descend, NotDerivable> {
Err(DerivableError::NotDerivable(var)) Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
} }
#[inline(always)] #[inline(always)]
fn visit_ranged_number(var: Variable, _range: NumericRange) -> Result<(), DerivableError> { fn visit_ranged_number(var: Variable, _range: NumericRange) -> Result<(), NotDerivable> {
Err(DerivableError::NotDerivable(var)) Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
} }
#[inline(always)] #[inline(always)]
@ -527,7 +570,7 @@ trait DerivableVisitor {
abilities_store: &AbilitiesStore, abilities_store: &AbilitiesStore,
subs: &mut Subs, subs: &mut Subs,
var: Variable, var: Variable,
) -> Result<(), DerivableError> { ) -> Result<(), NotDerivable> {
let mut stack = vec![var]; let mut stack = vec![var];
let mut seen_recursion_vars = vec![]; let mut seen_recursion_vars = vec![];
@ -545,14 +588,18 @@ trait DerivableVisitor {
let content = subs.get_content_without_compacting(var); let content = subs.get_content_without_compacting(var);
use Content::*; use Content::*;
use DerivableError::*;
use FlatType::*; use FlatType::*;
match *content { match *content {
FlexVar(opt_name) => { FlexVar(opt_name) => {
// Promote the flex var to be bound to the ability. // Promote the flex var to be bound to the ability.
subs.set_content(var, Content::FlexAbleVar(opt_name, Self::ABILITY)); subs.set_content(var, Content::FlexAbleVar(opt_name, Self::ABILITY));
} }
RigidVar(_) => return Err(NotDerivable(var)), RigidVar(_) => {
return Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
FlexAbleVar(_, ability) => Self::visit_flex_able(var, ability)?, FlexAbleVar(_, ability) => Self::visit_flex_able(var, ability)?,
RigidAbleVar(_, ability) => Self::visit_rigid_able(var, ability)?, RigidAbleVar(_, ability) => Self::visit_rigid_able(var, ability)?,
RecursionVar { RecursionVar {
@ -622,7 +669,12 @@ trait DerivableVisitor {
EmptyRecord => Self::visit_empty_record(var)?, EmptyRecord => Self::visit_empty_record(var)?,
EmptyTagUnion => Self::visit_empty_tag_union(var)?, EmptyTagUnion => Self::visit_empty_tag_union(var)?,
Erroneous(_) => return Err(NotDerivable(var)), Erroneous(_) => {
return Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
}, },
Alias( Alias(
Symbol::NUM_NUM | Symbol::NUM_INTEGER | Symbol::NUM_FLOATINGPOINT, Symbol::NUM_NUM | Symbol::NUM_INTEGER | Symbol::NUM_FLOATINGPOINT,
@ -639,7 +691,10 @@ trait DerivableVisitor {
.is_err() .is_err()
&& !Self::is_derivable_builtin_opaque(opaque) && !Self::is_derivable_builtin_opaque(opaque)
{ {
return Err(NotDerivable(var)); return Err(NotDerivable {
var,
context: NotDerivableContext::Opaque(opaque),
});
} }
} }
Alias(symbol, _alias_variables, real_var, AliasKind::Structural) => { Alias(symbol, _alias_variables, real_var, AliasKind::Structural) => {
@ -650,9 +705,17 @@ trait DerivableVisitor {
} }
RangedNumber(range) => Self::visit_ranged_number(var, range)?, RangedNumber(range) => Self::visit_ranged_number(var, range)?,
LambdaSet(..) => return Err(NotDerivable(var)), LambdaSet(..) => {
return Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
Error => { Error => {
return Err(NotDerivable(var)); return Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
});
} }
} }
} }
@ -671,19 +734,22 @@ impl DerivableVisitor for DeriveEncoding {
} }
#[inline(always)] #[inline(always)]
fn visit_recursion(_var: Variable) -> Result<Descend, DerivableError> { fn visit_recursion(_var: Variable) -> Result<Descend, NotDerivable> {
Ok(Descend(true)) Ok(Descend(true))
} }
#[inline(always)] #[inline(always)]
fn visit_apply(var: Variable, symbol: Symbol) -> Result<Descend, DerivableError> { fn visit_apply(var: Variable, symbol: Symbol) -> Result<Descend, NotDerivable> {
if matches!( if matches!(
symbol, symbol,
Symbol::LIST_LIST | Symbol::SET_SET | Symbol::DICT_DICT | Symbol::STR_STR, Symbol::LIST_LIST | Symbol::SET_SET | Symbol::DICT_DICT | Symbol::STR_STR,
) { ) {
Ok(Descend(true)) Ok(Descend(true))
} else { } else {
Err(DerivableError::NotDerivable(var)) Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
} }
} }
@ -692,37 +758,37 @@ impl DerivableVisitor for DeriveEncoding {
_subs: &Subs, _subs: &Subs,
_var: Variable, _var: Variable,
_fields: RecordFields, _fields: RecordFields,
) -> Result<Descend, DerivableError> { ) -> Result<Descend, NotDerivable> {
Ok(Descend(true)) Ok(Descend(true))
} }
#[inline(always)] #[inline(always)]
fn visit_tag_union(_var: Variable) -> Result<Descend, DerivableError> { fn visit_tag_union(_var: Variable) -> Result<Descend, NotDerivable> {
Ok(Descend(true)) Ok(Descend(true))
} }
#[inline(always)] #[inline(always)]
fn visit_recursive_tag_union(_var: Variable) -> Result<Descend, DerivableError> { fn visit_recursive_tag_union(_var: Variable) -> Result<Descend, NotDerivable> {
Ok(Descend(true)) Ok(Descend(true))
} }
#[inline(always)] #[inline(always)]
fn visit_function_or_tag_union(_var: Variable) -> Result<Descend, DerivableError> { fn visit_function_or_tag_union(_var: Variable) -> Result<Descend, NotDerivable> {
Ok(Descend(true)) Ok(Descend(true))
} }
#[inline(always)] #[inline(always)]
fn visit_empty_record(_var: Variable) -> Result<(), DerivableError> { fn visit_empty_record(_var: Variable) -> Result<(), NotDerivable> {
Ok(()) Ok(())
} }
#[inline(always)] #[inline(always)]
fn visit_empty_tag_union(_var: Variable) -> Result<(), DerivableError> { fn visit_empty_tag_union(_var: Variable) -> Result<(), NotDerivable> {
Ok(()) Ok(())
} }
#[inline(always)] #[inline(always)]
fn visit_alias(_var: Variable, symbol: Symbol) -> Result<Descend, DerivableError> { fn visit_alias(_var: Variable, symbol: Symbol) -> Result<Descend, NotDerivable> {
if is_builtin_number_alias(symbol) { if is_builtin_number_alias(symbol) {
Ok(Descend(false)) Ok(Descend(false))
} else { } else {
@ -731,7 +797,7 @@ impl DerivableVisitor for DeriveEncoding {
} }
#[inline(always)] #[inline(always)]
fn visit_ranged_number(_var: Variable, _range: NumericRange) -> Result<(), DerivableError> { fn visit_ranged_number(_var: Variable, _range: NumericRange) -> Result<(), NotDerivable> {
Ok(()) Ok(())
} }
} }
@ -746,19 +812,22 @@ impl DerivableVisitor for DeriveDecoding {
} }
#[inline(always)] #[inline(always)]
fn visit_recursion(_var: Variable) -> Result<Descend, DerivableError> { fn visit_recursion(_var: Variable) -> Result<Descend, NotDerivable> {
Ok(Descend(true)) Ok(Descend(true))
} }
#[inline(always)] #[inline(always)]
fn visit_apply(var: Variable, symbol: Symbol) -> Result<Descend, DerivableError> { fn visit_apply(var: Variable, symbol: Symbol) -> Result<Descend, NotDerivable> {
if matches!( if matches!(
symbol, symbol,
Symbol::LIST_LIST | Symbol::SET_SET | Symbol::DICT_DICT | Symbol::STR_STR, Symbol::LIST_LIST | Symbol::SET_SET | Symbol::DICT_DICT | Symbol::STR_STR,
) { ) {
Ok(Descend(true)) Ok(Descend(true))
} else { } else {
Err(DerivableError::NotDerivable(var)) Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
} }
} }
@ -767,46 +836,49 @@ impl DerivableVisitor for DeriveDecoding {
subs: &Subs, subs: &Subs,
var: Variable, var: Variable,
fields: RecordFields, fields: RecordFields,
) -> Result<Descend, DerivableError> { ) -> Result<Descend, NotDerivable> {
let has_optional_field = subs let has_optional_field = subs
.get_subs_slice(fields.record_fields()) .get_subs_slice(fields.record_fields())
.iter() .iter()
.any(|field| matches!(field, RecordField::Optional(..))); .any(|field| matches!(field, RecordField::Optional(..)));
if has_optional_field { if has_optional_field {
Err(DerivableError::NotDerivable(var)) Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
} else { } else {
Ok(Descend(true)) Ok(Descend(true))
} }
} }
#[inline(always)] #[inline(always)]
fn visit_tag_union(_var: Variable) -> Result<Descend, DerivableError> { fn visit_tag_union(_var: Variable) -> Result<Descend, NotDerivable> {
Ok(Descend(true)) Ok(Descend(true))
} }
#[inline(always)] #[inline(always)]
fn visit_recursive_tag_union(_var: Variable) -> Result<Descend, DerivableError> { fn visit_recursive_tag_union(_var: Variable) -> Result<Descend, NotDerivable> {
Ok(Descend(true)) Ok(Descend(true))
} }
#[inline(always)] #[inline(always)]
fn visit_function_or_tag_union(_var: Variable) -> Result<Descend, DerivableError> { fn visit_function_or_tag_union(_var: Variable) -> Result<Descend, NotDerivable> {
Ok(Descend(true)) Ok(Descend(true))
} }
#[inline(always)] #[inline(always)]
fn visit_empty_record(_var: Variable) -> Result<(), DerivableError> { fn visit_empty_record(_var: Variable) -> Result<(), NotDerivable> {
Ok(()) Ok(())
} }
#[inline(always)] #[inline(always)]
fn visit_empty_tag_union(_var: Variable) -> Result<(), DerivableError> { fn visit_empty_tag_union(_var: Variable) -> Result<(), NotDerivable> {
Ok(()) Ok(())
} }
#[inline(always)] #[inline(always)]
fn visit_alias(_var: Variable, symbol: Symbol) -> Result<Descend, DerivableError> { fn visit_alias(_var: Variable, symbol: Symbol) -> Result<Descend, NotDerivable> {
if is_builtin_number_alias(symbol) { if is_builtin_number_alias(symbol) {
Ok(Descend(false)) Ok(Descend(false))
} else { } else {
@ -815,7 +887,7 @@ impl DerivableVisitor for DeriveDecoding {
} }
#[inline(always)] #[inline(always)]
fn visit_ranged_number(_var: Variable, _range: NumericRange) -> Result<(), DerivableError> { fn visit_ranged_number(_var: Variable, _range: NumericRange) -> Result<(), NotDerivable> {
Ok(()) Ok(())
} }
} }

View file

@ -1,5 +1,5 @@
use roc_can::expected::{Expected, PExpected}; use roc_can::expected::{Expected, PExpected};
use roc_module::symbol::Symbol; use roc_module::{ident::Lowercase, symbol::Symbol};
use roc_problem::can::CycleEntry; use roc_problem::can::CycleEntry;
use roc_region::all::Region; use roc_region::all::Region;
@ -55,7 +55,21 @@ pub enum Unfulfilled {
pub enum UnderivableReason { pub enum UnderivableReason {
NotABuiltin, NotABuiltin,
/// The surface type is not derivable /// The surface type is not derivable
SurfaceNotDerivable, SurfaceNotDerivable(NotDerivableContext),
/// A nested type is not derivable /// A nested type is not derivable
NestedNotDerivable(ErrorType), NestedNotDerivable(ErrorType, NotDerivableContext),
}
#[derive(PartialEq, Debug, Clone)]
pub enum NotDerivableContext {
NoContext,
Function,
UnboundVar,
Opaque(Symbol),
Decode(NotDerivableDecode),
}
#[derive(PartialEq, Debug, Clone)]
pub enum NotDerivableDecode {
OptionalRecordField(Lowercase),
} }

View file

@ -7,6 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
roc_collections = { path = "../compiler/collections" } roc_collections = { path = "../compiler/collections" }
roc_error_macros = { path = "../error_macros" }
roc_exhaustive = { path = "../compiler/exhaustive" } roc_exhaustive = { path = "../compiler/exhaustive" }
roc_region = { path = "../compiler/region" } roc_region = { path = "../compiler/region" }
roc_module = { path = "../compiler/module" } roc_module = { path = "../compiler/module" }

View file

@ -2,12 +2,15 @@ use crate::error::canonicalize::{to_circular_def_doc, CIRCULAR_DEF};
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity}; use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity};
use roc_can::expected::{Expected, PExpected}; use roc_can::expected::{Expected, PExpected};
use roc_collections::all::{HumanIndex, MutSet, SendMap}; use roc_collections::all::{HumanIndex, MutSet, SendMap};
use roc_error_macros::internal_error;
use roc_exhaustive::CtorName; use roc_exhaustive::CtorName;
use roc_module::called_via::{BinOp, CalledVia}; use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::{Ident, IdentStr, Lowercase, TagName}; use roc_module::ident::{Ident, IdentStr, Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{LineInfo, Loc, Region}; use roc_region::all::{LineInfo, Loc, Region};
use roc_solve_problem::{TypeError, UnderivableReason, Unfulfilled}; use roc_solve_problem::{
NotDerivableContext, NotDerivableDecode, TypeError, UnderivableReason, Unfulfilled,
};
use roc_std::RocDec; use roc_std::RocDec;
use roc_types::pretty_print::{Parens, WILDCARD}; use roc_types::pretty_print::{Parens, WILDCARD};
use roc_types::types::{ use roc_types::types::{
@ -334,9 +337,11 @@ fn report_underivable_reason<'a>(
UnderivableReason::NotABuiltin => { UnderivableReason::NotABuiltin => {
Some(alloc.reflow("Only builtin abilities can have generated implementations!")) Some(alloc.reflow("Only builtin abilities can have generated implementations!"))
} }
UnderivableReason::SurfaceNotDerivable => underivable_hint(alloc, ability, typ), UnderivableReason::SurfaceNotDerivable(context) => {
UnderivableReason::NestedNotDerivable(nested_typ) => { underivable_hint(alloc, ability, context, typ)
let hint = underivable_hint(alloc, ability, &nested_typ); }
UnderivableReason::NestedNotDerivable(nested_typ, context) => {
let hint = underivable_hint(alloc, ability, context, &nested_typ);
let reason = alloc.stack( let reason = alloc.stack(
[ [
alloc.reflow("In particular, an implementation for"), alloc.reflow("In particular, an implementation for"),
@ -354,31 +359,17 @@ fn report_underivable_reason<'a>(
fn underivable_hint<'b>( fn underivable_hint<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
ability: Symbol, ability: Symbol,
context: NotDerivableContext,
typ: &ErrorType, typ: &ErrorType,
) -> Option<RocDocBuilder<'b>> { ) -> Option<RocDocBuilder<'b>> {
match typ { match context {
ErrorType::Function(..) => Some(alloc.note("").append(alloc.concat([ NotDerivableContext::NoContext => None,
NotDerivableContext::Function => Some(alloc.note("").append(alloc.concat([
alloc.symbol_unqualified(ability), alloc.symbol_unqualified(ability),
alloc.reflow(" cannot be generated for functions."), alloc.reflow(" cannot be generated for functions."),
]))), ]))),
ErrorType::FlexVar(v) | ErrorType::RigidVar(v) => Some(alloc.tip().append(alloc.concat([ NotDerivableContext::Opaque(symbol) => Some(alloc.tip().append(alloc.concat([
alloc.reflow("This type variable is not bound to "), alloc.symbol_unqualified(symbol),
alloc.symbol_unqualified(ability),
alloc.reflow(". Consider adding a "),
alloc.keyword("has"),
alloc.reflow(" clause to bind the type variable, like "),
alloc.inline_type_block(alloc.concat([
alloc.string("| ".to_string()),
alloc.type_variable(v.clone()),
alloc.space(),
alloc.keyword("has"),
alloc.space(),
alloc.symbol_qualified(ability),
])),
]))),
ErrorType::Alias(symbol, _, _, AliasKind::Opaque) => {
Some(alloc.tip().append(alloc.concat([
alloc.symbol_unqualified(*symbol),
alloc.reflow(" does not implement "), alloc.reflow(" does not implement "),
alloc.symbol_unqualified(ability), alloc.symbol_unqualified(ability),
alloc.reflow("."), alloc.reflow("."),
@ -394,7 +385,7 @@ fn underivable_hint<'b>(
alloc.symbol_qualified(ability), alloc.symbol_qualified(ability),
])), ])),
alloc.reflow(" to the definition of "), alloc.reflow(" to the definition of "),
alloc.symbol_unqualified(*symbol), alloc.symbol_unqualified(symbol),
]) ])
} else { } else {
alloc.nil() alloc.nil()
@ -404,9 +395,44 @@ fn underivable_hint<'b>(
} else { } else {
alloc.nil() alloc.nil()
}, },
]))),
NotDerivableContext::UnboundVar => {
let v = match typ {
ErrorType::FlexVar(v) => v,
ErrorType::RigidVar(v) => v,
_ => internal_error!("unbound variable context only applicable for variables"),
};
Some(alloc.tip().append(alloc.concat([
alloc.reflow("This type variable is not bound to "),
alloc.symbol_unqualified(ability),
alloc.reflow(". Consider adding a "),
alloc.keyword("has"),
alloc.reflow(" clause to bind the type variable, like "),
alloc.inline_type_block(alloc.concat([
alloc.string("| ".to_string()),
alloc.type_variable(v.clone()),
alloc.space(),
alloc.keyword("has"),
alloc.space(),
alloc.symbol_qualified(ability),
])),
]))) ])))
} }
_ => None, NotDerivableContext::Decode(reason) => match reason {
NotDerivableDecode::OptionalRecordField(field) => {
Some(alloc.note("").append(alloc.concat([
alloc.reflow("Roc cannot derive decoding for a record with an optional field, which in this case is "),
alloc.record_field(field),
alloc.reflow(". Optional record fields are polymorphic over records that may or may not contain them at compile time, "),
alloc.reflow("but are not a concept that extends to runtime! That means Roc cannot derive a decoder for a record with an optional value "),
alloc.reflow("by way of an optional record field. If you want to model the idea that a field may or may not be present at runtime, "),
alloc.reflow("consider using a "),
alloc.symbol_unqualified(Symbol::RESULT_RESULT),
alloc.reflow("."),
])))
}
},
} }
} }