Skip to content

Commit 1c19c60

Browse files
Add source and behaviour information to docs chunk metadata (#13914)
1 parent f256584 commit 1c19c60

12 files changed

+155
-72
lines changed

lib/elixir/lib/kernel.ex

+6-3
Original file line numberDiff line numberDiff line change
@@ -5010,12 +5010,14 @@ defmodule Kernel do
50105010
assert_no_match_or_guard_scope(env.context, "defmodule/2")
50115011
expanded = expand_module_alias(alias, env)
50125012

5013+
module_meta = module_meta(alias)
5014+
50135015
{expanded, with_alias} =
50145016
case is_atom(expanded) do
50155017
true ->
50165018
{full, old, opts} = alias_defmodule(alias, expanded, env)
50175019
# Expand the module considering the current environment/nesting
5018-
meta = [defined: full] ++ alias_meta(alias)
5020+
meta = [defined: full] ++ module_meta
50195021
{full, {:require, meta, [old, opts]}}
50205022

50215023
false ->
@@ -5047,6 +5049,7 @@ defmodule Kernel do
50475049
unquote(with_alias)
50485050

50495051
:elixir_module.compile(
5052+
unquote(module_meta),
50505053
unquote(expanded),
50515054
unquote(escaped),
50525055
unquote(module_vars),
@@ -5056,8 +5059,8 @@ defmodule Kernel do
50565059
end
50575060
end
50585061

5059-
defp alias_meta({:__aliases__, meta, _}), do: meta
5060-
defp alias_meta(_), do: []
5062+
defp module_meta({_, meta, _}), do: meta
5063+
defp module_meta(_), do: []
50615064

50625065
# We don't want to trace :alias_reference since we are defining the alias
50635066
defp expand_module_alias({:__aliases__, meta, list} = alias, env) do

lib/elixir/lib/kernel/typespec.ex

+4-4
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ defmodule Kernel.Typespec do
290290
:lists.filter(fun, types)
291291
end
292292

293-
defp translate_type({kind, {:"::", _, [{name, _, args}, definition]}, pos}, state) do
293+
defp translate_type({kind, {:"::", _, [{name, meta, args}, definition]}, pos}, state) do
294294
caller = :elixir_locals.get_cached_env(pos)
295295
state = clean_local_state(state)
296296

@@ -335,7 +335,7 @@ defmodule Kernel.Typespec do
335335
IO.warn(message, caller)
336336
end
337337

338-
{{kind, {name, arity}, caller.line, type, export}, state}
338+
{{kind, {name, arity}, meta, type, export}, state}
339339
end
340340

341341
defp valid_variable_ast?({variable_name, _, context})
@@ -357,7 +357,7 @@ defmodule Kernel.Typespec do
357357
translate_spec(kind, spec, [], caller, state)
358358
end
359359

360-
defp translate_spec(kind, {:"::", meta, [{name, _, args}, return]}, guard, caller, state)
360+
defp translate_spec(kind, {:"::", _, [{name, meta, args}, return]}, guard, caller, state)
361361
when is_atom(name) and name != :"::" do
362362
translate_spec(kind, meta, name, args, return, guard, caller, state)
363363
end
@@ -401,7 +401,7 @@ defmodule Kernel.Typespec do
401401
ensure_no_unused_local_vars!(caller, state.local_vars)
402402

403403
arity = length(args)
404-
{{kind, {name, arity}, caller.line, spec}, state}
404+
{{kind, {name, arity}, meta, spec}, state}
405405
end
406406

407407
# TODO: Remove char_list type by v2.0

lib/elixir/lib/module.ex

+1-1
Original file line numberDiff line numberDiff line change
@@ -916,7 +916,7 @@ defmodule Module do
916916
defp create(meta, module, quoted, env_or_opts) do
917917
next = :elixir_module.next_counter(nil)
918918
quoted = :elixir_quote.linify_with_context_counter(meta, {module, next}, quoted)
919-
:elixir_module.compile(module, quoted, [], false, :elixir.env_for_eval(env_or_opts))
919+
:elixir_module.compile(meta, module, quoted, [], false, :elixir.env_for_eval(env_or_opts))
920920
end
921921

922922
@doc """

lib/elixir/lib/module/parallel_checker.ex

+3-1
Original file line numberDiff line numberDiff line change
@@ -229,13 +229,15 @@ defmodule Module.ParallelChecker do
229229
%{
230230
module: module,
231231
file: file,
232-
line: line,
232+
anno: anno,
233233
compile_opts: compile_opts,
234234
definitions: definitions,
235235
uses_behaviours: uses_behaviours,
236236
impls: impls
237237
} = module_map
238238

239+
line = :erl_anno.line(anno)
240+
239241
no_warn_undefined =
240242
compile_opts
241243
|> extract_no_warn_undefined()

lib/elixir/src/elixir_bootstrap.erl

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
'MACRO-defmacro'(Caller, Call, Expr) -> define(Caller, defmacro, Call, Expr).
1919
'MACRO-defmacrop'(Caller, Call, Expr) -> define(Caller, defmacrop, Call, Expr).
2020

21-
'MACRO-defmodule'(_Caller, Alias, [{do, Block}]) ->
21+
'MACRO-defmodule'({Line, _S, _E} = _Caller, Alias, [{do, Block}]) ->
2222
Escaped = elixir_quote:escape(Block, none, false),
23-
Args = [Alias, Escaped, [], false, env()],
23+
Args = [[{line, Line}], Alias, Escaped, [], false, env()],
2424
{{'.', [], [elixir_module, compile]}, [], Args}.
2525

2626
'__info__'(functions) ->

lib/elixir/src/elixir_compiler.erl

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ fast_compile({defmodule, Meta, [Mod, [{do, Block}]]}, NoLineE) ->
128128
end,
129129

130130
ContextModules = [Expanded | ?key(E, context_modules)],
131-
elixir_module:compile(Expanded, Block, [], false, E#{context_modules := ContextModules}).
131+
elixir_module:compile(Meta, Expanded, Block, [], false, E#{context_modules := ContextModules}).
132132

133133
no_tail_optimize(Meta, Block) ->
134134
{'__block__', Meta, [

lib/elixir/src/elixir_erl.erl

+66-33
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,14 @@ debug_info(_, _, _, _) ->
3939
%% Builds Erlang AST annotation.
4040

4141
get_ann(Opts) when is_list(Opts) ->
42-
get_ann(Opts, false, 0).
42+
get_ann(Opts, false, 0, undefined).
4343

44-
get_ann([{generated, true} | T], _, Line) -> get_ann(T, true, Line);
45-
get_ann([{line, Line} | T], Gen, _) when is_integer(Line) -> get_ann(T, Gen, Line);
46-
get_ann([_ | T], Gen, Line) -> get_ann(T, Gen, Line);
47-
get_ann([], Gen, Line) -> erl_anno:set_generated(Gen, erl_anno:new(Line)).
44+
get_ann([{generated, true} | T], _, Line, Column) -> get_ann(T, true, Line, Column);
45+
get_ann([{line, Line} | T], Gen, _, Column) when is_integer(Line) -> get_ann(T, Gen, Line, Column);
46+
get_ann([{column, Column} | T], Gen, Line, _) when is_integer(Column) -> get_ann(T, Gen, Line, Column);
47+
get_ann([_ | T], Gen, Line, Column) -> get_ann(T, Gen, Line, Column);
48+
get_ann([], Gen, Line, undefined) -> erl_anno:set_generated(Gen, erl_anno:new(Line));
49+
get_ann([], Gen, Line, Column) -> erl_anno:set_generated(Gen, erl_anno:new({Line, Column})).
4850

4951
%% Converts an Elixir definition to an anonymous function.
5052

@@ -122,7 +124,7 @@ consolidate(Map, TypeSpecs, Chunks) ->
122124

123125
%% Dynamic compilation hook, used in regular compiler
124126

125-
compile(#{module := Module, line := Line} = Map) ->
127+
compile(#{module := Module, anno := Anno} = Map) ->
126128
{Set, Bag} = elixir_module:data_tables(Module),
127129

128130
TranslatedTypespecs =
@@ -135,13 +137,14 @@ compile(#{module := Module, line := Line} = Map) ->
135137
{Prefix, Forms, Def, Defmacro, Macros} = dynamic_form(Map),
136138
{Types, Callbacks, TypeSpecs} = typespecs_form(Map, TranslatedTypespecs, Macros),
137139

138-
DocsChunk = docs_chunk(Set, Module, Line, Def, Defmacro, Types, Callbacks),
140+
DocsChunk = docs_chunk(Map, Set, Module, Anno, Def, Defmacro, Types, Callbacks),
139141
CheckerChunk = checker_chunk(Def, Defmacro, Map),
140142
load_form(Map, Prefix, Forms, TypeSpecs, DocsChunk ++ CheckerChunk).
141143

142-
dynamic_form(#{module := Module, line := Line, relative_file := RelativeFile,
144+
dynamic_form(#{module := Module, anno := Anno, relative_file := RelativeFile,
143145
attributes := Attributes, definitions := Definitions, unreachable := Unreachable,
144146
deprecated := Deprecated, compile_opts := Opts} = Map) ->
147+
Line = erl_anno:line(Anno),
145148
{Def, Defmacro, Macros, Exports, Functions} =
146149
split_definition(Definitions, Unreachable, Line, [], [], [], [], {[], []}),
147150

@@ -168,13 +171,14 @@ split_definition([{Tuple, Kind, Meta, Clauses} | T], Unreachable, Line,
168171
true ->
169172
split_definition(T, Unreachable, Line, Def, Defmacro, Macros, Exports, Functions)
170173
end;
174+
171175
split_definition([], _Unreachable, _Line, Def, Defmacro, Macros, Exports, {Head, Tail}) ->
172-
{lists:usort(Def), lists:usort(Defmacro), Macros, Exports, Head ++ Tail}.
176+
{lists:sort(Def), lists:sort(Defmacro), Macros, Exports, Head ++ Tail}.
173177

174178
split_definition(Tuple, def, Meta, Clauses, T, Unreachable, Line,
175179
Def, Defmacro, Macros, Exports, Functions) ->
176180
{_, _, N, A, _} = Entry = translate_definition(def, Line, Meta, Tuple, Clauses),
177-
split_definition(T, Unreachable, Line, [Tuple | Def], Defmacro, Macros, [{N, A} | Exports],
181+
split_definition(T, Unreachable, Line, [{Tuple, Meta} | Def], Defmacro, Macros, [{N, A} | Exports],
178182
add_definition(Meta, Entry, Functions));
179183

180184
split_definition(Tuple, defp, Meta, Clauses, T, Unreachable, Line,
@@ -186,7 +190,7 @@ split_definition(Tuple, defp, Meta, Clauses, T, Unreachable, Line,
186190
split_definition(Tuple, defmacro, Meta, Clauses, T, Unreachable, Line,
187191
Def, Defmacro, Macros, Exports, Functions) ->
188192
{_, _, N, A, _} = Entry = translate_definition(defmacro, Line, Meta, Tuple, Clauses),
189-
split_definition(T, Unreachable, Line, Def, [Tuple | Defmacro], [Tuple | Macros], [{N, A} | Exports],
193+
split_definition(T, Unreachable, Line, Def, [{Tuple, Meta} | Defmacro], [Tuple | Macros], [{N, A} | Exports],
190194
add_definition(Meta, Entry, Functions));
191195

192196
split_definition(Tuple, defmacrop, Meta, Clauses, T, Unreachable, Line,
@@ -261,6 +265,9 @@ functions_form(Line, Module, Def, Defmacro, Exports, Body, Deprecated, Struct) -
261265
[{attribute, Line, export, lists:usort([{'__info__', 1} | Exports])}, Spec, Info | Body].
262266

263267
add_info_function(Line, Module, Def, Defmacro, Deprecated, Struct) ->
268+
DefNA = [NA || {NA, _Meta} <- Def],
269+
DefmacroNA = [NA || {NA, _Meta} <- Defmacro],
270+
264271
AllowedAttrs = [attributes, compile, functions, macros, md5, exports_md5, module, deprecated, struct],
265272
AllowedArgs = lists:map(fun(Atom) -> {atom, Line, Atom} end, AllowedAttrs),
266273

@@ -277,10 +284,10 @@ add_info_function(Line, Module, Def, Defmacro, Deprecated, Struct) ->
277284
Info =
278285
{function, 0, '__info__', 1, [
279286
get_module_info(Module),
280-
functions_info(Def),
281-
macros_info(Defmacro),
287+
functions_info(DefNA),
288+
macros_info(DefmacroNA),
282289
struct_info(Struct),
283-
exports_md5_info(Struct, Def, Defmacro),
290+
exports_md5_info(Struct, DefNA, DefmacroNA),
284291
get_module_info(Module, attributes),
285292
get_module_info(Module, compile),
286293
get_module_info(Module, md5),
@@ -331,19 +338,34 @@ typespecs_form(Map, TranslatedTypespecs, MacroNames) ->
331338
Forms2 = callspecs_form(spec, Specs, [], MacroNames, Forms1, Map),
332339
Forms3 = callspecs_form(callback, AllCallbacks, OptionalCallbacks, MacroCallbackNames, Forms2, Map),
333340

334-
AllCallbacksWithoutSpecs = lists:usort([
335-
{Kind, Name, Arity} || {Kind, {Name, Arity}, _Line, _Spec} <- AllCallbacks
341+
AllCallbacksWithoutSpecs = usort_callbacks([
342+
{{Kind, Name, Arity}, Meta} || {Kind, {Name, Arity}, Meta, _Spec} <- AllCallbacks
336343
]),
337344

338345
{Types, AllCallbacksWithoutSpecs, Forms3}.
339346

347+
usort_callbacks(Callbacks) ->
348+
% Sort and deduplicate callbacks. For duplicated callbacks we take
349+
% the one with earliest line.
350+
351+
LineComparator = fun
352+
({Callback1, Meta1}, {Callback1, Meta2}) -> ?line(Meta1) =< ?line(Meta2);
353+
({Callback1, _Meta1}, {Callback2, _Meta2}) -> Callback1 =< Callback2
354+
end,
355+
356+
UniqFun = fun({Callback, _Meta}) -> Callback end,
357+
358+
lists:uniq(UniqFun, lists:sort(LineComparator, Callbacks)).
359+
340360
%% Types
341361

342362
types_form(Types, Forms) ->
343363
Fun = fun
344-
({Kind, NameArity, Line, Expr, true}, Acc) ->
364+
({Kind, NameArity, Meta, Expr, true}, Acc) ->
365+
Line = ?line(Meta),
345366
[{attribute, Line, export_type, [NameArity]}, {attribute, Line, Kind, Expr} | Acc];
346-
({Kind, _NameArity, Line, Expr, false}, Acc) ->
367+
({Kind, _NameArity, Meta, Expr, false}, Acc) ->
368+
Line = ?line(Meta),
347369
[{attribute, Line, Kind, Expr} | Acc]
348370
end,
349371

@@ -387,7 +409,9 @@ callspecs_form(Kind, Entries, Optional, Macros, Forms, ModuleMap) ->
387409
#{unreachable := Unreachable} = ModuleMap,
388410

389411
{SpecsMap, Signatures} =
390-
lists:foldl(fun({_, NameArity, Line, Spec}, {Acc, NA}) ->
412+
lists:foldl(fun({_, NameArity, Meta, Spec}, {Acc, NA}) ->
413+
Line = ?line(Meta),
414+
391415
case Kind of
392416
spec -> validate_spec_for_existing_function(ModuleMap, NameArity, Line);
393417
_ -> ok
@@ -440,7 +464,7 @@ validate_spec_for_existing_function(ModuleMap, NameAndArity, Line) ->
440464

441465
case lists:keymember(NameAndArity, 1, Defs) of
442466
true -> ok;
443-
false -> file_error(#{line => Line, file => File}, {spec_for_undefined_function, NameAndArity})
467+
false -> file_error(#{anno => erl_anno:new(Line), file => File}, {spec_for_undefined_function, NameAndArity})
444468
end.
445469

446470
% Attributes
@@ -472,22 +496,30 @@ take_debug_opts(Opts) ->
472496
extra_chunks_opts([], Opts) -> Opts;
473497
extra_chunks_opts(Chunks, Opts) -> [{extra_chunks, Chunks} | Opts].
474498

475-
docs_chunk(Set, Module, Line, Def, Defmacro, Types, Callbacks) ->
499+
docs_chunk(Map, Set, Module, Anno, Def, Defmacro, Types, Callbacks) ->
500+
#{file := File, uses_behaviours := UsesBehaviours} = Map,
501+
476502
case elixir_config:get(docs) of
477503
true ->
478-
{ModuleDocLine, ModuleDoc} = get_moduledoc(Line, Set),
504+
{ModuleDocLine, ModuleDoc} = get_moduledoc(erl_anno:line(Anno), Set),
479505
ModuleDocMeta = get_moduledoc_meta(Set),
480506
FunctionDocs = get_docs(Set, Module, Def, function),
481507
MacroDocs = get_docs(Set, Module, Defmacro, macro),
482508
CallbackDocs = get_callback_docs(Set, Callbacks),
483509
TypeDocs = get_type_docs(Set, Types),
484510

511+
ModuleMeta = ModuleDocMeta#{
512+
source_path => File,
513+
source_annos => [Anno],
514+
behaviours => UsesBehaviours
515+
},
516+
485517
DocsChunkData = term_to_binary({docs_v1,
486518
erl_anno:new(ModuleDocLine),
487519
elixir,
488520
<<"text/markdown">>,
489521
ModuleDoc,
490-
ModuleDocMeta,
522+
ModuleMeta,
491523
FunctionDocs ++ MacroDocs ++ CallbackDocs ++ TypeDocs
492524
}, [deterministic, compressed]),
493525

@@ -529,8 +561,8 @@ get_docs(Set, Module, Definitions, Kind) ->
529561
maybe_generated(erl_anno:new(Line), Ctx),
530562
[signature_to_binary(Module, Name, Signature)],
531563
doc_value(Doc, Name),
532-
Meta
533-
} || {Name, Arity} <- Definitions,
564+
Meta#{source_annos => [?ann(DefinitionMeta)]}
565+
} || {{Name, Arity}, DefinitionMeta} <- Definitions,
534566
{Key, Ctx, Line, Signature, Doc, Meta} <- ets:lookup(Set, {Kind, Name, Arity})].
535567

536568
maybe_generated(Ann, nil) -> Ann;
@@ -541,16 +573,16 @@ get_callback_docs(Set, Callbacks) ->
541573
erl_anno:new(Line),
542574
[],
543575
doc_value(Doc, Name),
544-
Meta
545-
} || Callback <- Callbacks, {{_, Name, _} = Key, Line, Doc, Meta} <- ets:lookup(Set, Callback)].
576+
Meta#{source_annos => [?ann(DefinitionMeta)]}
577+
} || {{Kind, Name, Arity}, DefinitionMeta} <- Callbacks, {Key, Line, Doc, Meta} <- ets:lookup(Set, {Kind, Name, Arity})].
546578

547579
get_type_docs(Set, Types) ->
548580
[{Key,
549581
erl_anno:new(Line),
550582
[],
551583
doc_value(Doc, Name),
552-
Meta
553-
} || {_Kind, {Name, Arity}, _, _, true} <- Types,
584+
Meta#{source_annos => [?ann(DefinitionMeta)]}
585+
} || {_Kind, {Name, Arity}, DefinitionMeta, _, true} <- Types,
554586
{Key, Line, Doc, Meta} <- ets:lookup(Set, {type, Name, Arity})].
555587

556588
signature_to_binary(_Module, Name, _Signature) when Name == '__aliases__'; Name == '__block__' ->
@@ -580,22 +612,23 @@ checker_chunk(Def, Defmacro, #{deprecated := Deprecated, defines_behaviour := De
580612

581613
Exports =
582614
[{FA, #{kind => def, deprecated_reason => maps:get(FA, DeprecatedMap, nil)}}
583-
|| FA <- prepend_behaviour_info(DefinesBehaviour, Def)] ++
615+
|| {FA, _Meta} <- prepend_behaviour_info(DefinesBehaviour, Def)] ++
584616
[{FA, #{kind => defmacro, deprecated_reason => maps:get(FA, DeprecatedMap, nil)}}
585-
|| FA <- Defmacro],
617+
|| {FA, _Meta} <- Defmacro],
586618

587619
Contents = #{
588620
exports => Exports
589621
},
590622

591623
[{<<"ExCk">>, term_to_binary({elixir_checker_v1, Contents}, [deterministic])}].
592624

593-
prepend_behaviour_info(true, Def) -> [{behaviour_info, 1} | Def];
625+
prepend_behaviour_info(true, Def) -> [{{behaviour_info, 1}, []} | Def];
594626
prepend_behaviour_info(false, Def) -> Def.
595627

596628
%% Errors
597629

598-
file_error(#{line := Line, file := File}, Error) ->
630+
file_error(#{anno := Anno, file := File}, Error) ->
631+
Line = erl_anno:line(Anno),
599632
elixir_errors:file_error([{line, Line}], File, ?MODULE, Error).
600633

601634
format_error({ill_defined_optional_callback, Callback}) ->

0 commit comments

Comments
 (0)