Skip to content

Commit

Permalink
Record refinement (#271)
Browse files Browse the repository at this point in the history
Support record matching and refinement.
  • Loading branch information
Francois Brodeur authored Oct 19, 2020
1 parent f4e95cb commit 766ba27
Show file tree
Hide file tree
Showing 8 changed files with 433 additions and 102 deletions.
366 changes: 277 additions & 89 deletions src/typechecker.erl

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions src/typelib.erl
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,10 @@ remove_pos({Type, _, Value})
remove_pos({user_type, Anno, Name, Params}) when is_list(Params) ->
{user_type, anno_keep_only_filename(Anno), Name,
lists:map(fun remove_pos/1, Params)};
remove_pos({type, Anno, record, Params = [{atom, AtomAnno, Name}]}) ->
{type, anno_keep_only_filename(Anno), record, [{atom, anno_keep_only_filename(AtomAnno), Name}]};
% Might need to bring this back but will need to support more params since the records can be refined now
% One thing to be careful is that we can "redefine" some fields for a specific record instance as well
% remove_pos({type, Anno, record, Params = [{atom, AtomAnno, Name}]}) ->
% {type, anno_keep_only_filename(Anno), record, [{atom, anno_keep_only_filename(AtomAnno), Name}]};
remove_pos({type, _, bounded_fun, [FT, Cs]}) ->
{type, erl_anno:new(0), bounded_fun, [remove_pos(FT)
,lists:map(fun remove_pos/1, Cs)]};
Expand Down
8 changes: 0 additions & 8 deletions test/known_problems/should_pass/pattern_record.erl

This file was deleted.

7 changes: 6 additions & 1 deletion test/should_fail/record.erl
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
-module(record).

-export([g/0]).
-export([g/0, h/0]).

-record(rec, { apa :: integer()}).

-spec g() -> integer().
g() ->
#rec{apa = 1}.

-spec h() -> integer().
h() ->
Rec = #rec{},
Rec#rec.apa.
40 changes: 40 additions & 0 deletions test/should_fail/record_refinement_fail.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
-module(record_refinement_fail).

-compile(export_all).

-record(one_field, {a :: integer() | undefined}).

-spec one_field(#one_field{}, integer()) -> integer().
one_field(#one_field{a = I}, _) -> I;
one_field(_, I) -> I.

-spec one_field2(#one_field{}, integer()) -> integer().
one_field2(R, _) -> R#one_field.a;
one_field2(_, I) -> I.

-record(refined_field, {f :: integer() | undefined}).
-spec refined_field(#refined_field{}) -> #refined_field{f :: integer()}.
refined_field(R) -> R.

-spec refined_field2(#refined_field{}) -> #refined_field{f :: atom()}.
refined_field2(#refined_field{f = undefined}) -> #refined_field{f = 0};
refined_field2(R) -> R.

-record(two_level2, {value :: undefined | binary()}).
-record(two_level1, {two_level2 :: undefined | #two_level2{}}).

-spec two_level1(#two_level1{}) -> integer().
two_level1(#two_level1{two_level2 = undefined}) ->
0;
two_level1(#two_level1{two_level2 = #two_level2{value = undefined}}) ->
0;
two_level1(#two_level1{two_level2 = #two_level2{value = Value}}) ->
Value.

-spec two_level2(#two_level1{}) -> integer().
two_level2(#two_level1{two_level2 = undefined}) ->
0;
two_level2(#two_level1{two_level2 = #two_level2{value = undefined}}) ->
0;
two_level2(R1) ->
R1#two_level1.two_level2#two_level2.value.
10 changes: 9 additions & 1 deletion test/should_pass/pattern_bind_reuse.erl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-module(pattern_bind_reuse).

-export([test/2, guess_the_die/1, is_same/2]).
-export([test/2, guess_the_die/1, is_same/2, record/2]).

-spec test(integer() | undefined, integer()) -> integer().
test(I, I) -> I + I;
Expand All @@ -27,3 +27,11 @@ is_same(N, N) ->
is_same(_, _) ->
%% False error: This clause can't be reached
false.

-record(r, { f :: integer() | undefined }).

-spec record(#r{}, integer()) -> integer().
record(#r{f = I}, I) -> I;
record(#r{f = undefined}, I) -> I;
record(#r{f = I}, _) -> I.

78 changes: 78 additions & 0 deletions test/should_pass/record_refinement.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
-module(record_refinement).

-record(one_field, {a :: integer() | undefined}).

-spec one_field(#one_field{}, integer()) -> integer().
one_field(#one_field{a = undefined}, I) -> I;
one_field(#one_field{a = I}, _) -> I.

-spec one_field2(#one_field{}, integer()) -> integer().
one_field2(#one_field{a = undefined}, I) -> I;
one_field2(R, _) -> R#one_field.a.

-record(two_field, {a :: atom(), b :: integer() | undefined}).

-spec two_field(#two_field{}, integer()) -> integer().
two_field(#two_field{b = undefined}, I) -> I;
two_field(#two_field{b = I}, _) -> I.

-record(multiple, {a :: integer() | undefined, b :: integer() | undefined}).

-spec multiple(#multiple{}) -> integer().
multiple(#multiple{a = undefined, b = undefined}) -> 0;
multiple(#multiple{a = undefined, b = B}) -> B;
multiple(#multiple{a = A, b = undefined}) -> A;
multiple(#multiple{a = A, b = B}) -> A + B.

-record(underscore, {a :: integer() | undefined, b :: integer() | undefined, c :: integer() | undefined}).

-spec underscore(#underscore{}) -> integer().
underscore(#underscore{_ = undefined}) -> 0;
underscore(#underscore{a = A, _ = undefined}) -> A;
underscore(#underscore{b = B, _ = undefined}) -> B;
underscore(#underscore{c = C, _ = undefined}) -> C;
underscore(#underscore{a = A, b = B, _ = undefined}) -> A + B;
underscore(#underscore{a = A, c = C, _ = undefined}) -> A + C;
underscore(#underscore{b = B, c = C, _ = undefined}) -> B + C;
underscore(#underscore{a = A, b = B, c = C}) -> A + B + C.

-record(type_var, {f :: integer()}).
-spec type_var([#type_var{}]) -> [integer()].
type_var(Rs) -> lists:map(fun (R) -> R#type_var.f end, Rs).

-record(any, {f :: integer()}).
without_spec(R) -> with_spec(R#any.f).

-spec with_spec(integer()) -> integer().
with_spec(I) -> I + 1.

-record(refined_field, {f :: integer() | undefined}).
-spec refined_field(#refined_field{}) -> #refined_field{f :: integer()}.
refined_field(#refined_field{f = undefined}) -> #refined_field{f = 0};
refined_field(R) -> R.

-spec refined_field_safe(#refined_field{f :: integer()}) -> #refined_field{f :: integer()}.
refined_field_safe(#refined_field{f = I}) -> #refined_field{f = I + 1}.

-spec refined_field_unsafe(#refined_field{}) -> #refined_field{}.
refined_field_unsafe(R = #refined_field{f = undefined}) -> R;
refined_field_unsafe(R) -> refined_field_safe(R).

-record(two_level2, {value :: undefined | integer()}).
-record(two_level1, {two_level2 :: undefined | #two_level2{}}).

-spec two_level1(#two_level1{}) -> integer().
two_level1(#two_level1{two_level2 = undefined}) ->
0;
two_level1(#two_level1{two_level2 = #two_level2{value = undefined}}) ->
0;
two_level1(#two_level1{two_level2 = #two_level2{value = Value}}) ->
Value.

-spec two_level2(#two_level1{}) -> integer().
two_level2(#two_level1{two_level2 = undefined}) ->
0;
two_level2(#two_level1{two_level2 = #two_level2{value = undefined}}) ->
0;
two_level2(R1) ->
R1#two_level1.two_level2#two_level2.value.
20 changes: 19 additions & 1 deletion test/should_pass/records.erl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-module(records).

-export([f/0, g/1, h/0, i/0, j/0,
-export([f/0, g/1, h/0, i/0, j/0, k/0, l/0,
rec_field_subtype/1,
rec_index_subtype/0,
record_as_tuple/1]).
Expand Down Expand Up @@ -32,6 +32,24 @@ i() ->
j() ->
#r.f2.

-record(test_k, {
field :: integer() | undefined
}).

-spec k() -> integer() | undefined.
k() ->
Test = #test_k{},
Test#test_k.field.

-record(test_l, {
field = 0 :: integer()
}).

-spec l() -> integer().
l() ->
Test = #test_l{},
Test#test_l.field.

-spec rec_field_subtype(#r{}) -> number().
rec_field_subtype(R) ->
R#r.f2.
Expand Down

0 comments on commit 766ba27

Please sign in to comment.