Skip to content

Commit 1d58413

Browse files
ggcampinhojosevalim
authored andcommitted
Introduce @deprecated (#7113)
1 parent e1941a7 commit 1d58413

File tree

7 files changed

+176
-65
lines changed

7 files changed

+176
-65
lines changed

lib/elixir/lib/module.ex

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,17 @@ defmodule Module do
104104
Multiple uses of `@compile` will accumulate instead of overriding
105105
previous ones. See the "Compile options" section below.
106106
107+
### `@deprecated`
108+
109+
Provides the deprecation reason for a function. For example:
110+
111+
defmodule Keyword do
112+
@deprecated "Use Kernel.length/1 instead"
113+
def size(keyword) do
114+
length(keyword)
115+
end
116+
end
117+
107118
### `@doc` (and `@since`)
108119
109120
Provides documentation for the function or macro that follows the
@@ -1177,22 +1188,29 @@ defmodule Module do
11771188
@doc false
11781189
# Used internally to compile documentation.
11791190
# This function is private and must be used only internally.
1180-
def compile_doc(env, kind, name, args, _guards, _body) do
1191+
def compile_definition_attributes(env, kind, name, args, _guards, _body) do
11811192
module = env.module
11821193
table = data_table_for(module)
11831194
arity = length(args)
11841195
pair = {name, arity}
11851196

1197+
# Docs must come first as they read the impl callback
1198+
compile_doc(table, pair, env, kind, args)
1199+
compile_deprecated(table, pair)
1200+
compile_impl(table, name, env, kind, args)
1201+
:ok
1202+
end
1203+
1204+
defp compile_doc(table, pair, env, kind, args) do
11861205
{line, doc} = get_doc_info(table, env)
11871206

11881207
# TODO: Store @since alongside the docs
11891208
_ = get_since_info(table)
11901209

11911210
add_doc(table, line, kind, pair, args, doc, env)
1192-
:ok
11931211
end
11941212

1195-
defp add_doc(_module, line, kind, {name, arity}, _args, doc, env)
1213+
defp add_doc(_table, line, kind, {name, arity}, _args, doc, env)
11961214
when kind in [:defp, :defmacrop] do
11971215
if doc do
11981216
error_message =
@@ -1217,12 +1235,14 @@ defmodule Module do
12171235
end
12181236
end
12191237

1220-
@doc false
1221-
# Used internally to check the validity of arguments to @impl.
1222-
# This function is private and must be used only internally.
1223-
def compile_impl(env, kind, name, args, _guards, _body) do
1224-
%{module: module, line: line, file: file} = env
1225-
table = data_table_for(module)
1238+
defp compile_deprecated(table, pair) do
1239+
if reason = get_deprecated_info(table) do
1240+
:ets.insert(table, {{:deprecated, pair}, reason})
1241+
end
1242+
end
1243+
1244+
defp compile_impl(table, name, env, kind, args) do
1245+
%{line: line, file: file} = env
12261246

12271247
case :ets.take(table, :impl) do
12281248
[{:impl, value, _, _}] ->
@@ -1236,8 +1256,6 @@ defmodule Module do
12361256
[] ->
12371257
:ok
12381258
end
1239-
1240-
:ok
12411259
end
12421260

12431261
defp args_count([{:\\, _, _} | tail], total, defaults) do
@@ -1686,6 +1704,12 @@ defmodule Module do
16861704
"the version a function, macro, type or callback was added, got: #{inspect(value)}"
16871705
end
16881706

1707+
defp preprocess_attribute(:deprecated, value) when not is_binary(value) do
1708+
raise ArgumentError,
1709+
"@deprecated expects a string with the reason for the deprecation, " <>
1710+
"got: #{inspect(value)}"
1711+
end
1712+
16891713
defp preprocess_attribute(_key, value) do
16901714
value
16911715
end
@@ -1707,6 +1731,13 @@ defmodule Module do
17071731
:ets.take(table, :since)
17081732
end
17091733

1734+
defp get_deprecated_info(table) do
1735+
case :ets.take(table, :deprecated) do
1736+
[{:deprecated, reason, _, _}] -> reason
1737+
[] -> nil
1738+
end
1739+
end
1740+
17101741
defp data_table_for(module) do
17111742
:elixir_module.data_table(module)
17121743
end

lib/elixir/lib/protocol.ex

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ defmodule Protocol do
309309
end
310310

311311
defp beam_protocol(protocol) do
312-
chunk_ids = [:abstract_code, :attributes, :compile_info, 'ExDc']
312+
chunk_ids = [:abstract_code, :attributes, :compile_info, 'ExDc', 'ExDp']
313313
opts = [:allow_missing_chunks]
314314

315315
case :beam_lib.chunks(beam_file(protocol), chunk_ids, opts) do
@@ -318,12 +318,13 @@ defmodule Protocol do
318318
{:abstract_code, {_raw, abstract_code}},
319319
{:attributes, attributes},
320320
{:compile_info, compile_info},
321-
{'ExDc', docs}
321+
{'ExDc', docs},
322+
{'ExDp', deprecated}
322323
] = entries
323324

