Allow assigning patterns to variables (#336)

* Change syntax tree and parser

* Add assign pattern to type inference

* Use check_expr instead of hard-coded type

* Add fcode generation for assign pattern

* Implement rename_spat for assign pattern

* Add tests

* Update CHANGELOG.md

* Update docs and changelog

* Add letpat to aeso_syntax_utils:fold

* Use Plus instead of Scoped
This commit is contained in:
Gaith Hallak 2021-09-11 17:18:30 +03:00 committed by GitHub
parent a7b7aafced
commit e8a171dc45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 68 additions and 4 deletions

View File

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Set` stdlib - `Set` stdlib
- `Option.force_msg` - `Option.force_msg`
- Loading namespaces into the current scope (e.g. `using Pair`) - Loading namespaces into the current scope (e.g. `using Pair`)
- Assign patterns to variables (e.g. `let x::(t = y::_) = [1, 2, 3, 4]` where `t == [2, 3, 4]`)
### Changed ### Changed
### Removed ### Removed

View File

@ -471,6 +471,17 @@ function
get_left(Both(x, _)) = Some(x) get_left(Both(x, _)) = Some(x)
``` ```
Sophia also supports the assignment of patterns to variables:
```sophia
function f(x) = switch(x)
h1::(t = h2::_) => (h1 + h2)::t // same as `h1::h2::k => (h1 + h2)::h2::k`
_ => x
function g(p : int * option(int)) : int =
let (a, (o = Some(b))) = p // o is equal to Pair.snd(p)
b
```
*NOTE: Data types cannot currently be recursive.* *NOTE: Data types cannot currently be recursive.*
## Lists ## Lists

View File

@ -1703,6 +1703,9 @@ infer_expr(Env, {lam, Attrs, Args, Body}) ->
infer_case(Env, Attrs, {tuple, Attrs, ArgPatterns}, {tuple_t, Attrs, ArgTypes}, Body, ResultType), infer_case(Env, Attrs, {tuple, Attrs, ArgPatterns}, {tuple_t, Attrs, ArgTypes}, Body, ResultType),
NewArgs = [{arg, As, NewPat, NewT} || {typed, As, NewPat, NewT} <- NewArgPatterns], NewArgs = [{arg, As, NewPat, NewT} || {typed, As, NewPat, NewT} <- NewArgPatterns],
{typed, Attrs, {lam, Attrs, NewArgs, NewBody}, {fun_t, Attrs, [], ArgTypes, ResultType}}; {typed, Attrs, {lam, Attrs, NewArgs, NewBody}, {fun_t, Attrs, [], ArgTypes, ResultType}};
infer_expr(Env, {letpat, Attrs, Id, Pattern}) ->
NewPattern = {typed, _, _, PatType} = infer_expr(Env, Pattern),
{typed, Attrs, {letpat, Attrs, check_expr(Env, Id, PatType), NewPattern}, PatType};
infer_expr(Env, Let = {letval, Attrs, _, _}) -> infer_expr(Env, Let = {letval, Attrs, _, _}) ->
type_error({missing_body_for_let, Attrs}), type_error({missing_body_for_let, Attrs}),
infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}); infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]});
@ -1918,6 +1921,8 @@ free_vars({record, _, Fields}) ->
free_vars([E || {field, _, _, E} <- Fields]); free_vars([E || {field, _, _, E} <- Fields]);
free_vars({typed, _, A, _}) -> free_vars({typed, _, A, _}) ->
free_vars(A); free_vars(A);
free_vars({letpat, _, Id, Pat}) ->
free_vars(Id) ++ free_vars(Pat);
free_vars(L) when is_list(L) -> free_vars(L) when is_list(L) ->
[V || Elem <- L, [V || Elem <- L,
V <- free_vars(Elem)]. V <- free_vars(Elem)].

View File

@ -96,7 +96,8 @@
| nil | nil
| {'::', var_name(), var_name()} | {'::', var_name(), var_name()}
| {con, arities(), tag(), [var_name()]} | {con, arities(), tag(), [var_name()]}
| {tuple, [var_name()]}. | {tuple, [var_name()]}
| {assign, var_name(), var_name()}.
-type ftype() :: integer -type ftype() :: integer
| boolean | boolean
@ -875,7 +876,8 @@ alts_to_fcode(Env, Type, X, Alts) ->
| {string, binary()} | {string, binary()}
| nil | {'::', fpat(), fpat()} | nil | {'::', fpat(), fpat()}
| {tuple, [fpat()]} | {tuple, [fpat()]}
| {con, arities(), tag(), [fpat()]}. | {con, arities(), tag(), [fpat()]}
| {assign, fpat(), fpat()}.
%% %% Invariant: the number of variables matches the number of patterns in each falt. %% %% Invariant: the number of variables matches the number of patterns in each falt.
-spec split_tree(env(), [{var_name(), ftype()}], [falt()]) -> fsplit(). -spec split_tree(env(), [{var_name(), ftype()}], [falt()]) -> fsplit().
@ -975,6 +977,8 @@ split_pat({'::', P, Q}) -> {{'::', fresh_name(), fresh_name()}, [P, Q]};
split_pat({con, As, I, Pats}) -> split_pat({con, As, I, Pats}) ->
Xs = [fresh_name() || _ <- Pats], Xs = [fresh_name() || _ <- Pats],
{{con, As, I, Xs}, Pats}; {{con, As, I, Xs}, Pats};
split_pat({assign, X = {var, _}, P}) ->
{{assign, fresh_name(), fresh_name()}, [X, P]};
split_pat({tuple, Pats}) -> split_pat({tuple, Pats}) ->
Xs = [fresh_name() || _ <- Pats], Xs = [fresh_name() || _ <- Pats],
{{tuple, Xs}, Pats}. {{tuple, Xs}, Pats}.
@ -985,6 +989,7 @@ split_vars({int, _}, integer) -> [];
split_vars({string, _}, string) -> []; split_vars({string, _}, string) -> [];
split_vars(nil, {list, _}) -> []; split_vars(nil, {list, _}) -> [];
split_vars({'::', X, Xs}, {list, T}) -> [{X, T}, {Xs, {list, T}}]; split_vars({'::', X, Xs}, {list, T}) -> [{X, T}, {Xs, {list, T}}];
split_vars({assign, X, P}, T) -> [{X, T}, {P, T}];
split_vars({con, _, I, Xs}, {variant, Cons}) -> split_vars({con, _, I, Xs}, {variant, Cons}) ->
lists:zip(Xs, lists:nth(I + 1, Cons)); lists:zip(Xs, lists:nth(I + 1, Cons));
split_vars({tuple, Xs}, {tuple, Ts}) -> split_vars({tuple, Xs}, {tuple, Ts}) ->
@ -1040,6 +1045,8 @@ pat_to_fcode(Env, {record_t, Fields}, {record, _, FieldPats}) ->
end end, end end,
make_tuple([pat_to_fcode(Env, FieldPat(Field)) make_tuple([pat_to_fcode(Env, FieldPat(Field))
|| Field <- Fields]); || Field <- Fields]);
pat_to_fcode(Env, _Type, {letpat, _, Id = {typed, _, {id, _, _}, _}, Pattern}) ->
{assign, pat_to_fcode(Env, Id), pat_to_fcode(Env, Pattern)};
pat_to_fcode(_Env, Type, Pat) -> pat_to_fcode(_Env, Type, Pat) ->
error({todo, Pat, ':', Type}). error({todo, Pat, ':', Type}).
@ -1530,6 +1537,7 @@ match_pat(L, {lit, L}) -> [];
match_pat(nil, nil) -> []; match_pat(nil, nil) -> [];
match_pat({'::', X, Y}, {op, '::', [A, B]}) -> [{X, A}, {Y, B}]; match_pat({'::', X, Y}, {op, '::', [A, B]}) -> [{X, A}, {Y, B}];
match_pat({var, X}, E) -> [{X, E}]; match_pat({var, X}, E) -> [{X, E}];
match_pat({assign, X, P}, E) -> [{X, E}, {P, E}];
match_pat(_, _) -> false. match_pat(_, _) -> false.
constructor_form(Env, Expr) -> constructor_form(Env, Expr) ->
@ -1765,6 +1773,7 @@ pat_vars(nil) -> [];
pat_vars({'::', P, Q}) -> pat_vars(P) ++ pat_vars(Q); pat_vars({'::', P, Q}) -> pat_vars(P) ++ pat_vars(Q);
pat_vars({tuple, Ps}) -> pat_vars(Ps); pat_vars({tuple, Ps}) -> pat_vars(Ps);
pat_vars({con, _, _, Ps}) -> pat_vars(Ps); pat_vars({con, _, _, Ps}) -> pat_vars(Ps);
pat_vars({assign, X, P}) -> pat_vars(X) ++ pat_vars(P);
pat_vars(Ps) when is_list(Ps) -> [X || P <- Ps, X <- pat_vars(P)]. pat_vars(Ps) when is_list(Ps) -> [X || P <- Ps, X <- pat_vars(P)].
-spec fsplit_pat_vars(fsplit_pat()) -> [var_name()]. -spec fsplit_pat_vars(fsplit_pat()) -> [var_name()].
@ -1985,7 +1994,11 @@ rename_spat(Ren, {con, Ar, C, Xs}) ->
{{con, Ar, C, Zs}, Ren1}; {{con, Ar, C, Zs}, Ren1};
rename_spat(Ren, {tuple, Xs}) -> rename_spat(Ren, {tuple, Xs}) ->
{Zs, Ren1} = rename_bindings(Ren, Xs), {Zs, Ren1} = rename_bindings(Ren, Xs),
{{tuple, Zs}, Ren1}. {{tuple, Zs}, Ren1};
rename_spat(Ren, {assign, X, P}) ->
{X1, Ren1} = rename_binding(Ren, X),
{P1, Ren2} = rename_binding(Ren1, P),
{{assign, X1, P1}, Ren2}.
rename_split(Ren, {split, Type, X, Cases}) -> rename_split(Ren, {split, Type, X, Cases}) ->
{split, Type, rename_var(Ren, X), [rename_case(Ren, C) || C <- Cases]}; {split, Type, rename_var(Ren, X), [rename_case(Ren, C) || C <- Cases]};

View File

@ -224,6 +224,9 @@ arg() -> choice(
?RULE(id(), {arg, get_ann(_1), _1, type_wildcard(get_ann(_1))}), ?RULE(id(), {arg, get_ann(_1), _1, type_wildcard(get_ann(_1))}),
?RULE(id(), tok(':'), type(), {arg, get_ann(_1), _1, _3})). ?RULE(id(), tok(':'), type(), {arg, get_ann(_1), _1, _3})).
letpat() ->
?RULE(keyword('('), id(), tok('='), pattern(), tok(')'), {letpat, get_ann(_1), _2, _4}).
%% -- Types ------------------------------------------------------------------ %% -- Types ------------------------------------------------------------------
type_vars() -> paren_list(tvar()). type_vars() -> paren_list(tvar()).
@ -328,6 +331,7 @@ exprAtom() ->
, ?RULE(keyword('['), Expr, token('|'), comma_sep(comprehension_exp()), tok(']'), list_comp_e(_1, _2, _4)) , ?RULE(keyword('['), Expr, token('|'), comma_sep(comprehension_exp()), tok(']'), list_comp_e(_1, _2, _4))
, ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4)) , ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4))
, ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2)) , ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2))
, letpat()
]) ])
end). end).
@ -588,6 +592,8 @@ tuple_e(Ann, Exprs) -> {tuple, Ann, Exprs}.
list_comp_e(Ann, Expr, Binds) -> {list_comp, Ann, Expr, Binds}. list_comp_e(Ann, Expr, Binds) -> {list_comp, Ann, Expr, Binds}.
-spec parse_pattern(aeso_syntax:expr()) -> aeso_parse_lib:parser(aeso_syntax:pat()). -spec parse_pattern(aeso_syntax:expr()) -> aeso_parse_lib:parser(aeso_syntax:pat()).
parse_pattern({letpat, Ann, Id, Pat}) ->
{letpat, Ann, Id, parse_pattern(Pat)};
parse_pattern({app, Ann, Con = {'::', _}, Es}) -> parse_pattern({app, Ann, Con = {'::', _}, Es}) ->
{app, Ann, Con, lists:map(fun parse_pattern/1, Es)}; {app, Ann, Con, lists:map(fun parse_pattern/1, Es)};
parse_pattern({app, Ann, {'-', _}, [{int, _, N}]}) -> parse_pattern({app, Ann, {'-', _}, [{int, _, N}]}) ->

View File

@ -299,6 +299,8 @@ tuple_type(Factors) ->
]). ]).
-spec expr_p(integer(), aeso_syntax:arg_expr()) -> doc(). -spec expr_p(integer(), aeso_syntax:arg_expr()) -> doc().
expr_p(P, {letpat, _, Id, Pat}) ->
paren(P > 100, follow(hsep(expr(Id), text("=")), expr(Pat)));
expr_p(P, {named_arg, _, Name, E}) -> expr_p(P, {named_arg, _, Name, E}) ->
paren(P > 100, follow(hsep(expr(Name), text("=")), expr(E))); paren(P > 100, follow(hsep(expr(Name), text("=")), expr(E)));
expr_p(P, {lam, _, Args, E}) -> expr_p(P, {lam, _, Args, E}) ->

View File

@ -58,6 +58,7 @@
-type letval() :: {letval, ann(), pat(), expr()}. -type letval() :: {letval, ann(), pat(), expr()}.
-type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()}. -type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()}.
-type letpat() :: {letpat, ann(), id(), pat()}.
-type fundecl() :: {fun_decl, ann(), id(), type()}. -type fundecl() :: {fun_decl, ann(), id(), type()}.
-type letbind() -type letbind()
@ -122,7 +123,8 @@
| {block, ann(), [stmt()]} | {block, ann(), [stmt()]}
| {op(), ann()} | {op(), ann()}
| id() | qid() | con() | qcon() | id() | qid() | con() | qcon()
| constant(). | constant()
| letpat().
-type record_or_map() :: record | map | record_or_map_error. -type record_or_map() :: record | map | record_or_map_error.
@ -156,6 +158,7 @@
| {list, ann(), [pat()]} | {list, ann(), [pat()]}
| {typed, ann(), pat(), type()} | {typed, ann(), pat(), type()}
| {record, ann(), [field(pat())]} | {record, ann(), [field(pat())]}
| letpat()
| constant() | constant()
| con() | con()
| id(). | id().

View File

@ -88,6 +88,7 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
{map_get, _, A, B} -> Expr([A, B]); {map_get, _, A, B} -> Expr([A, B]);
{map_get, _, A, B, C} -> Expr([A, B, C]); {map_get, _, A, B, C} -> Expr([A, B, C]);
{block, _, Ss} -> Expr(Ss); {block, _, Ss} -> Expr(Ss);
{letpat, _, X, P} -> Plus(BindExpr(X), Expr(P));
%% field() %% field()
{field, _, LV, E} -> Expr([LV, E]); {field, _, LV, E} -> Expr([LV, E]);
{field, _, LV, _, E} -> Expr([LV, E]); {field, _, LV, _, E} -> Expr([LV, E]);

View File

@ -201,6 +201,7 @@ compilable_contracts() ->
"create", "create",
"child_contract_init_bug", "child_contract_init_bug",
"using_namespace", "using_namespace",
"assign_patterns",
"test" % Custom general-purpose test file. Keep it last on the list. "test" % Custom general-purpose test file. Keep it last on the list.
]. ].
@ -236,6 +237,7 @@ failing_contracts() ->
, ?PARSE_ERROR(vsemi, [<<?Pos(3, 3) "Unexpected indentation. Did you forget a '}'?">>]) , ?PARSE_ERROR(vsemi, [<<?Pos(3, 3) "Unexpected indentation. Did you forget a '}'?">>])
, ?PARSE_ERROR(vclose, [<<?Pos(4, 3) "Unexpected indentation. Did you forget a ']'?">>]) , ?PARSE_ERROR(vclose, [<<?Pos(4, 3) "Unexpected indentation. Did you forget a ']'?">>])
, ?PARSE_ERROR(indent_fail, [<<?Pos(3, 2) "Unexpected token 'entrypoint'.">>]) , ?PARSE_ERROR(indent_fail, [<<?Pos(3, 2) "Unexpected token 'entrypoint'.">>])
, ?PARSE_ERROR(assign_pattern_to_pattern, [<<?Pos(3, 22) "Unexpected token '='.">>])
%% Type errors %% Type errors
, ?TYPE_ERROR(name_clash, , ?TYPE_ERROR(name_clash,

View File

@ -0,0 +1,4 @@
contract AssignPatternToPattern =
entrypoint f() =
let x::(t::z = y) = [1, 2, 3]
(x + t)::y

View File

@ -0,0 +1,16 @@
include "List.aes"
contract AssignPatterns =
entrypoint test() = foo([1, 0, 2], (2, Some(3)), Some([4, 5]))
entrypoint foo(xs : list(int), p : int * option(int), some : option(list(int))) =
let x::(t = y::_) = xs
let z::_ = t
let (a, (o = Some(b))) = p
let Some((f = g::_)) = some
g + List.get(1, f)
x + y + z + a + b