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

@ -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