324325
case attributes[:protocol] do
325326
[fallback_to_any: any] ->
326-
{:ok, {protocol, any, abstract_code}, {compile_info, docs}}
327+
{:ok, {protocol, any, abstract_code}, {compile_info, docs, deprecated}}
327328

328329
_ ->
329330
{:error, :not_a_protocol}
@@ -497,14 +498,14 @@ defmodule Protocol do
497498
end
498499

499500
# Finally compile the module and emit its bytecode.
500-
defp compile(protocol, code, {compile_info, docs}) do
501+
defp compile(protocol, code, {compile_info, docs, deprecated}) do
501502
opts = Keyword.take(compile_info, [:source])
502503
opts = if Code.compiler_options()[:debug_info], do: [:debug_info | opts], else: opts
503504
{:ok, ^protocol, binary, _warnings} = :compile.forms(code, [:return | opts])
504505

505506
case docs do
506507
:missing_chunk -> {:ok, binary}
507-
_ -> {:ok, :elixir_erl.add_beam_chunks(binary, [{"ExDc", docs}])}
508+
_ -> {:ok, :elixir_erl.add_beam_chunks(binary, [{"ExDc", docs}, {"ExDp", deprecated}])}
508509
end
509510
end
510511

lib/elixir/src/elixir_erl.erl

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ debug_info(elixir_v1, _Module, none, _Opts) ->
2121
{error, missing};
2222
debug_info(elixir_v1, _Module, {elixir_v1, Map, _Specs}, _Opts) ->
2323
{ok, Map};
24-
debug_info(erlang_v1, _Module, {elixir_v1, Map, Specs}, _Opts) ->
25-
{Prefix, Forms, _, _} = dynamic_form(Map),
24+
debug_info(erlang_v1, Module, {elixir_v1, Map, Specs}, _Opts) ->
25+
{Prefix, Forms, _, _} = dynamic_form(Map, deprecated_chunk_from_beam(Module)),
2626
{ok, Prefix ++ Specs ++ Forms};
27-
debug_info(core_v1, _Module, {elixir_v1, Map, Specs}, Opts) ->
28-
{Prefix, Forms, _, _} = dynamic_form(Map),
27+
debug_info(core_v1, Module, {elixir_v1, Map, Specs}, Opts) ->
28+
{Prefix, Forms, _, _} = dynamic_form(Map, deprecated_chunk_from_beam(Module)),
2929
#{compile_opts := CompileOpts} = Map,
3030

3131
%% Do not rely on elixir_erl_compiler because we don't
@@ -39,6 +39,25 @@ debug_info(core_v1, _Module, {elixir_v1, Map, Specs}, Opts) ->
3939
debug_info(_, _, _, _) ->
4040
{error, unknown_format}.
4141

42+
%% `ExDp` chunk from beam
43+
44+
deprecated_chunk_from_beam(Module) ->
45+
Chunk = binary_to_list(<<"ExDp">>),
46+
Beam = abstract_code_beam(Module),
47+
48+
{elixir_deprecated_v1, Deprecated} =
49+
case beam_lib:chunks(Beam, [Chunk]) of
50+
{ok, {Module, [{Chunk, DeprecatedBin}]}} -> binary_to_term(DeprecatedBin);
51+
_ -> {elixir_deprecated_v1, []}
52+
end,
53+
Deprecated.
54+
55+
abstract_code_beam(Module) ->
56+
case code:get_object_code(Module) of
57+
{Module, Beam, _} -> Beam;
58+
error -> Module
59+
end.
60+
4261
%% Builds Erlang AST annotation.
4362

4463
get_ann(Opts) when is_list(Opts) ->
@@ -149,16 +168,17 @@ definition_scope(Meta, File) ->
149168

