Skip to content

Commit 03cc629

Browse files
committed
Type check size in binaries
1 parent cc0cb4f commit 03cc629

File tree

5 files changed

+159
-64
lines changed

5 files changed

+159
-64
lines changed

lib/elixir/lib/module/types/expr.ex

+3-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ defmodule Module.Types.Expr do
2727
@atom_true atom([true])
2828
@exception open_map(__struct__: atom(), __exception__: @atom_true)
2929

30-
defp of_expr(expr, expected_expr, stack, context) do
30+
# of_expr/4 is public as it is called recursively from Of.binary
31+
def of_expr(expr, expected_expr, stack, context) do
3132
with {:ok, actual, context} <- of_expr(expr, stack, context) do
3233
Of.intersect(actual, expected_expr, stack, context)
3334
end
@@ -81,7 +82,7 @@ defmodule Module.Types.Expr do
8182

8283
# <<...>>>
8384
def of_expr({:<<>>, _meta, args}, stack, context) do
84-
case Of.binary(args, :expr, stack, context, &of_expr/4) do
85+
case Of.binary(args, :expr, stack, context) do
8586
{:ok, context} -> {:ok, binary(), context}
8687
# It is safe to discard errors from binary inside expressions
8788
{:error, context} -> {:ok, binary(), context}

lib/elixir/lib/module/types/of.ex

