Skip to content

Commit 1f92332

Browse files
committed
Improve error messages when matching on size
1 parent d127196 commit 1f92332

File tree

6 files changed

+53
-42
lines changed

6 files changed

+53
-42
lines changed

lib/elixir/lib/module/types.ex

+1-3
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,7 @@ defmodule Module.Types do
7474
# List of calls to not warn on as undefined
7575
no_warn_undefined: no_warn_undefined,
7676
# A list of cached modules received from the parallel compiler
77-
cache: cache,
78-
# If variable refinements is enabled or not
79-
refine: true
77+
cache: cache
8078
}
8179
end
8280

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,9 @@ defmodule Module.Types.Expr do
403403
{"rescue #{expr_to_string(expr)} ->", hints}
404404
end
405405

406-
{:ok, _type, context} = Of.refine_var(var, expected, expr, formatter, stack, context)
406+
{:ok, _type, context} =
407+
Of.refine_var(var, {expected, expr}, formatter, stack, context)
408+
407409
context
408410
end
409411

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

+7-7
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ defmodule Module.Types.Of do
2929
@doc """
3030
Refines the type of a variable.
3131
"""
32-
def refine_var(var, type, expr, formatter \\ :default, stack, context) do
32+
def refine_var(var, {type, expr}, formatter \\ :default, stack, context) do
3333
{var_name, meta, var_context} = var
3434
version = Keyword.fetch!(meta, :version)
3535

@@ -251,7 +251,7 @@ defmodule Module.Types.Of do
251251
end
252252

253253
with {:ok, _type, context} <- result do
254-
{:ok, specifier_size(kind, right, stack, context)}
254+
{:ok, specifier_size(kind, right, expr, stack, context)}
255255
end
256256
end
257257

@@ -271,27 +271,27 @@ defmodule Module.Types.Of do
271271
defp specifier_type(_kind, {:binary, _, _}), do: @binary
272272
defp specifier_type(_kind, _specifier), do: @integer
273273

274-
defp specifier_size(kind, {:-, _, [left, right]}, stack, context) do
275-
specifier_size(kind, right, stack, specifier_size(kind, left, stack, context))
274+
defp specifier_size(kind, {:-, _, [left, right]}, expr, stack, context) do
275+
specifier_size(kind, right, expr, stack, specifier_size(kind, left, expr, stack, context))
276276
end
277277

278-
defp specifier_size(:expr, {:size, _, [arg]} = expr, stack, context)
278+
defp specifier_size(:expr, {:size, _, [arg]}, expr, stack, context)
279279
when not is_integer(arg) do
280280
case Module.Types.Expr.of_expr(arg, {integer(), expr}, stack, context) do
281281
{:ok, _, context} -> context
282282
{:error, context} -> context
283283
end
284284
end
285285

286-
defp specifier_size(_pattern_or_guard, {:size, _, [arg]} = expr, stack, context)
286+
defp specifier_size(_pattern_or_guard, {:size, _, [arg]}, expr, stack, context)
287287
when not is_integer(arg) do
288288
case Module.Types.Pattern.of_guard(arg, {integer(), expr}, stack, context) do
289289
{:ok, _, context} -> context
290290
{:error, context} -> context
291291
end
292292
end
293293

294-
defp specifier_size(_kind, _expr, _stack, context) do
294+
defp specifier_size(_kind, _specifier, _expr, _stack, context) do
295295
context
296296
end
297297

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

+34-29
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,6 @@ defmodule Module.Types.Pattern do
3838
of_pattern(expr, {dynamic(), expr}, stack, context)
3939
end
4040

41-
# ^var
42-
def of_pattern({:^, _meta, [var]}, expected_expr, stack, context) do
43-
Of.intersect(Of.var(var, context), expected_expr, stack, context)
44-
end
45-
4641
# left = right
4742
# TODO: Track variables and handle nesting
4843
def of_pattern({:=, _meta, [left_expr, right_expr]}, {expected, expr}, stack, context) do
@@ -73,7 +68,7 @@ defmodule Module.Types.Pattern do
7368
)
7469
when not is_atom(struct_var) do
7570
with {:ok, struct_type, context} <-
76-
of_pattern(struct_var, {atom(), expr}, %{stack | refine: false}, context),
71+
of_struct_var(struct_var, {atom(), expr}, stack, context),
7772
{:ok, map_type, context} <-
7873
of_open_map(args, [__struct__: struct_type], expected_expr, stack, context),
7974
{_, struct_type} = map_fetch(map_type, :__struct__),
@@ -111,23 +106,8 @@ defmodule Module.Types.Pattern do
111106
end
112107