150169
compile(#{module := Module} = Map) ->
151170
Data = elixir_module:data_table(Module),
152-
{Prefix, Forms, Defmacro, Unreachable} = dynamic_form(Map),
171+
Deprecated = get_deprecated(Data),
172+
{Prefix, Forms, Defmacro, Unreachable} = dynamic_form(Map, Deprecated),
153173
Specs =
154174
case elixir_config:get(bootstrap) of
155175
true -> [];
156176
false -> specs_form(Map, Data, Defmacro, Unreachable, types_form(Data, []))
157177
end,
158-
load_form(Map, Data, Prefix, Forms, Specs).
178+
load_form(Map, Data, Prefix, Forms, Specs, Deprecated).
159179

160180
dynamic_form(#{module := Module, line := Line, file := File, attributes := Attributes,
161-
definitions := Definitions, unreachable := Unreachable}) ->
181+
definitions := Definitions, unreachable := Unreachable}, Deprecated) ->
162182
{Def, Defmacro, Macros, Exports, Functions} =
163183
split_definition(Definitions, File, Unreachable, [], [], [], [], {[], []}),
164184

@@ -167,7 +187,7 @@ dynamic_form(#{module := Module, line := Line, file := File, attributes := Attri
167187
{attribute, Line, module, Module},
168188
{attribute, Line, compile, no_auto_import}],
169189

170-
Forms0 = functions_form(Line, Module, Def, Defmacro, Exports, Functions),
190+
Forms0 = functions_form(Line, Module, Def, Defmacro, Exports, Functions, Deprecated),
171191
Forms1 = attributes_form(Line, Attributes, Forms0),
172192
{Prefix, Forms1, Macros, Unreachable}.
173193

@@ -263,12 +283,12 @@ is_macro(_) -> false.
263283

264284
% Functions
265285

266-
functions_form(Line, Module, Def, Defmacro, Exports, Body) ->
267-
{Spec, Info} = add_info_function(Line, Module, Def, Defmacro),
286+
functions_form(Line, Module, Def, Defmacro, Exports, Body, Deprecated) ->
287+
{Spec, Info} = add_info_function(Line, Module, Def, Defmacro, Deprecated),
268288
[{attribute, Line, export, lists:sort([{'__info__', 1} | Exports])}, Spec, Info | Body].
269289

270-
add_info_function(Line, Module, Def, Defmacro) ->
271-
AllowedAttrs = [attributes, compile, functions, macros, md5, module],
290+
add_info_function(Line, Module, Def, Defmacro, Deprecated) ->
291+
AllowedAttrs = [attributes, compile, functions, macros, md5, module, deprecated],
272292
AllowedArgs = lists:map(fun(Atom) -> {atom, Line, Atom} end, AllowedAttrs),
273293

274294
Spec =
@@ -303,7 +323,8 @@ add_info_function(Line, Module, Def, Defmacro) ->
303323
macros_info(Defmacro),
304324
get_module_info(Module, attributes),
305325
get_module_info(Module, compile),
306-
get_module_info(Module, md5)
326+
get_module_info(Module, md5),
327+
deprecated_info(Deprecated)
307328
]},
308329

309330
{Spec, Info}.
@@ -321,6 +342,9 @@ get_module_info(Module, Key) ->
321342
Call = remote(0, erlang, get_module_info, [{atom, 0, Module}, {atom, 0, Key}]),
322343
{clause, 0, [{atom, 0, Key}], [], [Call]}.
323344

345+
deprecated_info(Deprecated) ->
346+
{clause, 0, [{atom, 0, deprecated}], [], [elixir_erl:elixir_to_erl(Deprecated)]}.
347+
324348
% Types
325349

326350
types_form(Data, Forms) ->
@@ -442,8 +466,9 @@ attributes_form(Line, Attributes, Forms) ->
442466

443467
% Loading forms
444468