+64-33
Original file line numberDiff line numberDiff line change
@@ -206,63 +206,94 @@ defmodule Module.Types.Of do
206206
In the stack, we add nodes such as <<expr>>, <<..., expr>>, etc,
207207
based on the position of the expression within the binary.
208208
"""
209-
def binary([], _kind, _stack, context, _of_fun) do
209+
def binary([], _kind, _stack, context) do
210210
{:ok, context}
211211
end
212212

213-
def binary([head], kind, stack, context, of_fun) do
214-
binary_segment(head, kind, [head], stack, context, of_fun)
213+
def binary([head], kind, stack, context) do
214+
binary_segment(head, kind, [head], stack, context)
215215
end
216216

217-
def binary([head | tail], kind, stack, context, of_fun) do
218-
case binary_segment(head, kind, [head, @suffix], stack, context, of_fun) do
219-
{:ok, context} -> binary_many(tail, kind, stack, context, of_fun)
220-
{:error, reason} -> {:error, reason}
217+
def binary([head | tail], kind, stack, context) do
218+
case binary_segment(head, kind, [head, @suffix], stack, context) do
219+
{:ok, context} -> binary_many(tail, kind, stack, context)
220+
{:error, context} -> {:error, context}
221221
end
222222
end
223223

224-
defp binary_many([last], kind, stack, context, of_fun) do
225-
binary_segment(last, kind, [@prefix, last], stack, context, of_fun)
224+
defp binary_many([last], kind, stack, context) do
225+
binary_segment(last, kind, [@prefix, last], stack, context)
226226
end
227227

228-
defp binary_many([head | tail], kind, stack, context, of_fun) do
229-
case binary_segment(head, kind, [@prefix, head, @suffix], stack, context, of_fun) do
230-
{:ok, context} -> binary_many(tail, kind, stack, context, of_fun)
231-
{:error, reason} -> {:error, reason}
228+
defp binary_many([head | tail], kind, stack, context) do
229+
case binary_segment(head, kind, [@prefix, head, @suffix], stack, context) do
230+
{:ok, context} -> binary_many(tail, kind, stack, context)
231+
{:error, context} -> {:error, context}
232232
end
233233
end
234234

235235
# If the segment is a literal, the compiler has already checked its validity,
236236
# so we just skip it.
237-
defp binary_segment({:"::", _meta, [left, _right]}, _kind, _args, _stack, context, _of_fun)
237+
defp binary_segment({:"::", _meta, [left, _right]}, _kind, _args, _stack, context)
238238
when is_binary(left) or is_number(left) do
239239
{:ok, context}
240240
end
241241

242-
defp binary_segment({:"::", meta, [left, right]}, kind, args, stack, context, of_fun) do
243-
expected_type = specifier_info(kind, right)
242+
defp binary_segment({:"::", meta, [left, right]}, kind, args, stack, context) do
243+
expected_type = specifier_type(kind, right)
244244
expr = {:<<>>, meta, args}
245245

246-
with {:ok, _type, context} <- of_fun.(left, {expected_type, expr}, stack, context) do
247-
{:ok, context}
246+
result =
247+
case kind do
248+
:pattern -> Module.Types.Pattern.of_pattern(left, {expected_type, expr}, stack, context)
249+
:guard -> Module.Types.Pattern.of_guard(left, {expected_type, expr}, stack, context)
250+
:expr -> Module.Types.Expr.of_expr(left, {expected_type, expr}, stack, context)
251+
end
252+
253+
with {:ok, _type, context} <- result do
254+
{:ok, specifier_size(kind, right, stack, context)}
255+
end
256+
end
257+
258+
defp specifier_type(kind, {:-, _, [left, _right]}), do: specifier_type(kind, left)
259+
defp specifier_type(:pattern, {:utf8, _, _}), do: @integer
260+
defp specifier_type(:pattern, {:utf16, _, _}), do: @integer
261+
defp specifier_type(:pattern, {:utf32, _, _}), do: @integer
262+
defp specifier_type(:pattern, {:float, _, _}), do: @float
263+
defp specifier_type(_kind, {:float, _, _}), do: @integer_or_float
264+
defp specifier_type(_kind, {:utf8, _, _}), do: @integer_or_binary
265+
defp specifier_type(_kind, {:utf16, _, _}), do: @integer_or_binary
266+
defp specifier_type(_kind, {:utf32, _, _}), do: @integer_or_binary
267+
defp specifier_type(_kind, {:integer, _, _}), do: @integer
268+
defp specifier_type(_kind, {:bits, _, _}), do: @binary
269+
defp specifier_type(_kind, {:bitstring, _, _}), do: @binary
270+
defp specifier_type(_kind, {:bytes, _, _}), do: @binary
271+
defp specifier_type(_kind, {:binary, _, _}), do: @binary
272+
defp specifier_type(_kind, _specifier), do: @integer
273+
274+
defp specifier_size(kind, {:-, _, [left, right]}, stack, context) do
275+
specifier_size(kind, right, stack, specifier_size(kind, left, stack, context))
276+
end
277+
278+
defp specifier_size(:expr, {:size, _, [arg]} = expr, stack, context)
279+
when not is_integer(arg) do
280+
case Module.Types.Expr.of_expr(arg, {integer(), expr}, stack, context) do
281+
{:ok, _, context} -> context
282+
{:error, context} -> context
248283
end
249284
end
250285

251-
defp specifier_info(kind, {:-, _, [left, _right]}), do: specifier_info(kind, left)
252-
defp specifier_info(:expr, {:float, _, _}), do: @integer_or_float
253-
defp specifier_info(:expr, {:utf8, _, _}), do: @integer_or_binary
254-
defp specifier_info(:expr, {:utf16, _, _}), do: @integer_or_binary
255-
defp specifier_info(:expr, {:utf32, _, _}), do: @integer_or_binary
256-
defp specifier_info(:pattern, {:utf8, _, _}), do: @integer
257-
defp specifier_info(:pattern, {:utf16, _, _}), do: @integer
258-
defp specifier_info(:pattern, {:utf32, _, _}), do: @integer
259-
defp specifier_info(:pattern, {:float, _, _}), do: @float
260-
defp specifier_info(_kind, {:integer, _, _}), do: @integer
261-
defp specifier_info(_kind, {:bits, _, _}), do: @binary
262-
defp specifier_info(_kind, {:bitstring, _, _}), do: @binary
263-
defp specifier_info(_kind, {:bytes, _, _}), do: @binary
264-
defp specifier_info(_kind, {:binary, _, _}), do: @binary
265-
defp specifier_info(_kind, _specifier), do: @integer
286+
defp specifier_size(_pattern_or_guard, {:size, _, [arg]} = expr, stack, context)
287+
when not is_integer(arg) do
288+
case Module.Types.Pattern.of_guard(arg, {integer(), expr}, stack, context) do
289+
{:ok, _, context} -> context
290+
{:error, context} -> context
291+
end
292+
end
293+
294+
defp specifier_size(_kind, _expr, _stack, context) do
295+
context
296+
end
266297

267298
## Apply
268299

lib/elixir/lib/module/types/pattern.ex

+35-28
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,22 @@ defmodule Module.Types.Pattern do
3030
end
3131

3232
## Patterns
33+
# of_pattern is public as it is called recursively from Of.binary
3334

34-
defp of_pattern(expr, stack, context) do
35-
# TODO: Remove the hardcoding of dynamic
36-
# TODO: Possibly remove this function
35+
# TODO: Remove the hardcoding of dynamic
36+
# TODO: Remove this function
37+
def of_pattern(expr, stack, context) do
3738
of_pattern(expr, {dynamic(), expr}, stack, context)
3839
end
3940

4041
# ^var
41-
defp of_pattern({:^, _meta, [var]}, expected_expr, stack, context) do
42+
def of_pattern({:^, _meta, [var]}, expected_expr, stack, context) do
4243
Of.intersect(Of.var(var, context), expected_expr, stack, context)
4344
end
4445

4546
# left = right
4647
# TODO: Track variables and handle nesting
47-
defp of_pattern({:=, _meta, [left_expr, right_expr]}, {expected, expr}, stack, context) do
48+
def of_pattern({:=, _meta, [left_expr, right_expr]}, {expected, expr}, stack, context) do
4849
case {is_var(left_expr), is_var(right_expr)} do
4950
{true, false} ->
5051
with {:ok, type, context} <- of_pattern(right_expr, {expected, expr}, stack, context) do
@@ -64,13 +65,13 @@ defmodule Module.Types.Pattern do
6465
end
6566

6667
# %var{...} and %^var{...}
67-
defp of_pattern(
68-
{:%, _meta, [struct_var, {:%{}, _meta2, args}]} = expr,
69-
expected_expr,
70-
stack,
71-
context
72-
)
73-
when not is_atom(struct_var) do
68+
def of_pattern(
69+
{:%, _meta, [struct_var, {:%{}, _meta2, args}]} = expr,
70+
expected_expr,
71+
stack,
72+
context
73+
)
74+
when not is_atom(struct_var) do
7475
with {:ok, struct_type, context} <-
7576
of_pattern(struct_var, {atom(), expr}, %{stack | refine: false}, context),
7677
{:ok, map_type, context} <-
@@ -83,35 +84,35 @@ defmodule Module.Types.Pattern do
8384
end
8485

8586
# %Struct{...}
86-
defp of_pattern({:%, _meta, [module, {:%{}, _, args}]} = expr, expected_expr, stack, context)
87-
when is_atom(module) do
87+
def of_pattern({:%, _meta, [module, {:%{}, _, args}]} = expr, expected_expr, stack, context)
88+
when is_atom(module) do
8889
with {:ok, actual, context} <-
8990
Of.struct(expr, module, args, :merge_defaults, stack, context, &of_pattern/3) do
9091
Of.intersect(actual, expected_expr, stack, context)
9192
end
9293
end
9394

9495
# %{...}
95-
defp of_pattern({:%{}, _meta, args}, expected_expr, stack, context) do
96+
def of_pattern({:%{}, _meta, args}, expected_expr, stack, context) do
9697
of_open_map(args, [], expected_expr, stack, context)
9798
end
9899

99100
# <<...>>>
100-
defp of_pattern({:<<>>, _meta, args}, _expected_expr, stack, context) do
101-
case Of.binary(args, :pattern, stack, context, &of_pattern/4) do
101+
def of_pattern({:<<>>, _meta, args}, _expected_expr, stack, context) do
102+
case Of.binary(args, :pattern, stack, context) do
102103
{:ok, context} -> {:ok, binary(), context}
103104
{:error, context} -> {:error, context}
104105
end
105106
end
106107

107108
# _
108-
defp of_pattern({:_, _meta, _var_context}, {expected, _expr}, _stack, context) do
109+
def of_pattern({:_, _meta, _var_context}, {expected, _expr}, _stack, context) do
109110
{:ok, expected, context}
110111
end
111112

112113
# var
113-
defp of_pattern({name, meta, ctx} = var, {expected, expr}, stack, context)
114-
when is_atom(name) and is_atom(ctx) do
114+
def of_pattern({name, meta, ctx} = var, {expected, expr}, stack, context)
115+
when is_atom(name) and is_atom(ctx) do
115116
case stack do
116117
%{refine: true} ->
117118
Of.refine_var(var, expected, expr, stack, context)
@@ -129,7 +130,7 @@ defmodule Module.Types.Pattern do
129130
end
130131
end
131132

132-
defp of_pattern(expr, expected_expr, stack, context) do
133+
def of_pattern(expr, expected_expr, stack, context) do
133134
of_shared(expr, expected_expr, stack, context, &of_pattern/4)
134135
end
135136

@@ -151,11 +152,11 @@ defmodule Module.Types.Pattern do
151152
end
152153
end
153154

154-
@doc """
155-
Refines the type variables in the typing context using type check guards
156-
such as `is_integer/1`.
157-
"""
155+
## Guards
156+
# of_guard is public as it is called recursively from Of.binary
158157

158+
# TODO: Remove the hardcoding of dynamic
159+
# TODO: Remove this function
159160
def of_guard(expr, stack, context) do
160161
of_guard(expr, {dynamic(), expr}, stack, context)
161162
end
@@ -173,7 +174,7 @@ defmodule Module.Types.Pattern do
173174

174175
# <<>>
175176
def of_guard({:<<>>, _meta, args}, _expected_expr, stack, context) do
176-
case Of.binary(args, :expr, stack, context, &of_guard/4) do
177+
case Of.binary(args, :guard, stack, context) do
177178
{:ok, context} -> {:ok, binary(), context}
178179
# It is safe to discard errors from binary inside expressions
179180
{:error, context} -> {:ok, binary(), context}
@@ -197,9 +198,15 @@ defmodule Module.Types.Pattern do
197198
end
198199
end
199200

201+
# ^var
202+
# Happens from inside size(^...) and map keys
203+
def of_guard({:^, _meta, [var]}, expected_expr, stack, context) do
204+
Of.intersect(Of.var(var, context), expected_expr, stack, context)
205+
end
206+
200207
# var
201-
def of_guard(var, _expected_expr, _stack, context) when is_var(var) do
202-
{:ok, Of.var(var, context), context}
208+
def of_guard(var, expected_expr, stack, context) when is_var(var) do
209+
Of.intersect(Of.var(var, context), expected_expr, stack, context)
203210
end
204211

205212
def of_guard(expr, expected_expr, stack, context) do

lib/elixir/test/elixir/module/types/expr_test.exs

+29-1
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,34 @@ defmodule Module.Types.ExprTest do
199199
#{hints(:inferred_bitstring_spec)}
200200
"""}
201201
end
202+
203+
test "size ok" do
204+
assert typecheck!([<<x, y>>, z], <<z::size(x - y)>>) == binary()
205+
end
206+
207+
test "size error" do
208+
assert typewarn!([<<x::binary>>, y], <<y::size(x)>>) ==
209+
{binary(),
210+
~l"""
211+
incompatible types in expression:
212+
213+
size(x)
214+
215+
expected type:
216+
217+
integer()
218+
219+
but got type:
220+
221+
binary()
222+
223+
where "x" was given the type:
224+
225+
# type: binary()
226+
# from: types_test.ex:LINE-2
227+
<<x::binary>>
228+
"""}
229+
end
202230
end
203231

