Support deriving Decode for opaques

This commit is contained in:
Ayaz Hafiz 2022-10-18 15:06:10 -05:00
parent 61ba59de07
commit 1d885c4ab2
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
3 changed files with 115 additions and 2 deletions

View file

@ -7,7 +7,7 @@
//! implementation to the value they wrap.
use roc_error_macros::internal_error;
use roc_module::symbol::Symbol;
use roc_module::{called_via::CalledVia, symbol::Symbol};
use roc_parse::ast;
use roc_region::all::{Loc, Region};
@ -49,6 +49,73 @@ fn to_encoder<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
)
}
fn decoder<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
let alloc_expr = |it| env.arena.alloc(Loc::at(DERIVED_REGION, it));
let call_custom = {
let bytes = "#bytes";
let fmt = "#fmt";
// Decode.decodeWith bytes Decode.decoder fmt
let call_decode_with = ast::Expr::Apply(
alloc_expr(ast::Expr::Var {
module_name: "Decode",
ident: "decodeWith",
}),
env.arena.alloc([
&*alloc_expr(ast::Expr::Var {
module_name: "",
ident: bytes,
}),
alloc_expr(ast::Expr::Var {
module_name: "Decode",
ident: "decoder",
}),
alloc_expr(ast::Expr::Var {
module_name: "",
ident: fmt,
}),
]),
CalledVia::Space,
);
// Decode.mapResult (Decode.decodeWith bytes Decode.decoder fmt) @Opaq
let call_map_result = ast::Expr::Apply(
alloc_expr(ast::Expr::Var {
module_name: "Decode",
ident: "mapResult",
}),
env.arena.alloc([
&*alloc_expr(call_decode_with),
alloc_expr(ast::Expr::OpaqueRef(at_opaque)),
]),
CalledVia::Space,
);
// \bytes, fmt ->
// Decode.mapResult (Decode.decodeWith bytes Decode.decoder fmt) @Opaq
let custom_closure = ast::Expr::Closure(
env.arena.alloc([
Loc::at(DERIVED_REGION, ast::Pattern::Identifier(bytes)),
Loc::at(DERIVED_REGION, ast::Pattern::Identifier(fmt)),
]),
alloc_expr(call_map_result),
);
// Decode.custom \bytes, fmt -> ...
ast::Expr::Apply(
alloc_expr(ast::Expr::Var {
module_name: "Decode",
ident: "custom",
}),
env.arena.alloc([&*alloc_expr(custom_closure)]),
CalledVia::Space,
)
};
call_custom
}
fn hash<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
let alloc_pat = |it| env.arena.alloc(Loc::at(DERIVED_REGION, it));
let alloc_expr = |it| env.arena.alloc(Loc::at(DERIVED_REGION, it));
@ -162,7 +229,7 @@ pub(crate) fn synthesize_member_impl<'a>(
format!("#{}_toEncoder", opaque_name),
to_encoder(env, at_opaque),
),
Symbol::DECODE_DECODER => (format!("#{}_decoder", opaque_name), todo!()),
Symbol::DECODE_DECODER => (format!("#{}_decoder", opaque_name), decoder(env, at_opaque)),
Symbol::HASH_HASH => (format!("#{}_hash", opaque_name), hash(env, at_opaque)),
Symbol::BOOL_IS_EQ => (format!("#{}_isEq", opaque_name), is_eq(env, at_opaque)),
other => internal_error!("{:?} is not a derivable ability member!", other),

View file

@ -8062,6 +8062,26 @@ mod solve_expr {
);
}
#[test]
fn derive_decoder_for_opaque() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
N := U8 has [Decoding]
main : Decoder N _
main = Decode.custom \bytes, fmt ->
Decode.decodeWith bytes Decode.decoder fmt
# ^^^^^^^^^^^^^^
"#
),
@"N#Decode.decoder(3) : List U8, fmt -[[7(7)]]-> { rest : List U8, result : [Err [TooShort], Ok U8] } | fmt has DecoderFormatting"
print_only_under_alias: true
);
}
#[test]
fn derive_hash_for_opaque() {
infer_queries!(

View file

@ -791,6 +791,32 @@ fn decode_use_stdlib() {
)
}
#[test]
#[cfg(all(
any(feature = "gen-llvm", feature = "gen-wasm"),
not(debug_assertions) // https://github.com/roc-lang/roc/issues/3898
))]
fn decode_derive_decoder_for_opaque() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Json]
provides [main] to "./platform"
HelloWorld := { a: Str } has [Decoding]
main =
when Str.toUtf8 """{"a":"Hello, World!"}""" |> Decode.fromBytes Json.fromUtf8 is
Ok (@HelloWorld {a}) -> a
_ -> "FAIL"
"#
),
RocStr::from(r#"Hello, World!"#),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn decode_use_stdlib_json_list() {