Skip to content

Commit 47dce5d

Browse files
committed
Use compatibility checks as much as possible
1 parent 3a783bd commit 47dce5d

File tree

7 files changed

+67
-60
lines changed

7 files changed

+67
-60
lines changed

lib/elixir/lib/module/types/descr.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1885,6 +1885,7 @@ defmodule Module.Types.Descr do
18851885
end
18861886

18871887
:open ->
1888+
fields = Map.to_list(fields)
18881889
{:%{}, [], [{:..., [], nil} | map_fields_to_quoted(tag, Enum.sort(fields), opts)]}
18891890
end
18901891
end

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ defmodule Module.Types.Expr do
535535
if stack.mode == :traversal do
536536
{dynamic(), context}
537537
else
538-
Of.refine_existing_var(var, expected, expr, stack, context)
538+
Of.refine_body_var(var, expected, expr, stack, context)
539539
end
540540
end
541541

@@ -567,7 +567,10 @@ defmodule Module.Types.Expr do
567567
_ ->
568568
expected = if structs == [], do: @exception, else: Enum.reduce(structs, &union/2)
569569
formatter = fn expr -> {"rescue #{expr_to_string(expr)} ->", hints} end
570-
{_ok?, _type, context} = Of.refine_var(var, expected, expr, formatter, stack, context)
570+
571+
{_ok?, _type, context} =
572+
Of.refine_head_var(var, expected, expr, formatter, stack, context)
573+
571574
context
572575
end
573576

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

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ defmodule Module.Types.Of do
4242
end
4343

4444
@doc """
45-
Refines a variable that already exists.
45+
Refines a variable that already exists (in a body).
4646
4747
This only happens if the var contains a gradual type,
4848
or if we are doing a guard analysis or occurrence typing.
4949
Returns `true` if there was a refinement, `false` otherwise.
5050
"""
51-
def refine_existing_var({_, meta, _}, type, expr, stack, context) do
51+
def refine_body_var({_, meta, _}, type, expr, stack, context) do
5252
version = Keyword.fetch!(meta, :version)
5353
%{vars: %{^version => %{type: old_type, off_traces: off_traces} = data} = vars} = context
5454

@@ -76,8 +76,12 @@ defmodule Module.Types.Of do
7676

7777
@doc """
7878
Refines the type of a variable.
79+
80+
Since this happens in a head, we use intersection
81+
because we want to refine types. Otherwise we should
82+
use compatibility.
7983
"""
80-
def refine_var(var, type, expr, formatter \\ :default, stack, context) do
84+
def refine_head_var(var, type, expr, formatter \\ :default, stack, context) do
8185
{var_name, meta, var_context} = var
8286
version = Keyword.fetch!(meta, :version)
8387

@@ -96,7 +100,7 @@ defmodule Module.Types.Of do
96100
# We need to return error otherwise it leads to cascading errors
97101
if empty?(new_type) do
98102
{:error, error_type(),
99-
error({:refine_var, old_type, type, var, context}, meta, stack, context)}
103+
error({:refine_head_var, old_type, type, var, context}, meta, stack, context)}
100104
else
101105
{:ok, new_type, context}
102106
end
@@ -357,12 +361,13 @@ defmodule Module.Types.Of do
357361
Module.Types.Pattern.of_match_var(left, type, expr, stack, context)
358362

359363
:guard ->
360-
Module.Types.Pattern.of_guard(left, type, expr, stack, context)
364+
{actual, context} = Module.Types.Pattern.of_guard(left, type, expr, stack, context)
365+
compatible(actual, type, expr, stack, context)
361366

362367
:expr ->
363368
left = annotate_interpolation(left, right)
364369
{actual, context} = Module.Types.Expr.of_expr(left, {type, expr}, stack, context)
365-
intersect(actual, type, expr, stack, context)
370+
compatible(actual, type, expr, stack, context)
366371
end
367372

