mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 05:49:08 +00:00
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:
commit
adb89bbf82
8 changed files with 324 additions and 118 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(..)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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" }
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,59 +359,80 @@ 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.reflow(" does not implement "),
|
||||||
alloc.symbol_unqualified(ability),
|
alloc.symbol_unqualified(ability),
|
||||||
alloc.reflow(". Consider adding a "),
|
alloc.reflow("."),
|
||||||
alloc.keyword("has"),
|
if symbol.module_id() == alloc.home {
|
||||||
alloc.reflow(" clause to bind the type variable, like "),
|
alloc.concat([
|
||||||
alloc.inline_type_block(alloc.concat([
|
alloc.reflow(" Consider adding a custom implementation"),
|
||||||
alloc.string("| ".to_string()),
|
if ability.is_builtin() {
|
||||||
alloc.type_variable(v.clone()),
|
alloc.concat([
|
||||||
alloc.space(),
|
alloc.reflow(" or "),
|
||||||
alloc.keyword("has"),
|
alloc.inline_type_block(alloc.concat([
|
||||||
alloc.space(),
|
alloc.keyword("has"),
|
||||||
alloc.symbol_qualified(ability),
|
alloc.space(),
|
||||||
])),
|
alloc.symbol_qualified(ability),
|
||||||
|
])),
|
||||||
|
alloc.reflow(" to the definition of "),
|
||||||
|
alloc.symbol_unqualified(symbol),
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
alloc.nil()
|
||||||
|
},
|
||||||
|
alloc.reflow("."),
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
alloc.nil()
|
||||||
|
},
|
||||||
]))),
|
]))),
|
||||||
ErrorType::Alias(symbol, _, _, AliasKind::Opaque) => {
|
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([
|
Some(alloc.tip().append(alloc.concat([
|
||||||
alloc.symbol_unqualified(*symbol),
|
alloc.reflow("This type variable is not bound to "),
|
||||||
alloc.reflow(" does not implement "),
|
|
||||||
alloc.symbol_unqualified(ability),
|
alloc.symbol_unqualified(ability),
|
||||||
alloc.reflow("."),
|
alloc.reflow(". Consider adding a "),
|
||||||
if symbol.module_id() == alloc.home {
|
alloc.keyword("has"),
|
||||||
alloc.concat([
|
alloc.reflow(" clause to bind the type variable, like "),
|
||||||
alloc.reflow(" Consider adding a custom implementation"),
|
alloc.inline_type_block(alloc.concat([
|
||||||
if ability.is_builtin() {
|
alloc.string("| ".to_string()),
|
||||||
alloc.concat([
|
alloc.type_variable(v.clone()),
|
||||||
alloc.reflow(" or "),
|
alloc.space(),
|
||||||
alloc.inline_type_block(alloc.concat([
|
alloc.keyword("has"),
|
||||||
alloc.keyword("has"),
|
alloc.space(),
|
||||||
alloc.space(),
|
alloc.symbol_qualified(ability),
|
||||||
alloc.symbol_qualified(ability),
|
])),
|
||||||
])),
|
|
||||||
alloc.reflow(" to the definition of "),
|
|
||||||
alloc.symbol_unqualified(*symbol),
|
|
||||||
])
|
|
||||||
} else {
|
|
||||||
alloc.nil()
|
|
||||||
},
|
|
||||||
alloc.reflow("."),
|
|
||||||
])
|
|
||||||
} else {
|
|
||||||
alloc.nil()
|
|
||||||
},
|
|
||||||
])))
|
])))
|
||||||
}
|
}
|
||||||
_ => 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("?"),
|
||||||
|
])))
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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`?
|
||||||
|
"###
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue