Merge pull request #3711 from roc-lang/record-decoding

Record decoding and their derivers
This commit is contained in:
Richard Feldman 2022-08-15 22:06:34 -04:00 committed by GitHub
commit 0ba5b3cfc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 1542 additions and 119 deletions

View file

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

View file

@ -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
"###
)
})
}

View file

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

View file

@ -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!(),

View file

@ -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() {