204232
describe "tuples" do
@@ -544,7 +572,7 @@ defmodule Module.Types.ExprTest do
544572
where "x" was given the type:
545573
546574
# type: integer()
547-
# from: types_test.ex:533
575+
# from: types_test.ex:LINE-2
548576
<<x>>
549577
550578
#{hints(:inferred_bitstring_spec)}

lib/elixir/test/elixir/module/types/pattern_test.exs

+28
Original file line numberDiff line numberDiff line change
@@ -131,5 +131,33 @@ defmodule Module.Types.PatternTest do
131131
#{hints(:inferred_bitstring_spec)}
132132
"""
133133
end
134+
135+
test "size ok" do
136+
assert typecheck!([<<x, y, _::size(x - y)>>], :ok) == atom([:ok])
137+
end
138+
139+
test "size error" do
140+
assert typewarn!([<<x::float, _::size(x)>>], :ok) ==
141+
{atom([:ok]),
142+
~l"""
143+
incompatible types in expression:
144+
145+
size(x)
146+
147+
expected type:
148+
149+
integer()
150+
151+
but got type:
152+
153+
float()
154+
155+
where "x" was given the type:
156+
157+
# type: float()
158+
# from: types_test.ex:LINE-2
159+
<<x::float, ...>>
160+
"""}
161+
end
134162
end
135163
end

0 commit comments

Comments
 (0)