Skip to content

Commit 766ba27

Browse files
author
Francois Brodeur
authored
Record refinement (#271)
Support record matching and refinement.
1 parent f4e95cb commit 766ba27

File tree

8 files changed

+433
-102
lines changed

8 files changed

+433
-102
lines changed

src/typechecker.erl

Lines changed: 277 additions & 89 deletions
Large diffs are not rendered by default.

src/typelib.erl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,10 @@ remove_pos({Type, _, Value})
9292
remove_pos({user_type, Anno, Name, Params}) when is_list(Params) ->
9393
{user_type, anno_keep_only_filename(Anno), Name,
9494
lists:map(fun remove_pos/1, Params)};
95-
remove_pos({type, Anno, record, Params = [{atom, AtomAnno, Name}]}) ->
96-
{type, anno_keep_only_filename(Anno), record, [{atom, anno_keep_only_filename(AtomAnno), Name}]};
95+
% Might need to bring this back but will need to support more params since the records can be refined now
96+
% One thing to be careful is that we can "redefine" some fields for a specific record instance as well
97+
% remove_pos({type, Anno, record, Params = [{atom, AtomAnno, Name}]}) ->
98+
% {type, anno_keep_only_filename(Anno), record, [{atom, anno_keep_only_filename(AtomAnno), Name}]};
9799
remove_pos({type, _, bounded_fun, [FT, Cs]}) ->
98100
{type, erl_anno:new(0), bounded_fun, [remove_pos(FT)
99101
,lists:map(fun remove_pos/1, Cs)]};

test/known_problems/should_pass/pattern_record.erl

Lines changed: 0 additions & 8 deletions
This file was deleted.

test/should_fail/record.erl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
-module(record).
22

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

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

77
-spec g() -> integer().
88
g() ->
99
#rec{apa = 1}.
10+
11+
-spec h() -> integer().
12+
h() ->
13+
Rec = #rec{},
14+
Rec#rec.apa.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
-module(record_refinement_fail).
2+
3+
-compile(export_all).
4+
5+
-record(one_field, {a :: integer() | undefined}).
6+
7+
-spec one_field(#one_field{}, integer()) -> integer().
8+
one_field(#one_field{a = I}, _) -> I;
9+
one_field(_, I) -> I.
10+
11+
-spec one_field2(#one_field{}, integer()) -> integer().
12+
one_field2(R, _) -> R#one_field.a;
13+
one_field2(_, I) -> I.
14+
15+
-record(refined_field, {f :: integer() | undefined}).
16+
-spec refined_field(#refined_field{}) -> #refined_field{f :: integer()}.
17+
refined_field(R) -> R.
18+
19+
-spec refined_field2(#refined_field{}) -> #refined_field{f :: atom()}.
20+
refined_field2(#refined_field{f = undefined}) -> #refined_field{f = 0};
21+
refined_field2(R) -> R.
22+
23+
-record(two_level2, {value :: undefined | binary()}).
24+
-record(two_level1, {two_level2 :: undefined | #two_level2{}}).
25+
26+
-spec two_level1(#two_level1{}) -> integer().
27+
two_level1(#two_level1{two_level2 = undefined}) ->
28+
0;
29+
two_level1(#two_level1{two_level2 = #two_level2{value = undefined}}) ->
30+
0;
31+
two_level1(#two_level1{two_level2 = #two_level2{value = Value}}) ->
32+
Value.
33+
34+
-spec two_level2(#two_level1{}) -> integer().
35+
two_level2(#two_level1{two_level2 = undefined}) ->
36+
0;
37+
two_level2(#two_level1{two_level2 = #two_level2{value = undefined}}) ->
38+
0;
39+
two_level2(R1) ->
40+
R1#two_level1.two_level2#two_level2.value.

test/should_pass/pattern_bind_reuse.erl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
-module(pattern_bind_reuse).
22

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

55
-spec test(integer() | undefined, integer()) -> integer().
66
test(I, I) -> I + I;
@@ -27,3 +27,11 @@ is_same(N, N) ->
2727
is_same(_, _) ->
2828
%% False error: This clause can't be reached
2929
false.
30+
31+
-record(r, { f :: integer() | undefined }).
32+
33+
-spec record(#r{}, integer()) -> integer().
34+
record(#r{f = I}, I) -> I;
35+
record(#r{f = undefined}, I) -> I;
36+
record(#r{f = I}, _) -> I.
37+
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
-module(record_refinement).
2+
3+
-record(one_field, {a :: integer() | undefined}).
4+
5+
-spec one_field(#one_field{}, integer()) -> integer().
6+
one_field(#one_field{a = undefined}, I) -> I;
7+
one_field(#one_field{a = I}, _) -> I.
8+
9+
-spec one_field2(#one_field{}, integer()) -> integer().
10+
one_field2(#one_field{a = undefined}, I) -> I;
11+
one_field2(R, _) -> R#one_field.a.
12+
13+
-record(two_field, {a :: atom(), b :: integer() | undefined}).
14+
15+
-spec two_field(#two_field{}, integer()) -> integer().
16+
two_field(#two_field{b = undefined}, I) -> I;
17+
two_field(#two_field{b = I}, _) -> I.
18+
19+
-record(multiple, {a :: integer() | undefined, b :: integer() | undefined}).
20+
21+
-spec multiple(#multiple{}) -> integer().
22+
multiple(#multiple{a = undefined, b = undefined}) -> 0;
23+
multiple(#multiple{a = undefined, b = B}) -> B;
24+
multiple(#multiple{a = A, b = undefined}) -> A;
25+
multiple(#multiple{a = A, b = B}) -> A + B.
26+
27+
-record(underscore, {a :: integer() | undefined, b :: integer() | undefined, c :: integer() | undefined}).
28+
29+
-spec underscore(#underscore{}) -> integer().
30+
underscore(#underscore{_ = undefined}) -> 0;
31+
underscore(#underscore{a = A, _ = undefined}) -> A;
32+
underscore(#underscore{b = B, _ = undefined}) -> B;
33+
underscore(#underscore{c = C, _ = undefined}) -> C;
34+
underscore(#underscore{a = A, b = B, _ = undefined}) -> A + B;
35+
underscore(#underscore{a = A, c = C, _ = undefined}) -> A + C;
36+
underscore(#underscore{b = B, c = C, _ = undefined}) -> B + C;
37+
underscore(#underscore{a = A, b = B, c = C}) -> A + B + C.
38+
39+
-record(type_var, {f :: integer()}).
40+
-spec type_var([#type_var{}]) -> [integer()].
41+
type_var(Rs) -> lists:map(fun (R) -> R#type_var.f end, Rs).
42+
43+
-record(any, {f :: integer()}).
44+
without_spec(R) -> with_spec(R#any.f).
45+
46+
-spec with_spec(integer()) -> integer().
47+
with_spec(I) -> I + 1.
48+
49+
-record(refined_field, {f :: integer() | undefined}).
50+
-spec refined_field(#refined_field{}) -> #refined_field{f :: integer()}.
51+
refined_field(#refined_field{f = undefined}) -> #refined_field{f = 0};
52+
refined_field(R) -> R.
53+
54+
-spec refined_field_safe(#refined_field{f :: integer()}) -> #refined_field{f :: integer()}.
55+
refined_field_safe(#refined_field{f = I}) -> #refined_field{f = I + 1}.
56+
57+
-spec refined_field_unsafe(#refined_field{}) -> #refined_field{}.
58+
refined_field_unsafe(R = #refined_field{f = undefined}) -> R;
59+
refined_field_unsafe(R) -> refined_field_safe(R).
60+
61+
-record(two_level2, {value :: undefined | integer()}).
62+
-record(two_level1, {two_level2 :: undefined | #two_level2{}}).
63+
64+
-spec two_level1(#two_level1{}) -> integer().
65+
two_level1(#two_level1{two_level2 = undefined}) ->
66+
0;
67+
two_level1(#two_level1{two_level2 = #two_level2{value = undefined}}) ->
68+
0;
69+
two_level1(#two_level1{two_level2 = #two_level2{value = Value}}) ->
70+
Value.
71+
72+
-spec two_level2(#two_level1{}) -> integer().
73+
two_level2(#two_level1{two_level2 = undefined}) ->
74+
0;
75+
two_level2(#two_level1{two_level2 = #two_level2{value = undefined}}) ->
76+
0;
77+
two_level2(R1) ->
78+
R1#two_level1.two_level2#two_level2.value.

test/should_pass/records.erl

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
-module(records).
22

3-
-export([f/0, g/1, h/0, i/0, j/0,
3+
-export([f/0, g/1, h/0, i/0, j/0, k/0, l/0,
44
rec_field_subtype/1,
55
rec_index_subtype/0,
66
record_as_tuple/1]).
@@ -32,6 +32,24 @@ i() ->
3232
j() ->
3333
#r.f2.
3434

35+
-record(test_k, {
36+
field :: integer() | undefined
37+
}).
38+
39+
-spec k() -> integer() | undefined.
40+
k() ->
41+
Test = #test_k{},
42+
Test#test_k.field.
43+
44+
-record(test_l, {
45+
field = 0 :: integer()
46+
}).
47+
48+
-spec l() -> integer().
49+
l() ->
50+
Test = #test_l{},
51+
Test#test_l.field.
52+
3553
-spec rec_field_subtype(#r{}) -> number().
3654
rec_field_subtype(R) ->
3755
R#r.f2.

0 commit comments

Comments
 (0)