Skip to content

Commit aef8673

Browse files
wojtekmachjosevalim
authored andcommitted
Preserve column when translating typespecs (#13101)
1 parent 901ec18 commit aef8673

File tree

4 files changed

+79
-51
lines changed

4 files changed

+79
-51
lines changed

Diff for: lib/elixir/lib/code/typespec.ex

+9-1
Original file line numberDiff line numberDiff line change
@@ -420,5 +420,13 @@ defmodule Code.Typespec do
420420
:error
421421
end
422422

423-
defp meta(anno), do: [line: :erl_anno.line(anno)]
423+
defp meta(anno) do
424+
case :erl_anno.location(anno) do
425+
{line, column} ->
426+
[line: line, column: column]
427+
428+
line when is_integer(line) ->
429+
[line: line]
430+
end
431+
end
424432
end

Diff for: lib/elixir/lib/kernel/typespec.ex

+51-41
Original file line numberDiff line numberDiff line change
@@ -385,17 +385,17 @@ defmodule Kernel.Typespec do
385385
compile_error(caller, error)
386386
end
387387

388-
line = line(meta)
388+
location = location(meta)
389389
vars = Keyword.keys(guard)
390390

391391
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
392392
{return, state} = typespec(return, vars, caller, state)
393-
spec = {:type, line, :fun, [{:type, line, :product, args}, return]}
393+
spec = {:type, location, :fun, [{:type, location, :product, args}, return]}
394394

395395
{spec, state} =
396396
case guard_to_constraints(guard, vars, meta, caller, state) do
397397
{[], state} -> {spec, state}
398-
{constraints, state} -> {{:type, line, :bounded_fun, [spec, constraints]}, state}
398+
{constraints, state} -> {{:type, location, :bounded_fun, [spec, constraints]}, state}
399399
end
400400

401401
ensure_no_unused_local_vars!(caller, state.local_vars)
@@ -437,17 +437,17 @@ defmodule Kernel.Typespec do
437437
defp ensure_not_default(_), do: :ok
438438

439439
defp guard_to_constraints(guard, vars, meta, caller, state) do
440-
line = line(meta)
440+
location = location(meta)
441441

442442
fun = fn
443443
{_name, {:var, _, context}}, {constraints, state} when is_atom(context) ->
444444
{constraints, state}
445445

446446
{name, type}, {constraints, state} ->
447447
{spec, state} = typespec(type, vars, caller, state)
448-
constraint = [{:atom, line, :is_subtype}, [{:var, line, name}, spec]]
448+
constraint = [{:atom, location, :is_subtype}, [{:var, location, name}, spec]]
449449
state = update_local_vars(state, name)
450-
{[{:type, line, :constraint, constraint} | constraints], state}
450+
{[{:type, location, :constraint, constraint} | constraints], state}
451451
end
452452

453453
{constraints, state} = :lists.foldl(fun, {[], state}, guard)
@@ -456,21 +456,27 @@ defmodule Kernel.Typespec do
456456

457457
## To typespec conversion
458458

459-
defp line(meta) do
460-
Keyword.get(meta, :line, 0)
459+
defp location(meta) do
460+
line = Keyword.get(meta, :line, 0)
461+
462+
if column = Keyword.get(meta, :column) do
463+
{line, column}
464+
else
465+
line
466+
end
461467
end
462468

463469
# Handle unions
464470
defp typespec({:|, meta, [_, _]} = exprs, vars, caller, state) do
465471
exprs = collect_union(exprs)
466472
{union, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, exprs)
467-
{{:type, line(meta), :union, union}, state}
473+
{{:type, location(meta), :union, union}, state}
468474
end
469475

470476
# Handle binaries
471477
defp typespec({:<<>>, meta, []}, _, _, state) do
472-
line = line(meta)
473-
{{:type, line, :binary, [{:integer, line, 0}, {:integer, line, 0}]}, state}
478+
location = location(meta)
479+
{{:type, location, :binary, [{:integer, location, 0}, {:integer, location, 0}]}, state}
474480
end
475481

476482
defp typespec(
@@ -480,14 +486,18 @@ defmodule Kernel.Typespec do
480486
state
481487
)
482488
when is_atom(ctx1) and is_atom(ctx2) and unit in 1..256 do
483-
line = line(meta)
484-
{{:type, line, :binary, [{:integer, line, 0}, {:integer, line(unit_meta), unit}]}, state}
489+
location = location(meta)
490+
491+
{{:type, location, :binary, [{:integer, location, 0}, {:integer, location(unit_meta), unit}]},
492+
state}
485493
end
486494

487495
defp typespec({:<<>>, meta, [{:"::", size_meta, [{:_, _, ctx}, size]}]}, _, _, state)
488496
when is_atom(ctx) and is_integer(size) and size >= 0 do
489-
line = line(meta)
490-
{{:type, line, :binary, [{:integer, line(size_meta), size}, {:integer, line, 0}]}, state}
497+
location = location(meta)
498+
499+
{{:type, location, :binary, [{:integer, location(size_meta), size}, {:integer, location, 0}]},
500+
state}
491501
end
492502

493503
defp typespec(
@@ -505,8 +515,8 @@ defmodule Kernel.Typespec do
505515
)
506516
when is_atom(ctx1) and is_atom(ctx2) and is_atom(ctx3) and is_integer(size) and
507517
size >= 0 and unit in 1..256 do
508-
args = [{:integer, line(size_meta), size}, {:integer, line(unit_meta), unit}]
509-
{{:type, line(meta), :binary, args}, state}
518+
args = [{:integer, location(size_meta), size}, {:integer, location(unit_meta), unit}]
519+
{{:type, location(meta), :binary, args}, state}
510520
end
511521

512522
defp typespec({:<<>>, _meta, _args}, _vars, caller, _state) do
@@ -519,25 +529,25 @@ defmodule Kernel.Typespec do
519529

520530
## Handle maps and structs
521531
defp typespec({:map, meta, args}, _vars, _caller, state) when args == [] or is_atom(args) do
522-
{{:type, line(meta), :map, :any}, state}
532+
{{:type, location(meta), :map, :any}, state}
523533
end
524534

525535
defp typespec({:%{}, meta, fields} = map, vars, caller, state) do
526536
fun = fn
527537
{{:required, meta2, [k]}, v}, state ->
528538
{arg1, state} = typespec(k, vars, caller, state)
529539
{arg2, state} = typespec(v, vars, caller, state)
530-
{{:type, line(meta2), :map_field_exact, [arg1, arg2]}, state}
540+
{{:type, location(meta2), :map_field_exact, [arg1, arg2]}, state}
531541

532542
{{:optional, meta2, [k]}, v}, state ->
533543
{arg1, state} = typespec(k, vars, caller, state)
534544
{arg2, state} = typespec(v, vars, caller, state)
535-
{{:type, line(meta2), :map_field_assoc, [arg1, arg2]}, state}
545+
{{:type, location(meta2), :map_field_assoc, [arg1, arg2]}, state}
536546

537547
{k, v}, state ->
538548
{arg1, state} = typespec(k, vars, caller, state)
539549
{arg2, state} = typespec(v, vars, caller, state)
540-
{{:type, line(meta), :map_field_exact, [arg1, arg2]}, state}
550+
{{:type, location(meta), :map_field_exact, [arg1, arg2]}, state}
541551

542552
{:|, _, [_, _]}, _state ->
543553
error =
@@ -551,7 +561,7 @@ defmodule Kernel.Typespec do
551561
end
552562

553563
{fields, state} = :lists.mapfoldl(fun, state, fields)
554-
{{:type, line(meta), :map, fields}, state}
564+
{{:type, location(meta), :map, fields}, state}
555565
end
556566

557567
defp typespec({:%, _, [name, {:%{}, meta, fields}]} = node, vars, caller, state) do
@@ -644,7 +654,7 @@ defmodule Kernel.Typespec do
644654
{right, state} = typespec(right, vars, caller, state)
645655
:ok = validate_range(left, right, caller)
646656

647-
{{:type, line(meta), :range, [left, right]}, state}
657+
{{:type, location(meta), :range, [left, right]}, state}
648658
end
649659

650660
# Handle special forms
@@ -668,7 +678,7 @@ defmodule Kernel.Typespec do
668678
pair -> pair
669679
end
670680

671-
{{:type, line(meta), :fun, fun_args}, state}
681+
{{:type, location(meta), :fun, fun_args}, state}
672682
end
673683

674684
# Handle type operator
@@ -691,10 +701,10 @@ defmodule Kernel.Typespec do
691701
# This may be generating an invalid typespec but we need to generate it
692702
# to avoid breaking existing code that was valid but only broke Dialyzer
693703
{right, state} = typespec(expr, vars, caller, state)
694-
{{:ann_type, line(meta), [{:var, line(var_meta), var_name}, right]}, state}
704+
{{:ann_type, location(meta), [{:var, location(var_meta), var_name}, right]}, state}
695705

696706
{right, state} ->
697-
{{:ann_type, line(meta), [{:var, line(var_meta), var_name}, right]}, state}
707+
{{:ann_type, location(meta), [{:var, location(var_meta), var_name}, right]}, state}
698708
end
699709
end
700710

@@ -723,13 +733,13 @@ defmodule Kernel.Typespec do
723733
{left, state} = typespec(left, vars, caller, state)
724734
state = %{state | undefined_type_error_enabled?: true}
725735
{right, state} = typespec(right, vars, caller, state)
726-
{{:ann_type, line(meta), [left, right]}, state}
736+
{{:ann_type, location(meta), [left, right]}, state}
727737
end
728738

729739
# Handle unary ops
730740
defp typespec({op, meta, [integer]}, _, _, state) when op in [:+, :-] and is_integer(integer) do
731-
line = line(meta)
732-
{{:op, line, op, {:integer, line, integer}}, state}
741+
location = location(meta)
742+
{{:op, location, op, {:integer, location, integer}}, state}
733743
end
734744

735745
# Handle remote calls in the form of @module_attribute.type.
@@ -778,12 +788,12 @@ defmodule Kernel.Typespec do
778788

779789
# Handle tuples
780790
defp typespec({:tuple, meta, []}, _vars, _caller, state) do
781-
{{:type, line(meta), :tuple, :any}, state}
791+
{{:type, location(meta), :tuple, :any}, state}
782792
end
783793

784794
defp typespec({:{}, meta, t}, vars, caller, state) when is_list(t) do
785795
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, t)
786-
{{:type, line(meta), :tuple, args}, state}
796+
{{:type, location(meta), :tuple, args}, state}
787797
end
788798

