Merge pull request #3695 from rtfeldman/derive-decoding-list

Derive decoding for lists!
This commit is contained in:
Ayaz 2022-08-05 10:41:24 -05:00 committed by GitHub
commit d4e81ccbd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 516 additions and 102 deletions

View File

@ -0,0 +1,258 @@
//! Derivers for the `Decoding` ability.
use roc_can::expr::{AnnotatedMark, ClosureData, Expr, Recursive};
use roc_can::pattern::Pattern;
use roc_derive_key::decoding::FlatDecodableKey;
use roc_error_macros::internal_error;
use roc_module::called_via::CalledVia;
use roc_module::symbol::Symbol;
use roc_region::all::Loc;
use roc_types::subs::{
Content, FlatType, GetSubsSlice, LambdaSet, OptVariable, SubsSlice, UnionLambdas, Variable,
};
use roc_types::types::AliasKind;
use crate::util::Env;
use crate::{synth_var, DerivedBody};
pub(crate) fn derive_decoder(
env: &mut Env<'_>,
key: FlatDecodableKey,
def_symbol: Symbol,
) -> DerivedBody {
let (body, body_type) = match key {
FlatDecodableKey::List() => decoder_list(env, def_symbol),
};
let specialization_lambda_sets =
env.get_specialization_lambda_sets(body_type, Symbol::DECODE_DECODER);
DerivedBody {
body,
body_type,
specialization_lambda_sets,
}
}
fn decoder_list(env: &mut Env<'_>, _def_symbol: Symbol) -> (Expr, Variable) {
// Build
//
// def_symbol : Decoder (List elem) fmt | elem has Decoding, fmt has DecoderFormatting
// def_symbol = Decode.custom \bytes, fmt -> Decode.decodeWith bytes (Decode.list Decode.decoder) fmt
//
// TODO try to reduce to `Decode.list Decode.decoder`
use Expr::*;
// Decode.list Decode.decoder : Decoder (List elem) fmt
let (decode_list_call, this_decode_list_ret_var) = {
// List elem
let elem_var = env.subs.fresh_unnamed_flex_var();
// Decode.decoder : Decoder elem fmt | elem has Decoding, fmt has EncoderFormatting
let (elem_decoder, elem_decoder_var) = {
// build `Decode.decoder : Decoder elem fmt` type
// Decoder val fmt | val has Decoding, fmt has EncoderFormatting
let elem_decoder_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODER);
// set val ~ elem
let val_var = match env.subs.get_content_without_compacting(elem_decoder_var) {
Content::Alias(Symbol::DECODE_DECODER_OPAQUE, vars, _, AliasKind::Opaque)
if vars.type_variables_len == 2 =>
{
env.subs.get_subs_slice(vars.type_variables())[0]
}
_ => internal_error!("Decode.decode not an opaque type"),
};
env.unify(val_var, elem_var);
(
AbilityMember(Symbol::DECODE_DECODER, None, elem_decoder_var),
elem_decoder_var,
)
};
// Build `Decode.list Decode.decoder` type
// Decoder val fmt -[uls]-> Decoder (List val) fmt | fmt has DecoderFormatting
let decode_list_fn_var = env.import_builtin_symbol_var(Symbol::DECODE_LIST);
// Decoder elem fmt -a-> b
let elem_decoder_var_slice = SubsSlice::insert_into_subs(env.subs, [elem_decoder_var]);
let this_decode_list_clos_var = env.subs.fresh_unnamed_flex_var();
let this_decode_list_ret_var = env.subs.fresh_unnamed_flex_var();
let this_decode_list_fn_var = synth_var(
env.subs,
Content::Structure(FlatType::Func(
elem_decoder_var_slice,
this_decode_list_clos_var,
this_decode_list_ret_var,
)),
);
// Decoder val fmt -[uls]-> Decoder (List val) fmt | fmt has DecoderFormatting
// ~ Decoder elem fmt -a -> b
env.unify(decode_list_fn_var, this_decode_list_fn_var);
let decode_list_member = AbilityMember(Symbol::DECODE_LIST, None, this_decode_list_fn_var);
let decode_list_fn = Box::new((
decode_list_fn_var,
Loc::at_zero(decode_list_member),
this_decode_list_clos_var,
this_decode_list_ret_var,
));
let decode_list_call = Call(
decode_list_fn,
vec![(elem_decoder_var, Loc::at_zero(elem_decoder))],
CalledVia::Space,
);
(decode_list_call, this_decode_list_ret_var)
};
let bytes_sym = env.new_symbol("bytes");
let bytes_var = env.subs.fresh_unnamed_flex_var();
let fmt_sym = env.new_symbol("fmt");
let fmt_var = env.subs.fresh_unnamed_flex_var();
// Decode.decodeWith bytes (Decode.list Decode.decoder) fmt : DecodeResult (List elem)
let (decode_with_call, decode_result_list_elem_var) = {
// Decode.decodeWith : List U8, Decoder val fmt, fmt -> DecodeResult val | fmt has DecoderFormatting
let decode_with_type = env.import_builtin_symbol_var(Symbol::DECODE_DECODE_WITH);
// Decode.decodeWith : bytes, Decoder (List elem) fmt, fmt -> DecoderResult (List val)
let this_decode_with_var_slice =
SubsSlice::insert_into_subs(env.subs, [bytes_var, this_decode_list_ret_var, fmt_var]);
let this_decode_with_clos_var = env.subs.fresh_unnamed_flex_var();
let this_decode_with_ret_var = env.subs.fresh_unnamed_flex_var();
let this_decode_with_fn_var = synth_var(
env.subs,
Content::Structure(FlatType::Func(
this_decode_with_var_slice,
this_decode_with_clos_var,
this_decode_with_ret_var,
)),
);
// List U8, Decoder val fmt, fmt -> DecodeResult val | fmt has DecoderFormatting
// ~ bytes, Decoder (List elem) fmt, fmt -> DecoderResult (List val)
env.unify(decode_with_type, this_decode_with_fn_var);
let decode_with_var = Var(Symbol::DECODE_DECODE_WITH);
let decode_with_fn = Box::new((
this_decode_with_fn_var,
Loc::at_zero(decode_with_var),
this_decode_with_clos_var,
this_decode_with_ret_var,
));
let decode_with_call = Call(
decode_with_fn,
vec![
// bytes (Decode.list Decode.decoder) fmt
(bytes_var, Loc::at_zero(Var(bytes_sym))),
(this_decode_list_ret_var, Loc::at_zero(decode_list_call)),
(fmt_var, Loc::at_zero(Var(fmt_sym))),
],
CalledVia::Space,
);
(decode_with_call, this_decode_with_ret_var)
};
// \bytes, fmt -> Decode.decodeWith bytes (Decode.list Decode.decoder) fmt
let (custom_lambda, custom_var) = {
let fn_name = env.new_symbol("custom");
// Create fn_var for ambient capture; we fix it up below.
let fn_var = synth_var(env.subs, Content::Error);
// -[[fn_name]]->
let fn_name_labels = UnionLambdas::insert_into_subs(env.subs, [(fn_name, vec![])]);
let fn_clos_var = synth_var(
env.subs,
Content::LambdaSet(LambdaSet {
solved: fn_name_labels,
recursion_var: OptVariable::NONE,
unspecialized: SubsSlice::default(),
ambient_function: fn_var,
}),
);
// bytes, fmt -[[fn_name]]-> DecoderResult (List elem)
let args_slice = SubsSlice::insert_into_subs(env.subs, vec![bytes_var, fmt_var]);
env.subs.set_content(
fn_var,
Content::Structure(FlatType::Func(
args_slice,
fn_clos_var,
decode_result_list_elem_var,
)),
);
// \bytes, fmt -[[fn_name]]-> Decode.decodeWith bytes (Decode.list Decode.decoder) fmt
let clos = Closure(ClosureData {
function_type: fn_var,
closure_type: fn_clos_var,
return_type: decode_result_list_elem_var,
name: fn_name,
captured_symbols: vec![],
recursive: Recursive::NotRecursive,
arguments: vec![
(
bytes_var,
AnnotatedMark::known_exhaustive(),
Loc::at_zero(Pattern::Identifier(bytes_sym)),
),
(
fmt_var,
AnnotatedMark::known_exhaustive(),
Loc::at_zero(Pattern::Identifier(fmt_sym)),
),
],
loc_body: Box::new(Loc::at_zero(decode_with_call)),
});
(clos, fn_var)
};
// Decode.custom \bytes, fmt -> Decode.decodeWith bytes (Decode.list Decode.decoder) fmt
let (decode_custom_call, decoder_var) = {
// (List U8, fmt -> DecodeResult val) -> Decoder val fmt | fmt has DecoderFormatting
let decode_custom_type = env.import_builtin_symbol_var(Symbol::DECODE_CUSTOM);
// (List U8, fmt -> DecodeResult (List elem)) -> Decoder (List elem) fmt
let this_decode_custom_args = SubsSlice::insert_into_subs(env.subs, [custom_var]);
let this_decode_custom_clos_var = env.subs.fresh_unnamed_flex_var();
let this_decode_custom_ret_var = env.subs.fresh_unnamed_flex_var();
let this_decode_custom_fn_var = synth_var(
env.subs,
Content::Structure(FlatType::Func(
this_decode_custom_args,
this_decode_custom_clos_var,
this_decode_custom_ret_var,
)),
);
// (List U8, fmt -> DecodeResult val) -> Decoder val fmt | fmt has DecoderFormatting
// ~ (List U8, fmt -> DecodeResult (List elem)) -> Decoder (List elem) fmt
env.unify(decode_custom_type, this_decode_custom_fn_var);
let decode_custom_var = Var(Symbol::DECODE_CUSTOM);
let decode_custom_fn = Box::new((
this_decode_custom_fn_var,
Loc::at_zero(decode_custom_var),
this_decode_custom_clos_var,
this_decode_custom_ret_var,
));
let decode_custom_call = Call(
decode_custom_fn,
vec![(custom_var, Loc::at_zero(custom_lambda))],
CalledVia::Space,
);
(decode_custom_call, this_decode_custom_ret_var)
};
(decode_custom_call, decoder_var)
}

