Merge pull request #3734 from roc-lang/decoding-optional-record-fields-illegal

Report errors for attempting to derive decoding of records with optional field types
This commit is contained in:
Richard Feldman 2022-08-27 21:12:44 -04:00 committed by GitHub
commit adb89bbf82
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 324 additions and 118 deletions

1
Cargo.lock generated
View file

@ -4048,6 +4048,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,9 +4,13 @@ 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, NotDerivableDecode, TypeError, UnderivableReason, Unfulfilled,
};
use roc_types::num::NumericRange; use roc_types::num::NumericRange;
use roc_types::subs::{instantiate_rigids, Content, FlatType, GetSubsSlice, Rank, Subs, Variable}; use roc_types::subs::{
instantiate_rigids, Content, FlatType, GetSubsSlice, Rank, RecordFields, Subs, Variable,
};
use roc_types::types::{AliasKind, Category, MemberImpl, PatternCategory}; use roc_types::types::{AliasKind, Category, MemberImpl, PatternCategory};
use roc_unify::unify::{Env, MustImplementConstraints}; use roc_unify::unify::{Env, MustImplementConstraints};
use roc_unify::unify::{MustImplementAbility, Obligated}; use roc_unify::unify::{MustImplementAbility, Obligated};
@ -276,11 +280,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),
}; };
@ -428,8 +435,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);
@ -443,76 +451,119 @@ 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)]
fn visit_record(var: Variable) -> Result<Descend, DerivableError> { fn visit_record(
Err(DerivableError::NotDerivable(var)) _subs: &Subs,
var: Variable,
_fields: RecordFields,
) -> Result<Descend, NotDerivable> {
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)]
@ -521,7 +572,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![];
@ -539,14 +590,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 {
@ -574,7 +629,7 @@ trait DerivableVisitor {
} }
} }
Record(fields, ext) => { Record(fields, ext) => {
let descend = Self::visit_record(var)?; let descend = Self::visit_record(subs, var, fields)?;
if descend.0 { if descend.0 {
push_var_slice!(fields.variables()); push_var_slice!(fields.variables());
if !matches!( if !matches!(
@ -616,7 +671,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,
@ -633,7 +693,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) => {
@ -644,9 +707,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,
});
} }
} }
} }
@ -665,54 +736,61 @@ 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,
})
} }
} }
#[inline(always)] #[inline(always)]
fn visit_record(_var: Variable) -> Result<Descend, DerivableError> { fn visit_record(
_subs: &Subs,
_var: Variable,
_fields: RecordFields,
) -> 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 {
@ -721,7 +799,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(())
} }
} }
@ -736,54 +814,72 @@ 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,
})
} }
} }
#[inline(always)] #[inline(always)]
fn visit_record(_var: Variable) -> Result<Descend, DerivableError> { fn visit_record(
subs: &Subs,
var: Variable,
fields: RecordFields,
) -> Result<Descend, NotDerivable> {
for (field_name, _, field) in fields.iter_all() {
if subs[field].is_optional() {
return Err(NotDerivable {
var,
context: NotDerivableContext::Decode(NotDerivableDecode::OptionalRecordField(
subs[field_name].clone(),
)),
});
}
}
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 {
@ -792,7 +888,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

@ -104,7 +104,10 @@ impl<T> RecordField<T> {
} }
pub fn is_optional(&self) -> bool { pub fn is_optional(&self) -> bool {
matches!(self, RecordField::Optional(..)) matches!(
self,
RecordField::Optional(..) | RecordField::RigidOptional(..)
)
} }
} }

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

@ -972,7 +972,7 @@ pub fn can_problem<'b>(
alloc.symbol_unqualified(original_opaque), alloc.symbol_unqualified(original_opaque),
alloc.reflow("."), alloc.reflow("."),
]), ]),
alloc.reflow("Ability specializations can only provide implementations for one opauqe type, since all opaque types are different!"), alloc.reflow("Ability specializations can only provide implementations for one opaque type, since all opaque types are different!"),
]); ]);
title = "OVERLOADED SPECIALIZATION".to_string(); title = "OVERLOADED SPECIALIZATION".to_string();
severity = Severity::Warning; severity = Severity::Warning;

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("I can't 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!"),
alloc.hardline(),
alloc.reflow("Maybe you wanted to use a "),
alloc.symbol_unqualified(Symbol::RESULT_RESULT),
alloc.reflow("?"),
])))
}
},
} }
} }