368373
specifier_size(kind, right, stack, context)
@@ -402,13 +407,14 @@ defmodule Module.Types.Of do
402407
defp specifier_size(:expr, {:size, _, [arg]} = expr, stack, context)
403408
when not is_integer(arg) do
404409
{actual, context} = Module.Types.Expr.of_expr(arg, {integer(), expr}, stack, context)
405-
{_, context} = intersect(actual, integer(), expr, stack, context)
410+
{_, context} = compatible(actual, integer(), expr, stack, context)
406411
context
407412
end
408413

409414
defp specifier_size(_pattern_or_guard, {:size, _, [arg]} = expr, stack, context)
410415
when not is_integer(arg) do
411-
{_type, context} = Module.Types.Pattern.of_guard(arg, integer(), expr, stack, context)
416+
{actual, context} = Module.Types.Pattern.of_guard(arg, integer(), expr, stack, context)
417+
{_, context} = compatible(actual, integer(), expr, stack, context)
412418
context
413419
end
414420

@@ -437,15 +443,14 @@ defmodule Module.Types.Of do
437443
## Warning helpers
438444

439445
@doc """
440-
Intersects two types and emit an incompatible error if empty.
446+
Checks if two types are compatible and emit an incompatible error if not.
441447
"""
442-
def intersect(actual, expected, expr, stack, context) do
443-
type = intersection(actual, expected)
444-
445-
if empty?(type) do
446-
{error_type(), incompatible_error(expr, expected, actual, stack, context)}
448+
# TODO: Consider getting rid of this and emitting precise errors instead.
449+
def compatible(actual, expected, expr, stack, context) do
450+
if compatible?(actual, expected) do
451+
{actual, context}
447452
else
448-
{type, context}
453+
{error_type(), incompatible_error(expr, expected, actual, stack, context)}
449454
end
450455
end
451456

@@ -468,7 +473,7 @@ defmodule Module.Types.Of do
468473

469474
## Warning formatting
470475

471-
def format_diagnostic({:refine_var, old_type, new_type, var, context}) do
476+
def format_diagnostic({:refine_head_var, old_type, new_type, var, context}) do
472477
traces = collect_traces(var, context)
473478