789799
defp typespec({left, right}, vars, caller, state) do
@@ -799,7 +809,7 @@ defmodule Kernel.Typespec do
799809
defp typespec({name, meta, atom}, vars, caller, state) when is_atom(atom) do
800810
if :lists.member(name, vars) do
801811
state = update_local_vars(state, name)
802-
{{:var, line(meta), name}, state}
812+
{{:var, location(meta), name}, state}
803813
else
804814
typespec({name, meta, []}, vars, caller, state)
805815
end
@@ -814,7 +824,7 @@ defmodule Kernel.Typespec do
814824

815825
IO.warn(warning, caller)
816826
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
817-
{{:type, line(meta), :string, args}, state}
827+
{{:type, location(meta), :string, args}, state}
818828
end
819829

820830
defp typespec({:nonempty_string, meta, args}, vars, caller, state) do
@@ -825,7 +835,7 @@ defmodule Kernel.Typespec do
825835

826836
IO.warn(warning, caller)
827837
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
828-
{{:type, line(meta), :nonempty_string, args}, state}
838+
{{:type, location(meta), :nonempty_string, args}, state}
829839
end
830840

831841
defp typespec({type, _meta, []}, vars, caller, state) when type in [:charlist, :char_list] do
@@ -855,7 +865,7 @@ defmodule Kernel.Typespec do
855865

