Skip to content

Commit 4acc76c

Browse files
authored
Merge pull request #10946 from lucioleKi/isabell/debugger/native-record
debugger: Implement native records
2 parents 4524539 + 8ff19d9 commit 4acc76c

6 files changed

Lines changed: 1032 additions & 41 deletions

File tree

lib/compiler/test/native_record_SUITE.erl

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,18 +118,23 @@ local_basic(_Config) ->
118118

119119
%% Test errors when updating native records.
120120
?assertError({badrecord,not_a_record}, (not_a_record)#b{x=99}),
121+
?assertError({badrecord,not_a_record}, (not_a_record)#?MODULE:b{x=99}),
121122
?assertError({badrecord,ARec}, ARec#b{x=99}),
122123
?assertError({badfield,{{?MODULE,b},bad_field}},
123124
BRec#b{bad_field = some_value}),
124125

125126
%% Test errors when accessing native records.
126127
?assertError({badfield,{{?MODULE,b},zoo}}, BRec#b.zoo),
127128
?assertError({badfield,{{?MODULE,b},zoo}}, BRec#?MODULE:b.zoo),
129+
?assertError({badfield,{{?MODULE,b},zoo}}, BRec#_.zoo),
128130
?assertError({badrecord,ARec}, ARec#b.x),
131+
?assertError({badrecord,ARec}, ARec#?MODULE:a.x),
129132
?assertError({badrecord,ARec}, ARec#non_existing_module:rec.x),
133+
?assertError({badrecord,BRec}, BRec#?MODULE:c.zoo),
130134

131135
%% Test errors when accessing literal native records.
132136
?assertError({badrecord,not_a_record}, not_a_record#a.x),
137+
?assertError({badrecord,not_a_record}, not_a_record#_.x),
133138
?assertError({badrecord,not_a_record},
134139
not_a_record#non_existing_module:rec.x),
135140
?assertError({badrecord,not_a_record}, not_a_record#a.x),
@@ -139,6 +144,17 @@ local_basic(_Config) ->
139144
true = is_int_ax(ARec),
140145
false = is_int_ax(id(#a{x=a,y=b})),
141146

147+
false = is_int_ax_guard_2(#b{}),
148+
false = is_int_ax_guard_2(#b{x=42}),
149+
150+
false = is_int_ax_guard_1(#b{}),
151+
false = is_int_ax_guard_1(#b{x=42}),
152+
153+
if
154+
is_integer(ARec#_.x) ->
155+
ok
156+
end,
157+
142158
try id(throw(ARec)) of
143159
_ ->
144160
error(should_fail)
@@ -288,14 +304,17 @@ external_records(_Config) ->
288304
#vector{x=10, y=1, z=5} = DefVector,
289305
#ext_records:vector{x=10, y=1, z=5} = DefVector,
290306
true = records:is_exported(DefVector),
307+
10 = DefVector#vector.x,
291308

292309
ExtLocal = ext_records:local([1,2,3], {a,b,c}),
293310
false = records:is_exported(ExtLocal),
294311

295312
?assertError({badrecord,{ext_records,local}}, #local{a=1, b=2}),
296313
?assertError({badrecord,{ext_records,foreign}}, #ext_records:foreign{a=1, b=2}),
297-
314+
?assertError({badrecord,{ext_records,non_existing}}, #ext_records:non_existing{}),
298315
?assertError({badrecord,ExtLocal}, ExtLocal#local{a=42,b=99}),
316+
?assertError({badrecord,ExtLocal}, ExtLocal#local.c),
317+
?assertError({badrecord,ExtLocal}, ExtLocal#ext_records:non_existing{}),
299318

300319
#local{} = ExtLocal,
301320
#ext_records:local{} = ExtLocal,
@@ -307,6 +326,23 @@ external_records(_Config) ->
307326
ok
308327
end,
309328

329+
if
330+
is_integer(DefVector#ext_records:vector.x) ->
331+
ok
332+
end,
333+
334+
if
335+
is_integer(ExtLocal#ext_records:vector.x) ->
336+
error(should_fail);
337+
true ->
338+
ok
339+
end,
340+
341+
if
342+
is_integer(DefVector#_.x) ->
343+
ok
344+
end,
345+
310346
ok.
311347

312348
any_record(_Config) ->
@@ -333,6 +369,8 @@ any_record(_Config) ->
333369
update_any_xy(id(#exp_abc{}), 0, 0)),
334370
?assertError({badfield,{{?MODULE,exp_x},y}},
335371
update_any_xy(id(#exp_x{}), 0, 0)),
372+
?assertError({badrecord,not_a_record},
373+
update_any_xy(not_a_record, 1, 1)),
336374

337375
{10,1} = get_any_xy(#ext_records:vector{}),
338376
{77,88} = get_any_xy(#ext_records:vector{x=77,y=88}),

lib/debugger/src/dbg_ieval.erl

Lines changed: 204 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,124 @@ expr({map,Line,E0,Fs0}, Bs0, Ieval0) ->
676676
end, E, Fs),
677677
{value,Value,merge_bindings(Bs2, Bs1, Ieval)};
678678

679-
%% Record update
679+
%% Native record
680+
expr({record_field,_Anno,{atom,_,N},V0}, Bs0, Ieval) ->
681+
{value,V1,Bs1} = expr(V0, Bs0, Ieval),
682+
{value, {N,V1}, Bs1};
683+
%% Native record field access
684+
expr({record_field,Line,Src0,{M,N},{atom,_,K}}, Bs0, Ieval) ->
685+
{value, Src1, Bs} = expr(Src0, Bs0, Ieval),
686+
case is_record(Src1, M, N) andalso records:is_exported(Src1) of
687+
true ->
688+
try records:get(K, Src1) of
689+
Val ->
690+
{value, Val, Bs}
691+
catch
692+
_:_ ->
693+
exception(error, {badfield,{{M,N},K}}, Bs, Ieval#ieval{line=Line})
694+
end;
695+
false ->
696+
native_record_error(Line, Src0, Src1, Bs, Ieval)
697+
end;
698+
expr({record_field,Line,Src0,[],{atom,_,K}}, Bs0, Ieval) ->
699+
{value, Src1, Bs} = expr(Src0, Bs0, Ieval),
700+
try records:get(K, Src1) of
701+
Val ->
702+
{value, Val, Bs}
703+
catch
704+
_:_ ->
705+
case is_record(Src1) of
706+
true ->
707+
M = records:get_module(Src1),
708+
N = records:get_name(Src1),
709+
exception(error, {badfield,{{M,N},K}}, Bs, Ieval#ieval{line=Line});
710+
false ->
711+
native_record_error(Line, Src0, Src1, Bs, Ieval)
712+
end
713+
end;
714+
expr({record_field,Line,Src0,N,{atom,_,K}}, Bs0, #ieval{module=M}=Ieval) ->
715+
{value, Src1, Bs} = expr(Src0, Bs0, Ieval),
716+
case is_record(Src1, M, N) of
717+
true ->
718+
try records:get(K, Src1) of
719+
Val ->
720+
{value, Val, Bs}
721+
catch
722+
_:_ ->
723+
exception(error, {badfield,{{M,N},K}}, Bs, Ieval#ieval{line=Line})
724+
end;
725+
false ->
726+
native_record_error(Line, Src0, Src1, Bs, Ieval)
727+
end;
728+
%% Native record creation
729+
expr({record,Line,{M,N},Es}, Bs0, Ieval) ->
730+
try records:get_definition(M, N) of
731+
{#{is_exported := false}, _} ->
732+
exception(error, {badrecord,{M, N}}, Bs0, Ieval#ieval{line=Line});
733+
{Ops, Defs} ->
734+
{Vs, Bs} = eval_list(Es, Bs0, Ieval#ieval{line=Line}),
735+
case native_record_init(Defs, Vs, []) of
736+
{novalue, K} ->
737+
exception(error, {novalue,{{M,N},K}}, Bs0, Ieval#ieval{line=Line});
738+
{badfield, F} ->
739+
exception(error, {badfield,{{M,N},F}}, Bs0, Ieval#ieval{line=Line});
740+
Acc ->
741+
R = records:create(M, N, Acc, Ops),
742+
{value, R, Bs}
743+
end
744+
catch
745+
_:_ ->
746+
exception(error, {badrecord,{M, N}}, Bs0, Ieval#ieval{line=Line})
747+
end;
748+
expr({record,Line,N,Es}, Bs0, #ieval{module=M}=Ieval) when is_atom(N)->
749+
{Ops, Defs} = records:get_definition(M, N),
750+
{Vs, Bs} = eval_list(Es, Bs0, Ieval#ieval{line=Line}),
751+
Acc = native_record_init(Defs, Vs, []),
752+
R = records:create(M, N, Acc, Ops),
753+
{value, R, Bs};
754+
%% Native record update
755+
expr({record,Line,Src0,{M,N},Es}, Bs0, Ieval) ->
756+
{value, Src1, Bs} = expr(Src0, Bs0, Ieval),
757+
case is_record(Src1, M, N) andalso records:is_exported(Src1) of
758+
true ->
759+
{Vs,Bs1} = eval_list(Es, Bs, Ieval#ieval{line=Line}),
760+
Updates = #{K => V || {K, V} <:- Vs},
761+
R = records:update(Src1, M, N, Updates),
762+
{value, R, Bs1};
763+
false ->
764+
native_record_error(Line, Src0, Src1, Bs, Ieval)
765+
end;
766+
expr({record,Line,Src0,[],Es}, Bs0, #ieval{module=Mod}=Ieval) ->
767+
{value, Src1, Bs} = expr(Src0, Bs0, Ieval),
768+
case is_record(Src1) of
769+
true ->
770+
M = records:get_module(Src1),
771+
N = records:get_name(Src1),
772+
case records:is_exported(Src1) orelse Mod =:= M of
773+
true ->
774+
{Vs,Bs} = eval_list(Es, Bs0, Ieval#ieval{line=Line}),
775+
Updates = #{K => V || {K, V} <:- Vs},
776+
R = records:update(Src1, M, N, Updates),
777+
{value, R, Bs};
778+
false ->
779+
native_record_error(Line, Src0, Src1, Bs, Ieval)
780+
end;
781+
false ->
782+
native_record_error(Line, Src0, Src1, Bs, Ieval)
783+
end;
784+
expr({record,Line,Src0,N,Es}, Bs0, #ieval{module=M}=Ieval) when is_atom(N) ->
785+
{value, Src1, Bs} = expr(Src0, Bs0, Ieval),
786+
case is_record(Src1, M, N) of
787+
true ->
788+
{Vs,Bs} = eval_list(Es, Bs0, Ieval#ieval{line=Line}),
789+
Updates = #{K => V || {K, V} <:- Vs},
790+
R = records:update(Src1, M, N, Updates),
791+
{value, R, Bs};
792+
false ->
793+
native_record_error(Line, Src0, Src1, Bs, Ieval)
794+
end;
795+
796+
%% Tuple record update
680797
expr({record_update,Line,Es},Bs,#ieval{level=Le}=Ieval0) ->
681798
%% Incr Level, we don't need to step (next) through temp
682799
%% variables creation and matching
@@ -1347,6 +1464,37 @@ is_generator_end([]) -> true;
13471464
is_generator_end(<<>>) -> true;
13481465
is_generator_end(Other) -> Other =:= #{}.
13491466

1467+
native_record_init([{K, V}|Defs], Vs, Acc) ->
1468+
%% Fill in fields that have default values.
1469+
case lists:keyfind(K, 1, Vs) of
1470+
false ->
1471+
native_record_init(Defs, Vs, [{K, V}|Acc]);
1472+
Res ->
1473+
native_record_init(Defs, Vs, [Res|Acc])
1474+
end;
1475+
native_record_init([K|Defs], Vs, Acc) ->
1476+
%% Fill in fields with no default values.
1477+
case lists:keyfind(K, 1, Vs) of
1478+
false ->
1479+
{novalue, K};
1480+
Res ->
1481+
native_record_init(Defs, Vs, [Res|Acc])
1482+
end;
1483+
native_record_init([], Vs, Acc) ->
1484+
%% Report error for fields that don't exist in the definition.
1485+
case lists:partition(fun({K, _}) -> lists:keyfind(K, 1, Acc) =:= false end, Vs) of
1486+
{[], _} -> lists:reverse(Acc);
1487+
{[{F,_}|_], _} ->{badfield, F}
1488+
end.
1489+
1490+
native_record_error(Line, Src0, Src1, Bs, Ieval) ->
1491+
case Src0 of
1492+
{value, _, Value} ->
1493+
exception(error, {badrecord,Value}, Bs, Ieval#ieval{line=Line});
1494+
_ ->
1495+
exception(error, {badrecord,Src1}, Bs, Ieval#ieval{line=Line})
1496+
end.
1497+
13501498
get_vars(Lit) ->
13511499
get_vars(Lit, []).
13521500

@@ -1851,6 +1999,17 @@ guard_expr({dbg,_,self,[]}, _) ->
18511999
guard_expr({safe_bif,_,erlang,'not',As0}, Bs) ->
18522000
{values,As} = guard_exprs(As0, Bs),
18532001
{value,apply(erlang, 'not', As)};
2002+
guard_expr({record_guard_wildcard_field,K,Src0}, Bs) ->
2003+
{value,Src1} = guard_expr(Src0, Bs),
2004+
{value, records:get(K, Src1)};
2005+
guard_expr({record_guard_field,{Mod,Name},K,Src0}, Bs) ->
2006+
{value,Src1} = guard_expr(Src0, Bs),
2007+
case is_record(Src1, Mod, Name) of
2008+
true ->
2009+
{value, records:get(K, Src1)};
2010+
false ->
2011+
error(failed)
2012+
end;
18542013
guard_expr({safe_bif,_,Mod,Func,As0}, Bs) ->
18552014
{values,As} = guard_exprs(As0, Bs),
18562015
{value,apply(Mod, Func, As)};
@@ -1975,6 +2134,10 @@ match1({tuple,_,Elts}, Tuple, Bs, BBs)
19752134
match_tuple(Elts, Tuple, 1, Bs, BBs);
19762135
match1({map,_,Fields}, Map, Bs, BBs) when is_map(Map) ->
19772136
match_map(Fields, Map, Bs, BBs);
2137+
match1({record,_,_,_}=R, Record, Bs, BBs) when is_record(Record) ->
2138+
match_record(R, Record, Bs, BBs);
2139+
match1({record_wildcard,_,_,_}=R, Record, Bs, BBs) when is_record(Record) ->
2140+
match_record(R, Record, Bs, BBs);
19782141
match1({bin,_,Fs}, B, Bs0, BBs) when is_bitstring(B) ->
19792142
try eval_bits:match_bits(Fs, B, Bs0, BBs,
19802143
match_fun(BBs),
@@ -2013,6 +2176,46 @@ match_map([{map_field_exact,_,K0,Pat}|Fs], Map, Bs0, BBs) ->
20132176
match_map([], _, Bs, _BBs) ->
20142177
{match,Bs}.
20152178

2179+
match_record({record_wildcard, _, Mod, KVs}, R, Bs, BBs) ->
2180+
case {KVs, records:get_module(R), records:is_exported(R)} of
2181+
{[], _, _} ->
2182+
{match, Bs};
2183+
{_, Mod, _} ->
2184+
match_record_field(KVs, R, Bs, BBs);
2185+
{_, _, true} ->
2186+
match_record_field(KVs, R, Bs, BBs);
2187+
_ ->
2188+
throw(nomatch)
2189+
end;
2190+
match_record({record, _, N, KVs}, R, Bs, BBs) ->
2191+
case {N, records:get_module(R), records:get_name(R)} of
2192+
{{Mod, Name}, Mod, Name} -> ok;
2193+
{N, _, N} -> ok;
2194+
_ -> throw(nomatch)
2195+
end,
2196+
%% Check if it's allowed to match fields.
2197+
case {KVs, records:is_exported(R), N} of
2198+
{[], _, _} ->
2199+
{match, Bs};
2200+
{_, true, _} ->
2201+
match_record_field(KVs, R, Bs, BBs);
2202+
{_, false, {_, _}} ->
2203+
throw(nomatch);
2204+
{_, false, _} ->
2205+
match_record_field(KVs, R, Bs, BBs)
2206+
end.
2207+
2208+
match_record_field([{K, V}|KVs], R, Bs0, BBs) ->
2209+
RV = try
2210+
records:get(K, R)
2211+
catch error:_ ->
2212+
throw(nomatch)
2213+
end,
2214+
{match, Bs} = match1(V, RV, Bs0, BBs),
2215+
match_record_field(KVs, R, Bs, BBs);
2216+
match_record_field([], _, Bs, _) ->
2217+
{match, Bs}.
2218+
20162219
head_match([Par|Pars], [Arg|Args], Bs0, BBs) ->
20172220
try match1(Par, Arg, Bs0, BBs) of
20182221
{match,Bs} -> head_match(Pars, Args, Bs, BBs)

0 commit comments

Comments
 (0)