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_constrain",
"roc_derive",
"roc_error_macros",
"roc_exhaustive",
"roc_fmt",
"roc_load",

View file

@ -4,9 +4,13 @@ use roc_collections::{VecMap, VecSet};
use roc_error_macros::{internal_error, todo_abilities};
use roc_module::symbol::Symbol;
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::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_unify::unify::{Env, MustImplementConstraints};
use roc_unify::unify::{MustImplementAbility, Obligated};
@ -276,11 +280,14 @@ impl ObligationCache {
// can derive!
None
}
Some(Err(DerivableError::NotDerivable(failure_var))) => Some(if failure_var == var {
UnderivableReason::SurfaceNotDerivable
Some(Err(NotDerivable {
var: failure_var,
context,
})) => Some(if failure_var == var {
UnderivableReason::SurfaceNotDerivable(context)
} else {
let (error_type, _skeletons) = subs.var_to_error_type(failure_var);
UnderivableReason::NestedNotDerivable(error_type)
UnderivableReason::NestedNotDerivable(error_type, context)
}),
None => Some(UnderivableReason::NotABuiltin),
};
@ -428,8 +435,9 @@ fn is_builtin_number_alias(symbol: Symbol) -> bool {
)
}
enum DerivableError {
NotDerivable(Variable),
struct NotDerivable {
var: Variable,
context: NotDerivableContext,
}
struct Descend(bool);
@ -443,76 +451,119 @@ trait DerivableVisitor {
}
#[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 {
Err(DerivableError::NotDerivable(var))
Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
} else {
Ok(())
}
}
#[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 {
Err(DerivableError::NotDerivable(var))
Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
} else {
Ok(())
}
}
#[inline(always)]
fn visit_recursion(var: Variable) -> Result<Descend, DerivableError> {
Err(DerivableError::NotDerivable(var))
fn visit_recursion(var: Variable) -> Result<Descend, NotDerivable> {
Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
#[inline(always)]
fn visit_apply(var: Variable, _symbol: Symbol) -> Result<Descend, DerivableError> {
Err(DerivableError::NotDerivable(var))
fn visit_apply(var: Variable, _symbol: Symbol) -> Result<Descend, NotDerivable> {
Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
#[inline(always)]
fn visit_func(var: Variable) -> Result<Descend, DerivableError> {
Err(DerivableError::NotDerivable(var))
fn visit_func(var: Variable) -> Result<Descend, NotDerivable> {
Err(NotDerivable {
var,
context: NotDerivableContext::Function,
})
}
#[inline(always)]
fn visit_record(var: Variable) -> Result<Descend, DerivableError> {
Err(DerivableError::NotDerivable(var))
fn visit_record(
_subs: &Subs,
var: Variable,
_fields: RecordFields,
) -> Result<Descend, NotDerivable> {
Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
#[inline(always)]
fn visit_tag_union(var: Variable) -> Result<Descend, DerivableError> {
Err(DerivableError::NotDerivable(var))
fn visit_tag_union(var: Variable) -> Result<Descend, NotDerivable> {
Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
#[inline(always)]
fn visit_recursive_tag_union(var: Variable) -> Result<Descend, DerivableError> {
Err(DerivableError::NotDerivable(var))
fn visit_recursive_tag_union(var: Variable) -> Result<Descend, NotDerivable> {
Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
#[inline(always)]
fn visit_function_or_tag_union(var: Variable) -> Result<Descend, DerivableError> {
Err(DerivableError::NotDerivable(var))
fn visit_function_or_tag_union(var: Variable) -> Result<Descend, NotDerivable> {
Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
#[inline(always)]
fn visit_empty_record(var: Variable) -> Result<(), DerivableError> {
Err(DerivableError::NotDerivable(var))
fn visit_empty_record(var: Variable) -> Result<(), NotDerivable> {
Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
#[inline(always)]
fn visit_empty_tag_union(var: Variable) -> Result<(), DerivableError> {
Err(DerivableError::NotDerivable(var))
fn visit_empty_tag_union(var: Variable) -> Result<(), NotDerivable> {
Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
#[inline(always)]
fn visit_alias(var: Variable, _symbol: Symbol) -> Result<Descend, DerivableError> {
Err(DerivableError::NotDerivable(var))
fn visit_alias(var: Variable, _symbol: Symbol) -> Result<Descend, NotDerivable> {
Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
#[inline(always)]
fn visit_ranged_number(var: Variable, _range: NumericRange) -> Result<(), DerivableError> {
Err(DerivableError::NotDerivable(var))
fn visit_ranged_number(var: Variable, _range: NumericRange) -> Result<(), NotDerivable> {
Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
#[inline(always)]
@ -521,7 +572,7 @@ trait DerivableVisitor {
abilities_store: &AbilitiesStore,
subs: &mut Subs,
var: Variable,
) -> Result<(), DerivableError> {
) -> Result<(), NotDerivable> {
let mut stack = vec![var];
let mut seen_recursion_vars = vec![];
@ -539,14 +590,18 @@ trait DerivableVisitor {
let content = subs.get_content_without_compacting(var);
use Content::*;
use DerivableError::*;
use FlatType::*;
match *content {
FlexVar(opt_name) => {
// Promote the flex var to be bound to the 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)?,
RigidAbleVar(_, ability) => Self::visit_rigid_able(var, ability)?,
RecursionVar {
@ -574,7 +629,7 @@ trait DerivableVisitor {
}
}
Record(fields, ext) => {
let descend = Self::visit_record(var)?;
let descend = Self::visit_record(subs, var, fields)?;
if descend.0 {
push_var_slice!(fields.variables());
if !matches!(
@ -616,7 +671,12 @@ trait DerivableVisitor {
EmptyRecord => Self::visit_empty_record(var)?,
EmptyTagUnion => Self::visit_empty_tag_union(var)?,
Erroneous(_) => return Err(NotDerivable(var)),
Erroneous(_) => {
return Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
},
Alias(
Symbol::NUM_NUM | Symbol::NUM_INTEGER | Symbol::NUM_FLOATINGPOINT,
@ -633,7 +693,10 @@ trait DerivableVisitor {
.is_err()
&& !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) => {
@ -644,9 +707,17 @@ trait DerivableVisitor {
}
RangedNumber(range) => Self::visit_ranged_number(var, range)?,
LambdaSet(..) => return Err(NotDerivable(var)),
LambdaSet(..) => {
return Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
Error => {
return Err(NotDerivable(var));
return Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
});
}
}
}
@ -665,54 +736,61 @@ impl DerivableVisitor for DeriveEncoding {
}
#[inline(always)]
fn visit_recursion(_var: Variable) -> Result<Descend, DerivableError> {
fn visit_recursion(_var: Variable) -> Result<Descend, NotDerivable> {
Ok(Descend(true))
}
#[inline(always)]
fn visit_apply(var: Variable, symbol: Symbol) -> Result<Descend, DerivableError> {
fn visit_apply(var: Variable, symbol: Symbol) -> Result<Descend, NotDerivable> {
if matches!(
symbol,
Symbol::LIST_LIST | Symbol::SET_SET | Symbol::DICT_DICT | Symbol::STR_STR,
) {
Ok(Descend(true))
} else {
Err(DerivableError::NotDerivable(var))
Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
}
#[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))
}
#[inline(always)]
fn visit_tag_union(_var: Variable) -> Result<Descend, DerivableError> {
fn visit_tag_union(_var: Variable) -> Result<Descend, NotDerivable> {
Ok(Descend(true))
}
#[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))
}
#[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))
}
#[inline(always)]
fn visit_empty_record(_var: Variable) -> Result<(), DerivableError> {
fn visit_empty_record(_var: Variable) -> Result<(), NotDerivable> {
Ok(())
}
#[inline(always)]
fn visit_empty_tag_union(_var: Variable) -> Result<(), DerivableError> {
fn visit_empty_tag_union(_var: Variable) -> Result<(), NotDerivable> {
Ok(())
}
#[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) {
Ok(Descend(false))
} else {
@ -721,7 +799,7 @@ impl DerivableVisitor for DeriveEncoding {
}
#[inline(always)]
fn visit_ranged_number(_var: Variable, _range: NumericRange) -> Result<(), DerivableError> {
fn visit_ranged_number(_var: Variable, _range: NumericRange) -> Result<(), NotDerivable> {
Ok(())
}
}
@ -736,54 +814,72 @@ impl DerivableVisitor for DeriveDecoding {
}
#[inline(always)]
fn visit_recursion(_var: Variable) -> Result<Descend, DerivableError> {
fn visit_recursion(_var: Variable) -> Result<Descend, NotDerivable> {
Ok(Descend(true))
}
#[inline(always)]
fn visit_apply(var: Variable, symbol: Symbol) -> Result<Descend, DerivableError> {
fn visit_apply(var: Variable, symbol: Symbol) -> Result<Descend, NotDerivable> {
if matches!(
symbol,
Symbol::LIST_LIST | Symbol::SET_SET | Symbol::DICT_DICT | Symbol::STR_STR,
) {
Ok(Descend(true))
} else {
Err(DerivableError::NotDerivable(var))
Err(NotDerivable {
var,
context: NotDerivableContext::NoContext,
})
}
}
#[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))
}
#[inline(always)]
fn visit_tag_union(_var: Variable) -> Result<Descend, DerivableError> {
fn visit_tag_union(_var: Variable) -> Result<Descend, NotDerivable> {
Ok(Descend(true))
}
#[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))
}
#[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))
}
#[inline(always)]
fn visit_empty_record(_var: Variable) -> Result<(), DerivableError> {
fn visit_empty_record(_var: Variable) -> Result<(), NotDerivable> {
Ok(())
}
#[inline(always)]
fn visit_empty_tag_union(_var: Variable) -> Result<(), DerivableError> {
fn visit_empty_tag_union(_var: Variable) -> Result<(), NotDerivable> {
Ok(())
}
#[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) {
Ok(Descend(false))
} else {
@ -792,7 +888,7 @@ impl DerivableVisitor for DeriveDecoding {
}
#[inline(always)]
fn visit_ranged_number(_var: Variable, _range: NumericRange) -> Result<(), DerivableError> {
fn visit_ranged_number(_var: Variable, _range: NumericRange) -> Result<(), NotDerivable> {
Ok(())
}
}

View file

@ -1,5 +1,5 @@
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_region::all::Region;
@ -55,7 +55,21 @@ pub enum Unfulfilled {
pub enum UnderivableReason {
NotABuiltin,
/// The surface type is not derivable
SurfaceNotDerivable,
SurfaceNotDerivable(NotDerivableContext),
/// 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 {
matches!(self, RecordField::Optional(..))
matches!(
self,
RecordField::Optional(..) | RecordField::RigidOptional(..)
)
}
}

View file

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

View file

@ -972,7 +972,7 @@ pub fn can_problem<'b>(
alloc.symbol_unqualified(original_opaque),
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();
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 roc_can::expected::{Expected, PExpected};
use roc_collections::all::{HumanIndex, MutSet, SendMap};
use roc_error_macros::internal_error;
use roc_exhaustive::CtorName;
use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::{Ident, IdentStr, Lowercase, TagName};
use roc_module::symbol::Symbol;
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_types::pretty_print::{Parens, WILDCARD};
use roc_types::types::{
@ -334,9 +337,11 @@ fn report_underivable_reason<'a>(
UnderivableReason::NotABuiltin => {
Some(alloc.reflow("Only builtin abilities can have generated implementations!"))
}
UnderivableReason::SurfaceNotDerivable => underivable_hint(alloc, ability, typ),
UnderivableReason::NestedNotDerivable(nested_typ) => {
let hint = underivable_hint(alloc, ability, &nested_typ);
UnderivableReason::SurfaceNotDerivable(context) => {
underivable_hint(alloc, ability, context, typ)
}
UnderivableReason::NestedNotDerivable(nested_typ, context) => {
let hint = underivable_hint(alloc, ability, context, &nested_typ);
let reason = alloc.stack(
[
alloc.reflow("In particular, an implementation for"),
@ -354,59 +359,80 @@ fn report_underivable_reason<'a>(
fn underivable_hint<'b>(
alloc: &'b RocDocAllocator<'b>,
ability: Symbol,
context: NotDerivableContext,
typ: &ErrorType,
) -> Option<RocDocBuilder<'b>> {
match typ {
ErrorType::Function(..) => Some(alloc.note("").append(alloc.concat([
match context {
NotDerivableContext::NoContext => None,
NotDerivableContext::Function => Some(alloc.note("").append(alloc.concat([
alloc.symbol_unqualified(ability),
alloc.reflow(" cannot be generated for functions."),
]))),
ErrorType::FlexVar(v) | ErrorType::RigidVar(v) => Some(alloc.tip().append(alloc.concat([
alloc.reflow("This type variable is not bound to "),
NotDerivableContext::Opaque(symbol) => Some(alloc.tip().append(alloc.concat([
alloc.symbol_unqualified(symbol),
alloc.reflow(" does not implement "),
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),
])),
alloc.reflow("."),
if symbol.module_id() == alloc.home {
alloc.concat([
alloc.reflow(" Consider adding a custom implementation"),
if ability.is_builtin() {
alloc.concat([
alloc.reflow(" or "),
alloc.inline_type_block(alloc.concat([
alloc.keyword("has"),
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([
alloc.symbol_unqualified(*symbol),
alloc.reflow(" does not implement "),
alloc.reflow("This type variable is not bound to "),
alloc.symbol_unqualified(ability),
alloc.reflow("."),
if symbol.module_id() == alloc.home {
alloc.concat([
alloc.reflow(" Consider adding a custom implementation"),
if ability.is_builtin() {
alloc.concat([
alloc.reflow(" or "),
alloc.inline_type_block(alloc.concat([
alloc.keyword("has"),
alloc.space(),
alloc.symbol_qualified(ability),
])),
alloc.reflow(" to the definition of "),
alloc.symbol_unqualified(*symbol),
])
} else {
alloc.nil()
},
alloc.reflow("."),
])
} else {
alloc.nil()
},
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`.
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
@ -8537,7 +8537,6 @@ All branches in an `if` must have the same type!
);
test_report!(
#[ignore = "TODO does not error yet"]
ability_specialization_is_duplicated_with_type_mismatch,
indoc!(
r#"
@ -8553,6 +8552,18 @@ All branches in an `if` must have the same type!
"#
),
@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!(
#[ignore = "needs structural deriving to be turned on first"]
nested_opaque_cannot_derive_encoding,
indoc!(
r#"
app "test" imports [Decode.{Decoder, DecoderFormatting, decoder}] provides [main] to "./platform"
A : {}
A := {}
main =
myDecoder : Decoder {x : A} fmt | fmt has DecoderFormatting
myDecoder = decoder
@ -10311,6 +10322,26 @@ All branches in an `if` must have the same type!
"#
),
@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.
"###
);
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`?
"###
);
}