113108
# var
114-
def of_pattern({name, meta, ctx} = var, {expected, expr}, stack, context)
115-
when is_atom(name) and is_atom(ctx) do
116-
case stack do
117-
%{refine: true} ->
118-
Of.refine_var(var, expected, expr, stack, context)
119-
120-
%{refine: false} ->
121-
version = Keyword.fetch!(meta, :version)
122-
123-
case context do
124-
%{vars: %{^version => %{type: type}}} ->
125-
Of.intersect(type, {expected, expr}, stack, context)
126-
127-
%{} ->
128-
{:ok, expected, context}
129-
end
130-
end
109+
def of_pattern(var, expected_expr, stack, context) when is_var(var) do
110+
Of.refine_var(var, expected_expr, stack, context)
131111
end
132112

133113
def of_pattern(expr, expected_expr, stack, context) do
@@ -198,21 +178,39 @@ defmodule Module.Types.Pattern do
198178
end
199179
end
200180

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-
207181
# var
208182
def of_guard(var, expected_expr, stack, context) when is_var(var) do
183+
# TODO: This should be ver refinement once we have inference in guards
184+
# Of.refine_var(var, expected_expr, stack, context)
209185
Of.intersect(Of.var(var, context), expected_expr, stack, context)
210186
end
211187

212188
def of_guard(expr, expected_expr, stack, context) do
213189
of_shared(expr, expected_expr, stack, context, &of_guard/4)
214190
end
215191

192+
## Helpers
193+
194+
defp of_struct_var({:_, _, _}, {expected, _expr}, _stack, context) do
195+
{:ok, expected, context}
196+
end
197+
198+
defp of_struct_var({:^, _, [var]}, expected_expr, stack, context) do
199+
Of.intersect(Of.var(var, context), expected_expr, stack, context)
200+
end
201+
202+
defp of_struct_var({_name, meta, _ctx}, expected_expr, stack, context) do
203+
version = Keyword.fetch!(meta, :version)
204+
205+
case context do
206+
%{vars: %{^version => %{type: type}}} ->
207+
Of.intersect(type, expected_expr, stack, context)
208+
209+
%{} ->
210+
{:ok, elem(expected_expr, 0), context}
211+
end
212+
end
213+
216214
## Shared
217215

218216
# :atom
@@ -269,6 +267,12 @@ defmodule Module.Types.Pattern do
269267
of_shared({:{}, [], [left, right]}, expected_expr, stack, context, fun)
270268
end
271269

270+
# ^var
271+
defp of_shared({:^, _meta, [var]}, expected_expr, stack, context, _fun) do
272+
# This is by definition a variable defined outside of this pattern, so we don't track it.
273+
Of.intersect(Of.var(var, context), expected_expr, stack, context)
274+
end
275+
272276
# left | []
273277
defp of_shared({:|, _meta, [left_expr, []]}, _expected_expr, stack, context, fun) do
274278
fun.(left_expr, {dynamic(), left_expr}, stack, context)
@@ -303,6 +307,7 @@ defmodule Module.Types.Pattern do
303307
end
304308

305309
# {...}
310+
# TODO: Implement this
306311
defp of_shared({:{}, _meta, exprs}, _expected_expr, stack, context, fun) do
307312
case map_reduce_ok(exprs, context, &fun.(&1, {dynamic(), &1}, stack, &2)) do
308313
{:ok, types, context} -> {:ok, tuple(types), context}

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ defmodule Module.Types.ExprTest do
210210
~l"""
211211
incompatible types in expression:
212212
213-
size(x)
213+
<<y::integer-size(x)>>
214214
215215
expected type:
216216
@@ -225,6 +225,12 @@ defmodule Module.Types.ExprTest do
225225
# type: binary()
226226
# from: types_test.ex:LINE-2
227227
<<x::binary>>
228+
229+
where "y" was given the type:
230+
231+
# type: dynamic()
232+
# from: types_test.ex:208
233+
y
228234
"""}
229235
end
230236
end

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ defmodule Module.Types.PatternTest do
142142
~l"""
143143
incompatible types in expression:
144144
145-
size(x)
145+
<<..., _::integer-size(x)>>
146146
147147
expected type:
148148

0 commit comments

Comments
 (0)