Skip to content

Commit 33ad195

Browse files
committed
Add support for map comprehensions
1 parent 639e3ab commit 33ad195

3 files changed

Lines changed: 160 additions & 2 deletions

File tree

src/typechecker.erl

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1926,6 +1926,8 @@ do_type_check_expr(Env, {lc, _, Expr, Qualifiers}) ->
19261926
type_check_comprehension(Env, lc, Expr, Qualifiers);
19271927
do_type_check_expr(Env, {bc, _, Expr, Qualifiers}) ->
19281928
type_check_comprehension(Env, bc, Expr, Qualifiers);
1929+
do_type_check_expr(Env, {mc, _, Assoc, Qualifiers}) ->
1930+
type_check_comprehension(Env, mc, Assoc, Qualifiers);
19291931
do_type_check_expr(Env, {block, _, Block}) ->
19301932
type_check_block(Env, Block);
19311933

@@ -2505,6 +2507,11 @@ type_check_comprehension(Env, lc, Expr, []) ->
25052507
{Ty, _VB} = type_check_expr(Env, Expr),
25062508
RetTy = {type, erl_anno:new(0), list, [Ty]},
25072509
{RetTy, Env};
2510+
type_check_comprehension(Env, mc, {map_field_assoc, _, KeyExpr, ValExpr}, []) ->
2511+
{KeyTy, _VB1} = type_check_expr(Env, KeyExpr),
2512+
{ValTy, _VB2} = type_check_expr(Env, ValExpr),
2513+
RetTy = type(map, [type_assoc(map_field_assoc, [KeyTy, ValTy])]),
2514+
{RetTy, Env};
25082515
type_check_comprehension(Env, bc, Expr, []) ->
25092516
{Ty, _VB} = type_check_expr(Env, Expr),
25102517
RetTy = case normalize(Ty, Env) of
@@ -2566,6 +2573,30 @@ type_check_comprehension(Env, Compr, Expr, [{BGenerateTag, _P, Pat, Gen} | Quals
25662573
{TyL, VarBinds2} =
25672574
type_check_comprehension(NewEnv, Compr, Expr, Quals),
25682575
{TyL, union_var_binds(VarBinds1, VarBinds2, Env)};
2576+
type_check_comprehension(Env, Compr, Expr, [{MGenerateTag, _, {map_field_exact, _, KeyPat, ValPat}, Gen} | Quals])
2577+
when MGenerateTag =:= m_generate; MGenerateTag =:= m_generate_strict ->
2578+
{Ty, _VB1} = type_check_expr(Env, Gen),
2579+
%% Generator patterns create fresh variable bindings (shadow outer vars)
2580+
GenEnv = remove_pat_vars(KeyPat, remove_pat_vars(ValPat, Env)),
2581+
case expect_map_type(normalize(Ty, Env), Env) of
2582+
{assoc_tys, AssocTys} when is_list(AssocTys) ->
2583+
{KeyTys, ValTys} = lists:foldl(
2584+
fun({type, _, _AssocTag, [KT, VT]}, {KAcc, VAcc}) ->
2585+
{[KT | KAcc], [VT | VAcc]}
2586+
end, {[], []}, AssocTys),
2587+
KeyTy = normalize(type(union, KeyTys), Env),
2588+
ValTy = normalize(type(union, ValTys), Env),
2589+
{_PatTys1, _UBounds1, Env1} =
2590+
add_types_pats([KeyPat], [KeyTy], GenEnv, capture_vars),
2591+
{_PatTys2, _UBounds2, NewEnv} =
2592+
add_types_pats([ValPat], [ValTy], Env1, capture_vars),
2593+
type_check_comprehension(NewEnv, Compr, Expr, Quals);
2594+
any ->
2595+
NewEnv = add_any_types_pat(ValPat, add_any_types_pat(KeyPat, GenEnv)),
2596+
type_check_comprehension(NewEnv, Compr, Expr, Quals);
2597+
{type_error, BadTy} ->
2598+
throw(type_error(Gen, BadTy, type(map)))
2599+
end;
25692600
type_check_comprehension(Env, Compr, Expr, [Guard | Quals]) ->
25702601
%% We don't require guards to return a boolean.
25712602
%% This decision is up for debate.
@@ -2825,6 +2856,8 @@ do_type_check_expr_in(Env, ResTy, {lc, P, Expr, Qualifiers} = OrigExpr) ->
28252856
type_check_comprehension_in(Env, ResTy, OrigExpr, lc, Expr, P, Qualifiers);
28262857
do_type_check_expr_in(Env, ResTy, {bc, P, Expr, Qualifiers} = OrigExpr) ->
28272858
type_check_comprehension_in(Env, ResTy, OrigExpr, bc, Expr, P, Qualifiers);
2859+
do_type_check_expr_in(Env, ResTy, {mc, P, Assoc, Qualifiers} = OrigExpr) ->
2860+
type_check_comprehension_in(Env, ResTy, OrigExpr, mc, Assoc, P, Qualifiers);
28282861

28292862
%% Functions
28302863
do_type_check_expr_in(Env, Ty, {'fun', _, {clauses, Clauses}} = Fun) ->
@@ -3279,14 +3312,15 @@ unary_op_arg_type('-', Ty = {type, _, float, []}) ->
32793312
-spec type_check_comprehension_in(Env :: env(),
32803313
ResTy :: type(),
32813314
OrigExpr :: gradualizer_type:abstract_expr(),
3282-
Compr :: lc | bc,
3315+
Compr :: lc | bc | mc,
32833316
Expr :: gradualizer_type:abstract_expr(),
32843317
Position :: erl_anno:anno(),
3285-
Qualifiers :: [ListGen | BinGen | Filter]) ->
3318+
Qualifiers :: [ListGen | BinGen | MapGen | Filter]) ->
32863319
env()
32873320
when
32883321
ListGen :: {generate | generate_strict, erl_anno:anno(), gradualizer_type:abstract_expr(), gradualizer_type:abstract_expr()},
32893322
BinGen :: {b_generate | b_generate_strict, erl_anno:anno(), gradualizer_type:abstract_expr(), gradualizer_type:abstract_expr()},
3323+
MapGen :: {m_generate | m_generate_strict, erl_anno:anno(), gradualizer_type:abstract_expr(), gradualizer_type:abstract_expr()},
32903324
Filter :: gradualizer_type:abstract_expr().
32913325
type_check_comprehension_in(Env, ResTy, OrigExpr, lc, Expr, _P, []) ->
32923326
case expect_list_type(ResTy, allow_nil_type, Env) of
@@ -3335,6 +3369,52 @@ type_check_comprehension_in(Env, ResTy, OrigExpr, bc, Expr, _P, []) ->
33353369
{type_error, Ty} ->
33363370
throw({type_error, OrigExpr, Ty, ResTy})
33373371
end;
3372+
type_check_comprehension_in(Env, ResTy, OrigExpr, mc,
3373+
{map_field_assoc, _, KeyExpr, ValExpr}, _P, []) ->
3374+
case expect_map_type(normalize(ResTy, Env), Env) of
3375+
any ->
3376+
{_KeyTy, _VB1} = type_check_expr(Env, KeyExpr),
3377+
{_ValTy, _VB2} = type_check_expr(Env, ValExpr),
3378+
Env;
3379+
{assoc_tys, AssocTys} when is_list(AssocTys) ->
3380+
{KeyTys, ValTys} = lists:foldl(
3381+
fun({type, _, _AssocTag, [KT, VT]}, {KAcc, VAcc}) ->
3382+
{[KT | KAcc], [VT | VAcc]}
3383+
end, {[], []}, AssocTys),
3384+
KeyTy = normalize(type(union, KeyTys), Env),
3385+
ValTy = normalize(type(union, ValTys), Env),
3386+
_VB1 = type_check_expr_in(Env, KeyTy, KeyExpr),
3387+
_VB2 = type_check_expr_in(Env, ValTy, ValExpr),
3388+
Env;
3389+
{type_error, _} ->
3390+
throw(type_error(OrigExpr, type(map), ResTy))
3391+
end;
3392+
type_check_comprehension_in(Env, ResTy, OrigExpr, Compr, Expr, P,
3393+
[{MGenerateTag, _, {map_field_exact, _, KeyPat, ValPat}, Gen} | Quals])
3394+
when MGenerateTag =:= m_generate; MGenerateTag =:= m_generate_strict ->
3395+
{Ty, _VB1} = type_check_expr(Env, Gen),
3396+
GenEnv = remove_pat_vars(KeyPat, remove_pat_vars(ValPat, Env)),
3397+
case expect_map_type(normalize(Ty, Env), Env) of
3398+
any ->
3399+
NewEnv = add_any_types_pat(ValPat, add_any_types_pat(KeyPat, GenEnv)),
3400+
_VB2 = type_check_comprehension_in(NewEnv, ResTy, OrigExpr, Compr, Expr, P, Quals),
3401+
Env;
3402+
{assoc_tys, AssocTys} when is_list(AssocTys) ->
3403+
{KeyTys, ValTys} = lists:foldl(
3404+
fun({type, _, _AssocTag, [KT, VT]}, {KAcc, VAcc}) ->
3405+
{[KT | KAcc], [VT | VAcc]}
3406+
end, {[], []}, AssocTys),
3407+
KeyTy = normalize(type(union, KeyTys), Env),
3408+
ValTy = normalize(type(union, ValTys), Env),
3409+
{_PatTys1, _UBounds1, Env1} =
3410+
add_types_pats([KeyPat], [KeyTy], GenEnv, capture_vars),
3411+
{_PatTys2, _UBounds2, NewEnv} =
3412+
add_types_pats([ValPat], [ValTy], Env1, capture_vars),
3413+
_VB2 = type_check_comprehension_in(NewEnv, ResTy, OrigExpr, Compr, Expr, P, Quals),
3414+
Env;
3415+
{type_error, BadTy} ->
3416+
throw(type_error(Gen, BadTy, type(map)))
3417+
end;
33383418
type_check_comprehension_in(Env, ResTy, OrigExpr, Compr, Expr, P,
33393419
[{GenerateTag, _, Pat, Gen} | Quals])
33403420
when GenerateTag =:= generate; GenerateTag =:= generate_strict ->
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-module(map_comprehension_fail).
2+
3+
%% Each exported function should produce exactly one type error.
4+
5+
-export([mc_wrong_return_type/0, map_gen_not_map/1]).
6+
7+
%% Map comprehension result used where integer is expected
8+
-spec mc_wrong_return_type() -> integer().
9+
mc_wrong_return_type() ->
10+
#{X => X || X <- [1, 2, 3]}.
11+
12+
%% Map generator from non-map value
13+
-spec map_gen_not_map(integer()) -> [integer()].
14+
map_gen_not_map(N) ->
15+
[V || _K := V <- N].
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
-module(map_comprehension_pass).
2+
3+
-compile([export_all, nowarn_export_all]).
4+
5+
%% Basic map comprehension with list generator
6+
-spec mc_from_list() -> #{integer() => integer()}.
7+
mc_from_list() ->
8+
#{X => X * X || X <- [1, 2, 3]}.
9+
10+
%% Map comprehension with map generator
11+
-spec mc_swap(#{atom() => integer()}) -> #{integer() => atom()}.
12+
mc_swap(M) ->
13+
#{V => K || K := V <- M}.
14+
15+
%% Map comprehension with map generator, doubling values
16+
-spec mc_double_values(#{atom() => integer()}) -> #{atom() => integer()}.
17+
mc_double_values(M) ->
18+
#{K => V * 2 || K := V <- M}.
19+
20+
%% Map comprehension with filter
21+
-spec mc_filter() -> #{integer() => integer()}.
22+
mc_filter() ->
23+
#{X => X * X || X <- [1, 2, 3, 4, 5], X rem 2 =:= 1}.
24+
25+
%% Map generator in list comprehension
26+
-spec keys_from_map(#{atom() => integer()}) -> [atom()].
27+
keys_from_map(M) ->
28+
[K || K := _V <- M].
29+
30+
%% Map generator in list comprehension, filtering values
31+
-spec keys_with_large_values(#{atom() => integer()}) -> [atom()].
32+
keys_with_large_values(M) ->
33+
[K || K := V <- M, V > 10].
34+
35+
%% Map comprehension with binary generator
36+
-spec mc_from_binary() -> #{integer() => integer()}.
37+
mc_from_binary() ->
38+
#{X => X * 2 || <<X>> <= <<1,2,3>>}.
39+
40+
%% Map generator producing values for binary comprehension
41+
-spec bc_from_map(#{atom() => integer()}) -> binary().
42+
bc_from_map(M) ->
43+
<< <<V>> || _K := V <- M >>.
44+
45+
%% Strict map generator
46+
-spec strict_mc(#{atom() => integer()}) -> #{atom() => integer()}.
47+
strict_mc(M) ->
48+
#{K => V + 1 || K := V <:- M}.
49+
50+
%% Strict map generator in list comprehension
51+
-spec strict_map_gen_lc(#{atom() => integer()}) -> [integer()].
52+
strict_map_gen_lc(M) ->
53+
[V || _K := V <:- M].
54+
55+
%% Map comprehension with any() map
56+
-spec mc_any_map() -> map().
57+
mc_any_map() ->
58+
#{K => V || K := V <- maps:new()}.
59+
60+
%% Mixed list and map generators
61+
-spec mixed_generators(#{atom() => integer()}) -> [{integer(), atom()}].
62+
mixed_generators(M) ->
63+
[{V, K} || K := V <- M].

0 commit comments

Comments
 (0)