mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 12:18:19 +00:00
Merge pull request #3711 from roc-lang/record-decoding
Record decoding and their derivers
This commit is contained in:
commit
0ba5b3cfc6
27 changed files with 1542 additions and 119 deletions
|
@ -16,7 +16,7 @@ roc_builtins = { path = "../builtins" }
|
|||
roc_load_internal = { path = "../load_internal" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_derive_key = { path = "../derive_key" }
|
||||
roc_derive = { path = "../derive", features = ["debug-derived-symbols"] }
|
||||
roc_derive = { path = "../derive", features = ["debug-derived-symbols", "open-extension-vars"] }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
|
|
|
@ -5,14 +5,48 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::{
|
||||
util::{check_immediate, derive_test},
|
||||
test_key_eq, test_key_neq,
|
||||
util::{check_immediate, check_underivable, derive_test},
|
||||
v,
|
||||
};
|
||||
use insta::assert_snapshot;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_types::subs::Variable;
|
||||
|
||||
use roc_derive_key::DeriveBuiltin::Decoder;
|
||||
use roc_derive_key::{DeriveBuiltin::Decoder, DeriveError};
|
||||
|
||||
test_key_eq! {
|
||||
Decoder,
|
||||
|
||||
same_record:
|
||||
v!({ a: v!(U8), }), v!({ a: v!(U8), })
|
||||
same_record_fields_diff_types:
|
||||
v!({ a: v!(U8), }), v!({ a: v!(STR), })
|
||||
same_record_fields_any_order:
|
||||
v!({ a: v!(U8), b: v!(U8), c: v!(U8), }),
|
||||
v!({ c: v!(U8), a: v!(U8), b: v!(U8), })
|
||||
explicit_empty_record_and_implicit_empty_record:
|
||||
v!(EMPTY_RECORD), v!({})
|
||||
|
||||
list_list_diff_types:
|
||||
v!(Symbol::LIST_LIST v!(STR)), v!(Symbol::LIST_LIST v!(U8))
|
||||
str_str:
|
||||
v!(Symbol::STR_STR), v!(Symbol::STR_STR)
|
||||
}
|
||||
|
||||
test_key_neq! {
|
||||
Decoder,
|
||||
|
||||
different_record_fields:
|
||||
v!({ a: v!(U8), }), v!({ b: v!(U8), })
|
||||
record_empty_vs_nonempty:
|
||||
v!(EMPTY_RECORD), v!({ a: v!(U8), })
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optional_record_field_derive_error() {
|
||||
check_underivable(Decoder, v!({ ?a: v!(U8), }), DeriveError::Underivable);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn immediates() {
|
||||
|
@ -49,3 +83,66 @@ fn list() {
|
|||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_2_fields() {
|
||||
derive_test(Decoder, v!({first: v!(STR), second: v!(STR),}), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for { first : Str, second : Str }
|
||||
# Decoder { first : val, second : val1 } fmt | fmt has DecoderFormatting, val has Decoding, val1 has Decoding
|
||||
# List U8, fmt -[[custom(22)]]-> { rest : List U8, result : [Err [TooShort], Ok { first : val, second : val1 }] } | fmt has DecoderFormatting, val has Decoding, val1 has Decoding
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[custom(22)]]
|
||||
#Derived.decoder_{first,second} =
|
||||
Decode.custom
|
||||
\#Derived.bytes3, #Derived.fmt3 ->
|
||||
Decode.decodeWith
|
||||
#Derived.bytes3
|
||||
(Decode.record
|
||||
{ second: Err NoField, first: Err NoField }
|
||||
\#Derived.stateRecord2, #Derived.field ->
|
||||
when #Derived.field is
|
||||
"first" ->
|
||||
Keep (Decode.custom
|
||||
\#Derived.bytes, #Derived.fmt ->
|
||||
when Decode.decodeWith
|
||||
#Derived.bytes
|
||||
Decode.decoder
|
||||
#Derived.fmt is
|
||||
#Derived.rec ->
|
||||
{
|
||||
result: when #Derived.rec.result is
|
||||
Ok #Derived.val ->
|
||||
Ok { stateRecord2 & first: Ok #Derived.val }
|
||||
Err #Derived.err -> Err #Derived.err,
|
||||
rest: #Derived.rec.rest
|
||||
})
|
||||
"second" ->
|
||||
Keep (Decode.custom
|
||||
\#Derived.bytes2, #Derived.fmt2 ->
|
||||
when Decode.decodeWith
|
||||
#Derived.bytes2
|
||||
Decode.decoder
|
||||
#Derived.fmt2 is
|
||||
#Derived.rec2 ->
|
||||
{
|
||||
result: when #Derived.rec2.result is
|
||||
Ok #Derived.val2 ->
|
||||
Ok { stateRecord2 & second: Ok #Derived.val2 }
|
||||
Err #Derived.err2 -> Err #Derived.err2,
|
||||
rest: #Derived.rec2.rest
|
||||
})
|
||||
_ -> Skip
|
||||
\#Derived.stateRecord ->
|
||||
when #Derived.stateRecord.first is
|
||||
Ok #Derived.first ->
|
||||
when #Derived.stateRecord.second is
|
||||
Ok #Derived.second ->
|
||||
Ok { second: #Derived.second, first: #Derived.first }
|
||||
_ -> Err TooShort
|
||||
_ -> Err TooShort)
|
||||
#Derived.fmt3
|
||||
"###
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
use insta::assert_snapshot;
|
||||
|
||||
use crate::{
|
||||
test_hash_eq, test_hash_neq,
|
||||
test_key_eq, test_key_neq,
|
||||
util::{check_immediate, derive_test},
|
||||
v,
|
||||
};
|
||||
|
@ -17,7 +17,7 @@ use roc_types::subs::Variable;
|
|||
|
||||
// {{{ hash tests
|
||||
|
||||
test_hash_eq! {
|
||||
test_key_eq! {
|
||||
ToEncoder,
|
||||
|
||||
same_record:
|
||||
|
@ -70,7 +70,7 @@ test_hash_eq! {
|
|||
v!(@Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::UNDERSCORE => v!([False, True]))
|
||||
}
|
||||
|
||||
test_hash_neq! {
|
||||
test_key_neq! {
|
||||
ToEncoder,
|
||||
|
||||
different_record_fields:
|
||||
|
@ -174,10 +174,7 @@ fn one_field_record() {
|
|||
\#Derived.bytes, #Derived.fmt ->
|
||||
Encode.appendWith
|
||||
#Derived.bytes
|
||||
(Encode.record
|
||||
[
|
||||
{ value: Encode.toEncoder #Derived.rcd.a, key: "a", },
|
||||
])
|
||||
(Encode.record [{ value: Encode.toEncoder #Derived.rcd.a, key: "a" }])
|
||||
#Derived.fmt
|
||||
"###
|
||||
)
|
||||
|
@ -202,8 +199,8 @@ fn two_field_record() {
|
|||
#Derived.bytes
|
||||
(Encode.record
|
||||
[
|
||||
{ value: Encode.toEncoder #Derived.rcd.a, key: "a", },
|
||||
{ value: Encode.toEncoder #Derived.rcd.b, key: "b", },
|
||||
{ value: Encode.toEncoder #Derived.rcd.a, key: "a" },
|
||||
{ value: Encode.toEncoder #Derived.rcd.b, key: "b" },
|
||||
])
|
||||
#Derived.fmt
|
||||
"###
|
||||
|
|
|
@ -19,7 +19,10 @@ pub fn pretty_print_def(c: &Ctx, d: &Def) -> String {
|
|||
|
||||
macro_rules! maybe_paren {
|
||||
($paren_if_above:expr, $my_prec:expr, $doc:expr) => {
|
||||
if $my_prec > $paren_if_above {
|
||||
maybe_paren!($paren_if_above, $my_prec, || true, $doc)
|
||||
};
|
||||
($paren_if_above:expr, $my_prec:expr, $extra_cond:expr, $doc:expr) => {
|
||||
if $my_prec > $paren_if_above && $extra_cond() {
|
||||
$doc.parens().group()
|
||||
} else {
|
||||
$doc
|
||||
|
@ -47,7 +50,7 @@ fn def<'a>(c: &Ctx, f: &'a Arena<'a>, d: &'a Def) -> DocBuilder<'a, Arena<'a>> {
|
|||
#[derive(PartialEq, PartialOrd)]
|
||||
enum EPrec {
|
||||
Free,
|
||||
CallArg,
|
||||
AppArg,
|
||||
}
|
||||
|
||||
fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, Arena<'a>> {
|
||||
|
@ -137,11 +140,11 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
|
|||
maybe_paren!(
|
||||
Free,
|
||||
p,
|
||||
expr(c, CallArg, f, &fun.value)
|
||||
expr(c, AppArg, f, &fun.value)
|
||||
.append(
|
||||
f.concat(args.iter().map(|le| f.line().append(expr(
|
||||
c,
|
||||
CallArg,
|
||||
AppArg,
|
||||
f,
|
||||
&le.1.value
|
||||
))))
|
||||
|
@ -175,15 +178,18 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
|
|||
Record { fields, .. } => f
|
||||
.reflow("{")
|
||||
.append(
|
||||
f.concat(fields.iter().map(|(name, field)| {
|
||||
let field = f
|
||||
.text(name.as_str())
|
||||
.append(f.reflow(": "))
|
||||
.append(expr(c, Free, f, &field.loc_expr.value))
|
||||
.nest(2)
|
||||
.group();
|
||||
f.line().append(field).append(",")
|
||||
}))
|
||||
f.intersperse(
|
||||
fields.iter().map(|(name, field)| {
|
||||
let field = f
|
||||
.text(name.as_str())
|
||||
.append(f.reflow(": "))
|
||||
.append(expr(c, Free, f, &field.loc_expr.value))
|
||||
.nest(2)
|
||||
.group();
|
||||
f.line().append(field)
|
||||
}),
|
||||
f.reflow(","),
|
||||
)
|
||||
.nest(2)
|
||||
.group(),
|
||||
)
|
||||
|
@ -193,15 +199,61 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
|
|||
EmptyRecord => f.text("{}"),
|
||||
Access {
|
||||
loc_expr, field, ..
|
||||
} => expr(c, CallArg, f, &loc_expr.value)
|
||||
} => expr(c, AppArg, f, &loc_expr.value)
|
||||
.append(f.text(format!(".{}", field.as_str())))
|
||||
.group(),
|
||||
OpaqueWrapFunction(OpaqueWrapFunctionData { opaque_name, .. }) => {
|
||||
f.text(format!("@{}", opaque_name.as_str(c.interns)))
|
||||
}
|
||||
Accessor(_) => todo!(),
|
||||
Update { .. } => todo!(),
|
||||
Tag { .. } => todo!(),
|
||||
Update {
|
||||
symbol, updates, ..
|
||||
} => f
|
||||
.reflow("{")
|
||||
.append(f.line())
|
||||
.append(f.text(symbol.as_str(c.interns).to_string()))
|
||||
.append(f.reflow(" &"))
|
||||
.append(
|
||||
f.intersperse(
|
||||
updates.iter().map(|(name, field)| {
|
||||
let field = f
|
||||
.text(name.as_str())
|
||||
.append(f.reflow(": "))
|
||||
.append(expr(c, Free, f, &field.loc_expr.value))
|
||||
.nest(2)
|
||||
.group();
|
||||
f.line().append(field)
|
||||
}),
|
||||
f.reflow(","),
|
||||
)
|
||||
.nest(2)
|
||||
.group(),
|
||||
)
|
||||
.append(f.line())
|
||||
.append(f.text("}"))
|
||||
.group(),
|
||||
Tag {
|
||||
name, arguments, ..
|
||||
} => maybe_paren!(
|
||||
Free,
|
||||
p,
|
||||
|| !arguments.is_empty(),
|
||||
f.text(name.0.as_str())
|
||||
.append(if arguments.is_empty() {
|
||||
f.nil()
|
||||
} else {
|
||||
f.space()
|
||||
})
|
||||
.append(
|
||||
f.intersperse(
|
||||
arguments
|
||||
.iter()
|
||||
.map(|(_, le)| expr(c, AppArg, f, &le.value)),
|
||||
f.space(),
|
||||
)
|
||||
)
|
||||
.group()
|
||||
),
|
||||
ZeroArgumentTag { .. } => todo!(),
|
||||
OpaqueRef { .. } => todo!(),
|
||||
Expect { .. } => todo!(),
|
||||
|
|
|
@ -18,7 +18,7 @@ use roc_collections::VecSet;
|
|||
use roc_constrain::expr::constrain_decls;
|
||||
use roc_debug_flags::dbg_do;
|
||||
use roc_derive::DerivedModule;
|
||||
use roc_derive_key::{DeriveBuiltin, DeriveKey, Derived};
|
||||
use roc_derive_key::{DeriveBuiltin, DeriveError, DeriveKey, Derived};
|
||||
use roc_load_internal::file::{add_imports, default_aliases, LoadedModule, Threading};
|
||||
use roc_module::symbol::{IdentIds, Interns, ModuleId, Symbol};
|
||||
use roc_region::all::LineInfo;
|
||||
|
@ -53,7 +53,7 @@ fn module_source_and_path(builtin: DeriveBuiltin) -> (ModuleId, &'static str, Pa
|
|||
}
|
||||
}
|
||||
|
||||
/// DSL for creating [`Content`][crate::subs::Content].
|
||||
/// DSL for creating [`Content`][roc_types::subs::Content].
|
||||
#[macro_export]
|
||||
macro_rules! v {
|
||||
({ $($field:ident: $make_v:expr,)* $(?$opt_field:ident : $make_opt_v:expr,)* }) => {{
|
||||
|
@ -65,7 +65,7 @@ macro_rules! v {
|
|||
$(let $opt_field = $make_opt_v(subs);)*
|
||||
let fields = vec![
|
||||
$( (stringify!($field).into(), RecordField::Required($field)) ,)*
|
||||
$( (stringify!($opt_field).into(), RecordField::Required($opt_field)) ,)*
|
||||
$( (stringify!($opt_field).into(), RecordField::Optional($opt_field)) ,)*
|
||||
];
|
||||
let fields = RecordFields::insert_into_subs(subs, fields);
|
||||
roc_derive::synth_var(subs, Content::Structure(FlatType::Record(fields, Variable::EMPTY_RECORD)))
|
||||
|
@ -178,7 +178,7 @@ where
|
|||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! test_hash_eq {
|
||||
macro_rules! test_key_eq {
|
||||
($builtin:expr, $($name:ident: $synth1:expr, $synth2:expr)*) => {$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
|
@ -188,7 +188,7 @@ macro_rules! test_hash_eq {
|
|||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! test_hash_neq {
|
||||
macro_rules! test_key_neq {
|
||||
($builtin:expr, $($name:ident: $synth1:expr, $synth2:expr)*) => {$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
|
@ -197,6 +197,18 @@ macro_rules! test_hash_neq {
|
|||
)*};
|
||||
}
|
||||
|
||||
pub(crate) fn check_underivable<Sy>(builtin: DeriveBuiltin, synth: Sy, err: DeriveError)
|
||||
where
|
||||
Sy: FnOnce(&mut Subs) -> Variable,
|
||||
{
|
||||
let mut subs = Subs::new();
|
||||
let var = synth(&mut subs);
|
||||
|
||||
let key = Derived::builtin(builtin, &subs, var);
|
||||
|
||||
assert_eq!(key, Err(err));
|
||||
}
|
||||
|
||||
pub(crate) fn check_immediate<S>(builtin: DeriveBuiltin, synth: S, immediate: Symbol)
|
||||
where
|
||||
S: FnOnce(&mut Subs) -> Variable,
|
||||
|
@ -324,7 +336,7 @@ fn check_derived_typechecks_and_golden(
|
|||
// run the solver, print and fail if we have errors
|
||||
dbg_do!(
|
||||
roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED,
|
||||
std::env::set_var(roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED, "1")
|
||||
std::env::set_var(roc_debug_flags::ROC_PRINT_UNIFICATIONS, "1")
|
||||
);
|
||||
let (mut solved_subs, _, problems, _) = roc_solve::module::run_solve(
|
||||
test_module,
|
||||
|
@ -338,6 +350,10 @@ fn check_derived_typechecks_and_golden(
|
|||
&exposed_for_module.exposed_by_module,
|
||||
Default::default(),
|
||||
);
|
||||
dbg_do!(
|
||||
roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED,
|
||||
std::env::set_var(roc_debug_flags::ROC_PRINT_UNIFICATIONS, "0")
|
||||
);
|
||||
let subs = solved_subs.inner_mut();
|
||||
|
||||
if !problems.is_empty() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue