Skip to content

Commit 58b8b93

Browse files
sabiwarajosevalim
authored andcommitted
Auto infer size of matched variable in bitstrings (#13106)
1 parent aef8673 commit 58b8b93

File tree

3 files changed

+38
-22
lines changed

3 files changed

+38
-22
lines changed

lib/elixir/lib/kernel.ex

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2064,9 +2064,6 @@ defmodule Kernel do
20642064
{var, _, nil} when is_atom(var) ->
20652065
invalid_concat_left_argument_error(Atom.to_string(var))
20662066

2067-
{:^, _, [{var, _, nil}]} when is_atom(var) ->
2068-
invalid_concat_left_argument_error("^#{Atom.to_string(var)}")
2069-
20702067
_ ->
20712068
expanded_arg
20722069
end

lib/elixir/src/elixir_bitstring.erl

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ expand(BitstrMeta, Fun, [{'::', Meta, [Left, Right]} | T], Acc, S, E, Alignment,
3535

3636
MatchOrRequireSize = RequireSize or is_match_size(T, EL),
3737
EType = expr_type(ELeft),
38-
{ERight, EAlignment, SS, ES} = expand_specs(EType, Meta, Right, SL, OriginalS, EL, MatchOrRequireSize),
38+
ExpectSize = case ELeft of
39+
{'^', _, [{_, _, _}]} -> {infer, ELeft};
40+
_ when MatchOrRequireSize -> required;
41+
_ -> optional
42+
end,
43+
{ERight, EAlignment, SS, ES} = expand_specs(EType, Meta, Right, SL, OriginalS, EL, ExpectSize),
3944

4045
EAcc = concat_or_prepend_bitstring(Meta, ELeft, ERight, Acc, ES, MatchOrRequireSize),
4146
expand(BitstrMeta, Fun, T, EAcc, {SS, OriginalS}, ES, alignment(Alignment, EAlignment), RequireSize);
@@ -147,7 +152,7 @@ expand_expr(Meta, Component, Fun, S, E) ->
147152

148153
%% Expands and normalizes types of a bitstring.
149154

150-
expand_specs(ExprType, Meta, Info, S, OriginalS, E, RequireSize) ->
155+
expand_specs(ExprType, Meta, Info, S, OriginalS, E, ExpectSize) ->
151156
Default =
152157
#{size => default,
153158
unit => default,
@@ -158,11 +163,17 @@ expand_specs(ExprType, Meta, Info, S, OriginalS, E, RequireSize) ->
158163
expand_each_spec(Meta, unpack_specs(Info, []), Default, S, OriginalS, E),
159164

160165
MergedType = type(Meta, ExprType, Type, E),
161-
validate_size_required(Meta, RequireSize, ExprType, MergedType, Size, ES),
166+
validate_size_required(Meta, ExpectSize, ExprType, MergedType, Size, ES),
162167
SizeAndUnit = size_and_unit(Meta, ExprType, Size, Unit, ES),
163168
Alignment = compute_alignment(MergedType, Size, Unit),
164169

165-
[H | T] = build_spec(Meta, Size, Unit, MergedType, Endianness, Sign, SizeAndUnit, ES),
170+
MaybeInferredSize = case {ExpectSize, MergedType, SizeAndUnit} of
171+
{{infer, PinnedVar}, binary, []} -> [{size, Meta, [{{'.', Meta, [erlang, byte_size]}, Meta, [PinnedVar]}]}];
172+
{{infer, PinnedVar}, bitstring, []} -> [{size, Meta, [{{'.', Meta, [erlang, bit_size]}, Meta, [PinnedVar]}]}];
173+
_ -> SizeAndUnit
174+
end,
175+
176+
[H | T] = build_spec(Meta, Size, Unit, MergedType, Endianness, Sign, MaybeInferredSize, ES),
166177
{lists:foldl(fun(I, Acc) -> {'-', Meta, [Acc, I]} end, H, T), Alignment, SS, ES}.
167178

168179
type(_, default, default, _) ->
@@ -276,7 +287,7 @@ validate_spec_arg(Meta, unit, Value, _S, _OriginalS, E) when not is_integer(Valu
276287
validate_spec_arg(_Meta, _Key, _Value, _S, _OriginalS, _E) ->
277288
ok.
278289

279-
validate_size_required(Meta, true, default, Type, default, E) when Type == binary; Type == bitstring ->
290+
validate_size_required(Meta, required, default, Type, default, E) when Type == binary; Type == bitstring ->
280291
function_error(Meta, E, ?MODULE, unsized_binary),
281292
ok;
282293
validate_size_required(_, _, _, _, _, _) ->

lib/elixir/test/elixir/kernel/binary_test.exs

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -128,20 +128,6 @@ defmodule Kernel.BinaryTest do
128128
assert_raise ArgumentError, message, fn ->
129129
Code.eval_string(~s["a" <> b <> "c" = "abc"])
130130
end
131-
132-
assert_raise ArgumentError, message, fn ->
133-
Code.eval_string(~s[
134-
a = "a"
135-
^a <> "b" = "ab"
136-
])
137-
end
138-
139-
assert_raise ArgumentError, message, fn ->
140-
Code.eval_string(~s[
141-
b = "b"
142-
"a" <> ^b <> "c" = "abc"
143-
])
144-
end
145131
end
146132

147133
test "hex" do
@@ -269,6 +255,28 @@ defmodule Kernel.BinaryTest do
269255
assert <<1::size((^foo).bar)>> = <<1::5>>
270256
end
271257

258+
test "automatic size computation of matched bitsyntax variable" do
259+
var = "foo"
260+
<<^var::binary, rest::binary>> = "foobar"
261+
assert rest == "bar"
262+
263+
<<^var::bytes, rest::bytes>> = "foobar"
264+
assert rest == "bar"
265+
266+
^var <> rest = "foobar"
267+
assert rest == "bar"
268+
269+
var = <<0, 1>>
270+
<<^var::bitstring, rest::bitstring>> = <<0, 1, 2, 3>>
271+
assert rest == <<2, 3>>
272+
273+
<<^var::bits, rest::bits>> = <<0, 1, 2, 3>>
274+
assert rest == <<2, 3>>
275+
276+
^var <> rest = <<0, 1, 2, 3>>
277+
assert rest == <<2, 3>>
278+
end
279+
272280
defmacro signed_16 do
273281
quote do
274282
big - signed - integer - unit(16)

0 commit comments

Comments
 (0)