View File

@ -16,6 +16,7 @@ use roc_types::subs::{
};
use util::Env;
mod decoding;
mod encoding;
mod util;
@ -59,20 +60,23 @@ fn build_derived_body(
derived_symbol: Symbol,
derive_key: DeriveKey,
) -> (Def, SpecializationLambdaSets) {
let mut env = Env {
subs: derived_subs,
exposed_types: exposed_by_module,
derived_ident_ids,
};
let DerivedBody {
body,
body_type,
specialization_lambda_sets,
} = match derive_key {
DeriveKey::ToEncoder(to_encoder_key) => {
let mut env = Env {
subs: derived_subs,
exposed_types: exposed_by_module,
derived_ident_ids,
};
encoding::derive_to_encoder(&mut env, to_encoder_key, derived_symbol)
}
DeriveKey::Decoder(_decoder_key) => todo!(),
DeriveKey::Decoder(decoder_key) => {
decoding::derive_decoder(&mut env, decoder_key, derived_symbol)
}
};
let def = Def {
@ -177,18 +181,18 @@ impl DerivedModule {
&mut self,
gen_subs: &mut Subs,
should_load_def: impl Fn(Symbol) -> bool,
) -> VecMap<Symbol, Expr> {
) -> VecMap<Symbol, (Expr, Variable)> {
self.map
.values()
.filter_map(|(symbol, def, _)| {
if should_load_def(*symbol) {
let (_new_expr_var, new_expr) = roc_can::copy::deep_copy_expr_across_subs(
let (new_expr_var, new_expr) = roc_can::copy::deep_copy_expr_across_subs(
&mut self.subs,
gen_subs,
def.expr_var,
&def.loc_expr.value,
);
Some((*symbol, new_expr))
Some((*symbol, (new_expr, new_expr_var)))
} else {
None
}

View File

@ -5116,7 +5116,7 @@ fn load_derived_partial_procs<'a>(
// TODO: we can be even lazier here if we move `add_def_to_module` to happen in mono. Also, the
// timings would be more accurate.
for (derived_symbol, derived_expr) in derives_to_add.into_iter() {
for (derived_symbol, (derived_expr, derived_expr_var)) in derives_to_add.into_iter() {
let mut mono_env = roc_mono::ir::Env {
arena,
subs,
@ -5155,7 +5155,22 @@ fn load_derived_partial_procs<'a>(
return_type,
)
}
_ => internal_error!("Expected only functions to be derived"),
_ => {
// mark this symbols as a top-level thunk before any other work on the procs
new_module_thunks.push(derived_symbol);
PartialProc {
annotation: derived_expr_var,
// This is a 0-arity thunk, so it has no arguments.
pattern_symbols: &[],
// This is a top-level definition, so it cannot capture anything
captured_symbols: CapturedSymbols::None,
body: derived_expr,
body_var: derived_expr_var,
// This is a 0-arity thunk, so it cannot be recursive
is_self_recursive: false,
}
}
};
procs_base

View File

@ -5238,18 +5238,26 @@ fn late_resolve_ability_specialization<'a>(
Resolved::Specialization(symbol) => symbol,
Resolved::NeedsGenerated(var) => {
let derive_key = roc_derive_key::Derived::builtin(
roc_derive_key::DeriveBuiltin::Decoder,
member.try_into().expect("derived symbols must be builtins"),
env.subs,
var,
)
.expect("not a builtin");
.expect("specialization var not derivable!");
match derive_key {
roc_derive_key::Derived::Immediate(imm) => {
// The immediate is an ability member itself, so it must be resolved!
late_resolve_ability_specialization(env, imm, None, specialization_var)
}
roc_derive_key::Derived::Key(_) => {
todo_abilities!("support derived specializations that aren't immediates")
roc_derive_key::Derived::Key(derive_key) => {
let mut derived_module = env
.derived_module
.lock()
.expect("derived module unavailable");
derived_module
.get_or_insert(env.exposed_by_module, derive_key)
.0
}
}
}

View File

@ -4,9 +4,13 @@
// For the `v!` macro we use uppercase variables when constructing tag unions.
#![allow(non_snake_case)]
use crate::{util::check_immediate, v};
use crate::{
util::{check_immediate, derive_test},
v,
};
use insta::assert_snapshot;
use roc_module::symbol::Symbol;
use roc_types::subs::{Subs, Variable};
use roc_types::subs::Variable;
use roc_derive_key::DeriveBuiltin::Decoder;
@ -27,3 +31,21 @@ fn immediates() {
check_immediate(Decoder, v!(F64), Symbol::DECODE_F64);
check_immediate(Decoder, v!(STR), Symbol::DECODE_STRING);
}
#[test]
fn list() {
derive_test(Decoder, v!(Symbol::LIST_LIST v!(STR)), |golden| {
assert_snapshot!(golden, @r###"
# derived for List Str
# Decoder (List val) fmt | fmt has DecoderFormatting, val has Decoding
# List U8, fmt -[[custom(3)]]-> { rest : List U8, result : [Err [TooShort], Ok (List val)] } | fmt has DecoderFormatting, val has Decoding
# Specialization lambda sets:
# @<1>: [[custom(3)]]
#Derived.decoder_list =
Decode.custom
\#Derived.bytes, #Derived.fmt ->
Decode.decodeWith #Derived.bytes (Decode.list Decode.decoder) #Derived.fmt
"###
)
})
}

View File

@ -11,16 +11,9 @@ use crate::{
util::{check_immediate, derive_test},
v,
};
use roc_derive::synth_var;
use roc_derive_key::DeriveBuiltin::ToEncoder;
use roc_module::{ident::TagName, symbol::Symbol};
use roc_types::{
subs::{
AliasVariables, Content, FlatType, RecordFields, Subs, SubsIndex, SubsSlice, UnionTags,
Variable,
},
types::{AliasKind, RecordField},
};
use roc_module::symbol::Symbol;
use roc_types::subs::Variable;
// {{{ hash tests
@ -50,9 +43,9 @@ test_hash_eq! {
v!(EMPTY_TAG_UNION), v!([])
same_recursive_tag_union:
v!([ Nil, Cons v!(*lst)] as lst), v!([ Nil, Cons v!(*lst)] as lst)
v!([ Nil, Cons v!(^lst)] as lst), v!([ Nil, Cons v!(^lst)] as lst)
same_tag_union_and_recursive_tag_union_fields:
v!([ Nil, Cons v!(STR)]), v!([ Nil, Cons v!(*lst)] as lst)
v!([ Nil, Cons v!(STR)]), v!([ Nil, Cons v!(^lst)] as lst)
list_list_diff_types:
v!(Symbol::LIST_LIST v!(STR)), v!(Symbol::LIST_LIST v!(U8))
@ -90,7 +83,7 @@ test_hash_neq! {
tag_union_empty_vs_nonempty:
v!(EMPTY_TAG_UNION), v!([ B v!(U8) ])
different_recursive_tag_union_tags:
v!([ Nil, Cons v!(*lst) ] as lst), v!([ Nil, Next v!(*lst) ] as lst)
v!([ Nil, Cons v!(^lst) ] as lst), v!([ Nil, Next v!(^lst) ] as lst)
same_alias_diff_real_type:
v!(Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::BOOL_BOOL => v!([ False, True, Maybe ]))
@ -324,7 +317,7 @@ fn tag_two_labels() {
fn recursive_tag_union() {
derive_test(
ToEncoder,
v!([Nil, Cons v!(U8) v!(*lst) ] as lst),
v!([Nil, Cons v!(U8) v!(^lst) ] as lst),
|golden| {
assert_snapshot!(golden, @r###"
# derived for [Cons U8 $rec, Nil] as $rec

View File

@ -53,82 +53,110 @@ fn module_source_and_path(builtin: DeriveBuiltin) -> (ModuleId, &'static str, Pa
}
}
/// Writing out the types into content is inconvenient, so we use a DSL for testing.
/// DSL for creating [`Content`][crate::subs::Content].
#[macro_export]
macro_rules! v {
({ $($field:ident: $make_v:expr,)* $(?$opt_field:ident : $make_opt_v:expr,)* }) => {
|subs: &mut Subs| {
$(let $field = $make_v(subs);)*
$(let $opt_field = $make_opt_v(subs);)*
let fields = vec![
$( (stringify!($field).into(), RecordField::Required($field)) ,)*
$( (stringify!($opt_field).into(), RecordField::Required($opt_field)) ,)*
];
let fields = RecordFields::insert_into_subs(subs, fields);
synth_var(subs, Content::Structure(FlatType::Record(fields, Variable::EMPTY_RECORD)))
}
};
([ $($tag:ident $($payload:expr)*),* ]) => {
|subs: &mut Subs| {
$(
let $tag = vec![ $( $payload(subs), )* ];
)*
let tags = UnionTags::insert_into_subs::<_, Vec<Variable>>(subs, vec![ $( (TagName(stringify!($tag).into()), $tag) ,)* ]);
synth_var(subs, Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)))
}
};
([ $($tag:ident $($payload:expr)*),* ] as $rec_var:ident) => {
|subs: &mut Subs| {
let $rec_var = subs.fresh_unnamed_flex_var();
let rec_name_index =
SubsIndex::push_new(&mut subs.field_names, stringify!($rec).into());
({ $($field:ident: $make_v:expr,)* $(?$opt_field:ident : $make_opt_v:expr,)* }) => {{
#[allow(unused)]
use roc_types::types::RecordField;
use roc_types::subs::{Subs, RecordFields, Content, FlatType, Variable};
|subs: &mut Subs| {
$(let $field = $make_v(subs);)*
$(let $opt_field = $make_opt_v(subs);)*
let fields = vec![
$( (stringify!($field).into(), RecordField::Required($field)) ,)*
$( (stringify!($opt_field).into(), RecordField::Required($opt_field)) ,)*
];
let fields = RecordFields::insert_into_subs(subs, fields);
roc_derive::synth_var(subs, Content::Structure(FlatType::Record(fields, Variable::EMPTY_RECORD)))
}
}};
([ $($tag:ident $($payload:expr)*),* ]$( $ext:tt )?) => {{
#[allow(unused)]
use roc_types::subs::{Subs, UnionTags, Content, FlatType, Variable};
#[allow(unused)]
use roc_module::ident::TagName;
|subs: &mut Subs| {
$(
let $tag = vec![ $( $payload(subs), )* ];
)*
let tags = UnionTags::insert_into_subs::<_, Vec<Variable>>(subs, vec![ $( (TagName(stringify!($tag).into()), $tag) ,)* ]);
$(
let $tag = vec![ $( $payload(subs), )* ];
)*
let tags = UnionTags::insert_into_subs::<_, Vec<Variable>>(subs, vec![ $( (TagName(stringify!($tag).into()), $tag) ,)* ]);
let tag_union_var = synth_var(subs, Content::Structure(FlatType::RecursiveTagUnion($rec_var, tags, Variable::EMPTY_TAG_UNION)));
#[allow(unused_mut)]
let mut ext = Variable::EMPTY_TAG_UNION;
$( ext = $crate::v!($ext)(subs); )?
subs.set_content(
$rec_var,
Content::RecursionVar {
structure: tag_union_var,
opt_name: Some(rec_name_index),
},
);
tag_union_var
}
};
(Symbol::$sym:ident $($arg:expr)*) => {
|subs: &mut Subs| {
let $sym = vec![ $( $arg(subs) ,)* ];
let var_slice = SubsSlice::insert_into_subs(subs, $sym);
synth_var(subs, Content::Structure(FlatType::Apply(Symbol::$sym, var_slice)))
}
};
(Symbol::$alias:ident $($arg:expr)* => $real_var:expr) => {
|subs: &mut Subs| {
let args = vec![$( $arg(subs) )*];
let alias_variables = AliasVariables::insert_into_subs::<Vec<_>, Vec<_>>(subs, args, vec![]);
let real_var = $real_var(subs);
synth_var(subs, Content::Alias(Symbol::$alias, alias_variables, real_var, AliasKind::Structural))
}
};
(@Symbol::$alias:ident $($arg:expr)* => $real_var:expr) => {
|subs: &mut Subs| {
let args = vec![$( $arg(subs) )*];
let alias_variables = AliasVariables::insert_into_subs::<Vec<_>, Vec<_>>(subs, args, vec![]);
let real_var = $real_var(subs);
synth_var(subs, Content::Alias(Symbol::$alias, alias_variables, real_var, AliasKind::Opaque))
}
};
(*$rec_var:ident) => {
|_: &mut Subs| { $rec_var }
};
($var:ident) => {
|_: &mut Subs| { Variable::$var }
};
}
roc_derive::synth_var(subs, Content::Structure(FlatType::TagUnion(tags, ext)))
}
}};
([ $($tag:ident $($payload:expr)*),* ] as $rec_var:ident) => {{
use roc_types::subs::{Subs, SubsIndex, Variable, Content, FlatType, UnionTags};
use roc_module::ident::TagName;
|subs: &mut Subs| {
let $rec_var = subs.fresh_unnamed_flex_var();
let rec_name_index =
SubsIndex::push_new(&mut subs.field_names, stringify!($rec).into());
$(
let $tag = vec![ $( $payload(subs), )* ];
)*
let tags = UnionTags::insert_into_subs::<_, Vec<Variable>>(subs, vec![ $( (TagName(stringify!($tag).into()), $tag) ,)* ]);
let tag_union_var = roc_derive::synth_var(subs, Content::Structure(FlatType::RecursiveTagUnion($rec_var, tags, Variable::EMPTY_TAG_UNION)));
subs.set_content(
$rec_var,
Content::RecursionVar {
structure: tag_union_var,
opt_name: Some(rec_name_index),
},
);
tag_union_var
}
}};
(Symbol::$sym:ident $($arg:expr)*) => {{
use roc_types::subs::{Subs, SubsSlice, Content, FlatType};
use roc_module::symbol::Symbol;
|subs: &mut Subs| {
let $sym = vec![ $( $arg(subs) ,)* ];
let var_slice = SubsSlice::insert_into_subs(subs, $sym);
roc_derive::synth_var(subs, Content::Structure(FlatType::Apply(Symbol::$sym, var_slice)))
}
}};
(Symbol::$alias:ident $($arg:expr)* => $real_var:expr) => {{
use roc_types::subs::{Subs, AliasVariables, Content};
use roc_types::types::AliasKind;
use roc_module::symbol::Symbol;
|subs: &mut Subs| {
let args = vec![$( $arg(subs) )*];
let alias_variables = AliasVariables::insert_into_subs::<Vec<_>, Vec<_>>(subs, args, vec![]);
let real_var = $real_var(subs);
roc_derive::synth_var(subs, Content::Alias(Symbol::$alias, alias_variables, real_var, AliasKind::Structural))
}
}};
(@Symbol::$alias:ident $($arg:expr)* => $real_var:expr) => {{
use roc_types::subs::{Subs, AliasVariables, Content};
use roc_types::types::AliasKind;
use roc_module::symbol::Symbol;
|subs: &mut Subs| {
let args = vec![$( $arg(subs) )*];
let alias_variables = AliasVariables::insert_into_subs::<Vec<_>, Vec<_>>(subs, args, vec![]);
let real_var = $real_var(subs);
roc_derive::synth_var(subs, Content::Alias(Symbol::$alias, alias_variables, real_var, AliasKind::Opaque))
}
}};
(*) => {{
use roc_types::subs::{Subs, Content};
|subs: &mut Subs| { roc_derive::synth_var(subs, Content::FlexVar(None)) }
}};
(^$rec_var:ident) => {{
use roc_types::subs::{Subs};
|_: &mut Subs| { $rec_var }
}};
($var:ident) => {{
use roc_types::subs::{Subs};
|_: &mut Subs| { Variable::$var }
}};
}
pub(crate) fn check_key<S1, S2>(builtin: DeriveBuiltin, eq: bool, synth1: S1, synth2: S2)
where

View File

@ -689,6 +689,31 @@ fn encode_derived_list_of_records() {
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[ignore = "#3696: Currently hits some weird panic in borrow checking, not sure if it's directly related to abilities."]
fn encode_derived_list_of_lists_of_strings() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode.{ toEncoder }, Json]
provides [main] to "./platform"
main =
lst = [["a", "b"], ["c", "d", "e"], ["f"]]
encoded = Encode.toBytes lst Json.toUtf8
result = Str.fromUtf8 encoded
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(r#"[["a","b"],["c","d","e"],["f"]]"#),
RocStr
)
}
#[test]
#[cfg(all(
any(feature = "gen-llvm", feature = "gen-wasm"),
@ -872,3 +897,64 @@ mod decode_immediate {
)
}
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn decode_list_of_strings() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Decode, Json] provides [main] to "./platform"
main =
when Str.toUtf8 "[\"a\",\"b\",\"c\"]" |> Decode.fromBytes Json.fromUtf8 is
Ok l -> Str.joinWith l ","
_ -> "<bad>"
"#
),
RocStr::from("a,b,c"),
RocStr
)
}
#[test]
#[cfg(all(
any(feature = "gen-llvm"), // currently fails on gen-wasm
not(feature = "gen-llvm-wasm") // hits a stack limit in wasm3
))]
fn encode_then_decode_list_of_strings() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Encode, Decode, Json] provides [main] to "./platform"
main =
when Encode.toBytes ["a", "b", "c"] Json.fromUtf8 |> Decode.fromBytes Json.fromUtf8 is
Ok l -> Str.joinWith l ","
_ -> "something went wrong"
"#
),
RocStr::from("a,b,c"),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[ignore = "#3696: Currently hits some weird panic in borrow checking, not sure if it's directly related to abilities."]
fn encode_then_decode_list_of_lists_of_strings() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Encode, Decode, Json] provides [main] to "./platform"
main =
when Encode.toBytes [["a", "b"], ["c", "d", "e"], ["f"]] Json.fromUtf8 |> Decode.fromBytes Json.fromUtf8 is
Ok list -> (List.map list \inner -> Str.joinWith inner ",") |> Str.joinWith l ";"
_ -> "something went wrong"
"#
),
RocStr::from("a,b;c,d,e;f"),
RocStr
)
}