474479
%{

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

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ defmodule Module.Types.Pattern do
183183
{var_changed?, context}
184184
else
185185
_ ->
186-
case Of.refine_var(var, type, expr, stack, context) do
186+
case Of.refine_head_var(var, type, expr, stack, context) do
187187
{:ok, _type, context} -> {var_changed? or reachable_var?, context}
188188
{:error, _type, context} -> throw(context)
189189
end
@@ -337,23 +337,27 @@ defmodule Module.Types.Pattern do
337337
@doc """
338338
Function used to assign a type to a variable. Used by %struct{}
339339
and binary patterns.
340+
341+
Given those values are actually checked at compile-time,
342+
except for the variables, that's the only scenario we need to handle.
340343
"""
344+
# TODO: Perhaps merge this with guards
341345
def of_match_var({:^, _, [var]}, expected, expr, stack, context) do
342-
{type, context} = Of.refine_existing_var(var, expected, expr, stack, context)
343-
Of.intersect(type, expected, expr, stack, context)
346+
{type, context} = Of.refine_body_var(var, expected, expr, stack, context)
347+
Of.compatible(type, expected, expr, stack, context)
344348
end
345349

346350
def of_match_var({:_, _, _}, expected, _expr, _stack, context) do
347351
{expected, context}
348352
end
349353

350354
def of_match_var(var, expected, expr, stack, context) when is_var(var) do
351-
{_ok?, type, context} = Of.refine_var(var, expected, expr, stack, context)
355+
{_ok?, type, context} = Of.refine_head_var(var, expected, expr, stack, context)
352356
{type, context}
353357
end
354358

355-
def of_match_var(ast, expected, expr, stack, context) do
356-
of_match(ast, expected, expr, :default, stack, context)
359+
def of_match_var(_ast, expected, _expr, _stack, context) do
360+
{expected, context}
357361
end
358362

359363
## Patterns
@@ -678,9 +682,9 @@ defmodule Module.Types.Pattern do
678682

679683
# ^var
680684
def of_guard({:^, _meta, [var]}, expected, expr, stack, context) do
681-
# This is by definition a variable defined outside of this pattern, so we don't track it.
682-
{type, context} = Of.refine_existing_var(var, expected, expr, stack, context)
683-
Of.intersect(type, expected, expr, stack, context)
685+
# This is used by binary size, which behaves as a mixture of match and guard
686+
{type, context} = Of.refine_body_var(var, expected, expr, stack, context)
687+
Of.compatible(type, expected, expr, stack, context)
684688
end
685689

686690
# {...}
@@ -716,7 +720,7 @@ defmodule Module.Types.Pattern do
716720

717721
# var
718722
def of_guard(var, expected, expr, stack, context) when is_var(var) do
719-
Of.intersect(Of.var(var, context), expected, expr, stack, context)
723+
Of.compatible(Of.var(var, context), expected, expr, stack, context)
720724
end
721725

722726
## Helpers

lib/elixir/src/elixir_bitstring.erl

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
-module(elixir_bitstring).
22
-export([expand/5, format_error/1, validate_spec/2]).
3-
-import(elixir_errors, [function_error/4, file_error/4]).
3+
-import(elixir_errors, [function_error/4]).
44
-include("elixir.hrl").
55

66
expand_match(Expr, {S, OriginalS}, E) ->
@@ -13,11 +13,6 @@ expand(Meta, Args, S, E, RequireSize) ->
1313
{EArgs, Alignment, {SA, _}, EA} =
1414
expand(Meta, fun expand_match/3, Args, [], {S, S}, E, 0, RequireSize),
1515

16-
case find_match(EArgs) of
17-
false -> ok;
18-
Match -> file_error(Meta, EA, ?MODULE, {nested_match, Match})
19-
end,
20-
2116
{{'<<>>', [{alignment, Alignment} | Meta], EArgs}, SA, EA};
2217
_ ->
2318
PairS = {elixir_env:prepare_write(S), S},
@@ -32,6 +27,7 @@ expand(_BitstrMeta, _Fun, [], Acc, S, E, Alignment, _RequireSize) ->
3227
{lists:reverse(Acc), Alignment, S, E};
3328
expand(BitstrMeta, Fun, [{'::', Meta, [Left, Right]} | T], Acc, S, E, Alignment, RequireSize) ->
3429
{ELeft, {SL, OriginalS}, EL} = expand_expr(Left, Fun, S, E),
30+
validate_expr(ELeft, Meta, E),
3531

3632
MatchOrRequireSize = RequireSize or is_match_size(T, EL),
3733
EType = expr_type(ELeft),
@@ -47,6 +43,7 @@ expand(BitstrMeta, Fun, [{'::', Meta, [Left, Right]} | T], Acc, S, E, Alignment,
4743
expand(BitstrMeta, Fun, [H | T], Acc, S, E, Alignment, RequireSize) ->
4844
Meta = extract_meta(H, BitstrMeta),
4945
{ELeft, {SS, OriginalS}, ES} = expand_expr(H, Fun, S, E),
46+
validate_expr(ELeft, Meta, E),
5047

5148
MatchOrRequireSize = RequireSize or is_match_size(T, ES),
5249
EType = expr_type(ELeft),
@@ -145,6 +142,17 @@ expand_expr({{'.', _, [Mod, to_string]}, _, [Arg]} = AST, Fun, S, #{context := C
145142
expand_expr(Component, Fun, S, E) ->
146143
Fun(Component, S, E).
147144

145+
validate_expr(Expr, Meta, #{context := match} = E) ->
146+
case Expr of
147+
{Var, _Meta, Ctx} when is_atom(Var), is_atom(Ctx) -> ok;
148+
{'<<>>', _, _} -> ok;
149+
{'^', _, _} -> ok;
150+
_ when is_number(Expr); is_binary(Expr) -> ok;
151+
_ -> function_error(extract_meta(Expr, Meta), E, ?MODULE, {unknown_match, Expr})
152+
end;
153+
validate_expr(_Expr, _Meta, _E) ->
154+
ok.
155+
148156
%% Expands and normalizes types of a bitstring.
149157

150158
expand_specs(ExprType, Meta, Info, S, OriginalS, E, ExpectSize) ->
@@ -353,18 +361,6 @@ valid_float_size(_) -> false.
353361
add_spec(default, Spec) -> Spec;
354362
add_spec(Key, Spec) -> [{Key, [], nil} | Spec].
355363

356-
find_match([{'=', _, [_Left, _Right]} = Expr | _Rest]) ->
357-
Expr;
358-
find_match([{_, _, Args} | Rest]) when is_list(Args) ->
359-
case find_match(Args) of
360-
false -> find_match(Rest);
361-
Match -> Match
362-
end;
363-
find_match([_Arg | Rest]) ->
364-
find_match(Rest);
365-
find_match([]) ->
366-
false.
367-
368364
format_error({unaligned_binary, Expr}) ->
369365
Message = "expected ~ts to be a binary but its number of bits is not divisible by 8",
370366
io_lib:format(Message, ['Elixir.Macro':to_string(Expr)]);
@@ -409,10 +405,9 @@ format_error({bittype_mismatch, Val1, Val2, Where}) ->
409405
format_error({bad_unit_argument, Unit}) ->
410406
io_lib:format("unit in bitstring expects an integer as argument, got: ~ts",
411407
['Elixir.Macro':to_string(Unit)]);
412-
format_error({nested_match, Expr}) ->
408+
format_error({unknown_match, Expr}) ->
413409
Message =
414-
"cannot pattern match inside a bitstring "
415-
"that is already in match, got: ~ts",
410+
"a bitstring only accepts binaries, numbers, and variables inside a match, got: ~ts",
416411
io_lib:format(Message, ['Elixir.Macro':to_string(Expr)]);
417412
format_error({undefined_var_in_spec, Var}) ->
418413
Message =

lib/elixir/test/elixir/kernel/errors_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -941,7 +941,7 @@ defmodule Kernel.ErrorsTest do
941941

942942
test "failed remote call stacktrace includes file/line info" do
943943
try do
944-
bad_remote_call(1)
944+
bad_remote_call(Process.get(:unused, 1))
945945
rescue
946946
ArgumentError ->
947947
assert [

lib/elixir/test/elixir/kernel/expansion_test.exs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2387,6 +2387,15 @@ defmodule Kernel.ExpansionTest do
23872387
|> clean_bit_modifiers()
23882388
end
23892389

2390+
test "invalid match" do
2391+
assert_compile_error(
2392+
"a bitstring only accepts binaries, numbers, and variables inside a match",
2393+
fn ->
2394+
expand(quote(do: <<%{}>> = foo()))
2395+
end
2396+
)
2397+
end
2398+
23902399
test "nested match" do
23912400
assert expand(quote(do: <<foo = bar>>)) |> clean_meta([:alignment]) ==
23922401
quote(do: <<foo = bar()::integer>>) |> clean_bit_modifiers()
@@ -2395,16 +2404,6 @@ defmodule Kernel.ExpansionTest do
23952404
|> clean_meta([:alignment]) ==
23962405
quote(do: <<45::integer, <<_::integer, _::binary>> = rest()::binary>>)
23972406
|> clean_bit_modifiers()
2398-
2399-
message = ~r"cannot pattern match inside a bitstring that is already in match"
2400-
2401-
assert_compile_error(message, fn ->
2402-
expand(quote(do: <<bar = baz>> = foo()))
2403-
end)
2404-
2405-
assert_compile_error(message, fn ->
2406-
expand(quote(do: <<?-, <<_, _::binary>> = rest::binary>> = foo()))
2407-
end)
24082407
end
24092408

24102409
test "inlines binaries inside interpolation" do

0 commit comments

Comments
 (0)