856866
defp typespec({:fun, meta, args}, vars, caller, state) do
857867
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
858-
{{:type, line(meta), :fun, args}, state}
868+
{{:type, location(meta), :fun, args}, state}
859869
end
860870

861871
defp typespec({:..., _meta, _args}, _vars, caller, _state) do
@@ -872,7 +882,7 @@ defmodule Kernel.Typespec do
872882

873883
case :erl_internal.is_type(name, arity) do
874884
true ->
875-
{{:type, line(meta), name, args}, state}
885+
{{:type, location(meta), name, args}, state}
876886

877887
false ->
878888
if state.undefined_type_error_enabled? and
@@ -890,7 +900,7 @@ defmodule Kernel.Typespec do
890900
%{state | used_type_pairs: [{name, arity} | state.used_type_pairs]}
891901
end
892902

893-
{{:user_type, line(meta), name, args}, state}
903+
{{:user_type, location(meta), name, args}, state}
894904
end
895905
end
896906

@@ -963,7 +973,7 @@ defmodule Kernel.Typespec do
963973

964974
defp remote_type({remote, meta, name, args}, vars, caller, state) do
965975
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
966-
{{:remote_type, line(meta), [remote, name, args]}, state}
976+
{{:remote_type, location(meta), [remote, name, args]}, state}
967977
end
968978