View file

@ -8510,7 +8510,7 @@ All branches in an `if` must have the same type!
Previously, we found it to specialize `hash` for `One`. Previously, we found it to specialize `hash` for `One`.
Ability specializations can only provide implementations for one Ability specializations can only provide implementations for one
opauqe type, since all opaque types are different! opaque type, since all opaque types are different!
TYPE MISMATCH /code/proj/Main.roc TYPE MISMATCH /code/proj/Main.roc
@ -8537,7 +8537,6 @@ All branches in an `if` must have the same type!
); );
test_report!( test_report!(
#[ignore = "TODO does not error yet"]
ability_specialization_is_duplicated_with_type_mismatch, ability_specialization_is_duplicated_with_type_mismatch,
indoc!( indoc!(
r#" r#"
@ -8553,6 +8552,18 @@ All branches in an `if` must have the same type!
"# "#
), ),
@r###" @r###"
OVERLOADED SPECIALIZATION /code/proj/Main.roc
This ability member specialization is already claimed to specialize
another opaque type:
7 Two := {} has [Hash {hash}]
^^^^
Previously, we found it to specialize `hash` for `One`.
Ability specializations can only provide implementations for one
opaque type, since all opaque types are different!
"### "###
); );
@ -10296,13 +10307,13 @@ All branches in an `if` must have the same type!
); );
test_report!( test_report!(
#[ignore = "needs structural deriving to be turned on first"]
nested_opaque_cannot_derive_encoding, nested_opaque_cannot_derive_encoding,
indoc!( indoc!(
r#" r#"
app "test" imports [Decode.{Decoder, DecoderFormatting, decoder}] provides [main] to "./platform" app "test" imports [Decode.{Decoder, DecoderFormatting, decoder}] provides [main] to "./platform"
A : {} A := {}
main = main =
myDecoder : Decoder {x : A} fmt | fmt has DecoderFormatting myDecoder : Decoder {x : A} fmt | fmt has DecoderFormatting
myDecoder = decoder myDecoder = decoder
@ -10311,6 +10322,26 @@ All branches in an `if` must have the same type!
"# "#
), ),
@r###" @r###"
TYPE MISMATCH /code/proj/Main.roc
This expression has a type that does not implement the abilities it's expected to:
7 myDecoder = decoder
^^^^^^^
Roc can't generate an implementation of the `Decode.Decoding` ability
for
{ x : A }
In particular, an implementation for
A
cannot be generated.
Tip: `A` does not implement `Decoding`. Consider adding a custom
implementation or `has Decode.Decoding` to the definition of `A`.
"### "###
); );
@ -10487,4 +10518,38 @@ All branches in an `if` must have the same type!
Note: `Decoding` cannot be generated for functions. Note: `Decoding` cannot be generated for functions.
"### "###
); );
test_report!(
record_with_optional_field_types_cannot_derive_decoding,
indoc!(
r#"
app "test" imports [Decode.{Decoder, DecoderFormatting, decoder}] provides [main] to "./platform"
main =
myDecoder : Decoder {x : Str, y ? Str} fmt | fmt has DecoderFormatting
myDecoder = decoder
myDecoder
"#
),
@r###"
TYPE MISMATCH /code/proj/Main.roc
This expression has a type that does not implement the abilities it's expected to:
5 myDecoder = decoder
^^^^^^^
Roc can't generate an implementation of the `Decode.Decoding` ability
for
{ x : Str, y ? Str }
Note: I can't derive decoding for a record with an optional field,
which in this case is `.y`. Optional record fields are polymorphic over
records that may or may not contain them at compile time, but are not
a concept that extends to runtime!
Maybe you wanted to use a `Result`?
"###
);
} }