445-
load_form(#{line := Line, file := File, compile_opts := Opts} = Map, Data, Prefix, Forms, Specs) ->
446-
{ExtraChunks, CompileOpts} = extra_chunks(Data, Line, debug_opts(Map, Specs, Opts)),
469+
load_form(#{line := Line, file := File, compile_opts := Opts} = Map,
470+
Data, Prefix, Forms, Specs, Deprecated) ->
471+
{ExtraChunks, CompileOpts} = extra_chunks(Data, Deprecated, Line, debug_opts(Map, Specs, Opts)),
447472
{_, Binary} = elixir_erl_compiler:forms(Prefix ++ Specs ++ Forms, File, CompileOpts),
448473
add_beam_chunks(Binary, ExtraChunks).
449474

@@ -469,12 +494,17 @@ supports_debug_tuple() ->
469494
_ -> true
470495
end.
471496

472-
extra_chunks(Data, Line, Opts) ->
497+
extra_chunks(Data, Deprecated, Line, Opts) ->
473498
Supported = supports_extra_chunks_option(),
474-
case docs_chunk(Data, Line, elixir_compiler:get_opt(docs)) of
499+
Chunks0 = lists:flatten([
500+
docs_chunk(Data, Line, elixir_compiler:get_opt(docs)),
501+
deprecated_chunk(Deprecated)
502+
]),
503+
504+
case Chunks0 of
475505
[] -> {[], Opts};
476-
Chunks when Supported -> {[], [{extra_chunks, Chunks} | Opts]};
477-
Chunks -> {Chunks, Opts}
506+
Chunks1 when Supported -> {[], [{extra_chunks, Chunks1} | Opts]};
507+
Chunks1 -> {Chunks1, Opts}
478508
end.
479509

480510
supports_extra_chunks_option() ->
@@ -495,6 +525,10 @@ docs_chunk(Data, Line, true) ->
495525
docs_chunk(_, _, _) ->
496526
[].
497527

528+
deprecated_chunk(Deprecated) ->
529+
ChunkData = term_to_binary({elixir_deprecated_v1, Deprecated}, [compressed]),
530+
[{<<"ExDp">>, ChunkData}].
531+
498532
get_moduledoc(Line, Data) ->
499533
case ets:lookup_element(Data, moduledoc, 2) of
500534
nil -> {Line, nil};
@@ -513,6 +547,9 @@ get_type_docs(Data) ->
513547
lists:usort(ets:select(Data, [{{{typedoc, '$1'}, '$2', '$3', '$4'},
514548
[], [{{'$1', '$2', '$3', '$4'}}]}])).
515549

550+
get_deprecated(Data) ->
551+
lists:usort(ets:select(Data, [{{{deprecated, '$1'}, '$2'}, [], [{{'$1', '$2'}}]}])).
552+
516553
%% Errors
517554

518555
form_error(#{line := Line, file := File}, Error) ->

lib/elixir/src/elixir_module.erl

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
-module(elixir_module).
2-
-export([data_table/1, defs_table/1, is_open/1, delete_doc/6,
3-
compile/4, expand_callback/6, format_error/1,
4-
compiler_modules/0, delete_impl/6,
2+
-export([data_table/1, defs_table/1, is_open/1, delete_definition_attributes/6,
3+
compile/4, expand_callback/6, format_error/1, compiler_modules/0,
54
write_cache/3, read_cache/2]).
65
-include("elixir.hrl").
76

@@ -32,13 +31,11 @@ defs_table(Module) ->
3231
is_open(Module) ->
3332
ets:lookup(elixir_modules, Module) /= [].
3433

35-
delete_doc(#{module := Module}, _, _, _, _, _) ->
36-
ets:delete(data_table(Module), doc),
37-
ok.
38-
39-
delete_impl(#{module := Module}, _, _, _, _, _) ->
40-
ets:delete(data_table(Module), impl),
41-
ok.
34+
delete_definition_attributes(#{module := Module}, _, _, _, _, _) ->
35+
Data = data_table(Module),
36+
ets:delete(Data, doc),
37+
ets:delete(Data, deprecated),
38+
ets:delete(Data, impl).
4239

4340
write_cache(Module, Key, Value) ->
4441
ets:insert(data_table(Module), {{cache, Key}, Value}).
@@ -176,20 +173,11 @@ build(Line, File, Module, Lexical) ->
176173
Ref = elixir_code_server:call({defmodule, self(),
177174
{Module, Data, Defs, Line, File}}),
178175

179-
DocsOnDefinition =
180-
case elixir_compiler:get_opt(docs) of
181-
true -> [{'Elixir.Module', compile_doc}];
182-
_ -> [{elixir_module, delete_doc}]
183-
end,
184-
185-
ImplOnDefinition =
186-
case elixir_config:get(bootstrap) of
187-
true -> [{elixir_module, delete_impl}];
188-
_ -> [{'Elixir.Module', compile_impl}]
189-
end,
190-
191-
%% Docs must come first as they read the impl callback.
192-
OnDefinition = DocsOnDefinition ++ ImplOnDefinition,
176+
OnDefinition =
177+
case elixir_config:get(bootstrap) of
178+
false -> [{'Elixir.Module', compile_definition_attributes}];
179+
_ -> [{elixir_module, delete_definition_attributes}]
180+
end,
193181

194182
ets:insert(Data, [
195183
% {Key, Value, Accumulate?, UnreadLine}

0 commit comments

Comments
 (0)