969979
defp collect_union({:|, _, [a, b]}), do: [a | collect_union(b)]
@@ -996,16 +1006,16 @@ defmodule Kernel.Typespec do
9961006
end
9971007

9981008
defp fn_args(meta, [{:..., _, _}], _vars, _caller, state) do
999-
{{:type, line(meta), :any}, state}
1009+
{{:type, location(meta), :any}, state}
10001010
end
10011011

10021012
defp fn_args(meta, args, vars, caller, state) do
10031013
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
1004-
{{:type, line(meta), :product, args}, state}
1014+
{{:type, location(meta), :product, args}, state}
10051015
end
10061016

10071017
defp variable({name, meta, args}) when is_atom(name) and is_atom(args) do
1008-
{:var, line(meta), name}
1018+
{:var, location(meta), name}
10091019
end
10101020

10111021
defp variable(expr), do: expr

Diff for: lib/elixir/test/elixir/protocol_test.exs

+6-2
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,12 @@ defmodule ProtocolTest do
154154
end
155155

156156
test "protocol defines callbacks" do
157-
assert [{:type, 13, :fun, args}] = get_callbacks(@sample_binary, :ok, 1)
158-
assert args == [{:type, 13, :product, [{:user_type, 13, :t, []}]}, {:type, 13, :boolean, []}]
157+
assert [{:type, {13, 19}, :fun, args}] = get_callbacks(@sample_binary, :ok, 1)
158+
159+
assert args == [
160+
{:type, {13, 19}, :product, [{:user_type, {13, 16}, :t, []}]},
161+
{:type, {13, 22}, :boolean, []}
162+
]
159163

160164
assert [{:type, 23, :fun, args}] = get_callbacks(@with_any_binary, :ok, 1)
161165
assert args == [{:type, 23, :product, [{:user_type, 23, :t, []}]}, {:type, 23, :term, []}]

Diff for: lib/elixir/test/elixir/typespec_test.exs

+13-7
Original file line numberDiff line numberDiff line change
@@ -1582,23 +1582,29 @@ defmodule TypespecTest do
15821582
""")
15831583

15841584
[type: type] = types(:typespec_test_mod)
1585-
line = 5
15861585

15871586
assert Code.Typespec.type_to_quoted(type) ==
1588-
{:"::", [], [{:t, [], [{:x, [line: line], nil}]}, [{:x, [line: line], nil}]]}
1587+
{:"::", [],
1588+
[
1589+
{:t, [], [{:x, meta(5, 9), nil}]},
1590+
[{:x, meta(5, 20), nil}]
1591+
]}
15891592

15901593
[{{:f, 1}, [spec]}] = specs(:typespec_test_mod)
1591-
line = 7
15921594

15931595
assert Code.Typespec.spec_to_quoted(:f, spec) ==
1594-
{:when, [line: line],
1596+
{:when, meta(7, 8),
15951597
[
1596-
{:"::", [line: line],
1597-
[{:f, [line: line], [{:x, [line: line], nil}]}, {:x, [line: line], nil}]},
1598-
[x: {:var, [line: line], nil}]
1598+
{:"::", meta(7, 8),
1599+
[{:f, meta(7, 8), [{:x, meta(7, 9), nil}]}, {:x, meta(7, 15), nil}]},
1600+
[x: {:var, meta(7, 8), nil}]
15991601
]}
16001602
end
16011603

1604+
defp meta(line, column) do
1605+
[line: line, column: column]
1606+
end
1607+
16021608
defp erlc(context, module, code) do
16031609
dir = context.tmp_dir
16041610

0 commit comments

Comments
 (0)