Skip to content
This repository has been archived by the owner on Sep 5, 2024. It is now read-only.

Commit

Permalink
otp/erl_lint: Generate deprecation and removal warnings from source
Browse files Browse the repository at this point in the history
  • Loading branch information
jhogberg committed Feb 12, 2020
1 parent d6ad22c commit 3b2e2f9
Show file tree
Hide file tree
Showing 9 changed files with 691 additions and 729 deletions.
9 changes: 9 additions & 0 deletions Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,15 @@ $(APPS):
ERL_TOP=$(ERL_TOP) PATH=$(BOOT_PREFIX)"$${PATH}" \
$(MAKE) $(TYPE) BUILD_ALL=true

##
## Generate `otp_internal` from the -deprecated() attributes in source.
##
deprecations: all
$(ERL_TOP)/lib/stdlib/scripts/update_deprecations \
$(ERL_TOP)/lib/*/ebin $(ERL_TOP)/erts/preloaded/ebin \
> $(ERL_TOP)/lib/stdlib/src/otp_internal.erl \
&& $(MAKE) stdlib

preloaded:
$(make_verbose)cd erts/preloaded/src && \
ERL_TOP=$(ERL_TOP) PATH=$(BOOT_PREFIX)"$${PATH}" \
Expand Down
17 changes: 0 additions & 17 deletions lib/edoc/src/edoc_data.erl
Original file line number Diff line number Diff line change
Expand Up @@ -328,33 +328,16 @@ get_deprecated(Ts, F, A, Env) ->
case otp_internal:obsolete(M, F, A) of
{Tag, Text} when Tag =:= deprecated; Tag =:= removed ->
deprecated([Text]);
{Tag, Repl, _Rel} when Tag =:= deprecated; Tag =:= removed ->
deprecated(Repl, Env);
_ ->
[]
end;
Es ->
Es
end.

deprecated(Repl, Env) ->
{Text, Ref} = replacement_function(Env#env.module, Repl),
Desc = ["Use ", {a, href(Ref, Env), [{code, [Text]}]}, " instead."],
deprecated(Desc).

deprecated(Desc) ->
[{deprecated, description(Desc)}].

-dialyzer({no_match, replacement_function/2}).

replacement_function(M0, {M,F,A}) when is_list(A) ->
%% refer to the largest listed arity - the most general version
replacement_function(M0, {M,F,lists:last(lists:sort(A))});
replacement_function(M, {M,F,A}) ->
{io_lib:fwrite("~w/~w", [F, A]), edoc_refs:function(F, A)};
replacement_function(_, {M,F,A}) ->
{io_lib:fwrite("~w:~w/~w", [M, F, A]), edoc_refs:function(M, F, A)}.

get_expr_ref(Expr) ->
case catch {ok, erl_syntax_lib:analyze_application(Expr)} of
{ok, {F, A}} when is_atom(F), is_integer(A) ->
Expand Down
149 changes: 149 additions & 0 deletions lib/stdlib/scripts/update_deprecations
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#!/usr/bin/env escript
%% -*- erlang -*-

%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2020. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%

-mode(compile).

-import(lists, [foldl/3,sort/1]).

-record(st, { functions = [], types = [] }).

main([_|_]=Directories) ->
emit(summarize(Directories)),
halt(0).

summarize(Directories) ->
foldl(fun summarize_directory/2, #st{}, Directories).

summarize_directory(Dir, Acc) ->
Files = [filename:join(Dir, F) || F <- filelib:wildcard("*.beam", Dir)],
foldl(fun summarize_file/2, Acc, Files).

summarize_file(File, Acc) ->
{ok, {Module, [Chunk]}} = beam_lib:chunks(File, [attributes]),
{attributes, Attributes} = Chunk,
summarize_attributes(Attributes, Module, Acc).

summarize_attributes([{deprecated, Ds} | As], Module, Acc0) ->
Fs = sa_1(Ds, deprecated, Module, Acc0#st.functions),
Acc = Acc0#st{ functions = Fs },
summarize_attributes(As, Module, Acc);
summarize_attributes([{removed, Rs} | As], Module, Acc0) ->
Fs = sa_1(Rs, removed, Module, Acc0#st.functions),
Acc = Acc0#st{ functions = Fs },
summarize_attributes(As, Module, Acc);
summarize_attributes([{deprecated_type, Ds} | As], Module, Acc0) ->
Ts = sa_1(Ds, deprecated, Module, Acc0#st.types),
Acc = Acc0#st{ types = Ts },
summarize_attributes(As, Module, Acc);
summarize_attributes([{removed_type, Rs} | As], Module, Acc0) ->
Ts = sa_1(Rs, removed, Module, Acc0#st.types),
Acc = Acc0#st{ types = Ts },
summarize_attributes(As, Module, Acc);
summarize_attributes([_ | As], Module, Acc) ->
summarize_attributes(As, Module, Acc);
summarize_attributes([], _Module, Acc) ->
Acc.

sa_1([{F, A, Info} | As], Tag, Module, Acc0) ->
sa_1(As, Tag, Module, [{Tag, Module, F, A, Info} | Acc0]);
sa_1([{F, A} | As], Tag, Module, Acc0) ->
sa_1(As, Tag, Module, [{Tag, Module, F, A, undefined} | Acc0]);
sa_1([module | As], Tag, Module, Acc0) ->
sa_1(As, Tag, Module, [{Tag, Module, '_', '_', undefined} | Acc0]);
sa_1([], _Tag, _Module, Acc) ->
Acc.

%%

emit(#st{ functions = Fs, types = Ts }) ->
io:format("%%\n"
"%% WARNING: DO NOT EDIT THIS FILE.\n"
"%%\n"
"%% This file was auto-generated from attributes in the source\n"
"%% code.\n"
"%%\n"
"%% To add a description to a deprecation or removal attribute,\n"
"%% write a string after the arity:\n"
"%%\n"
"%% -deprecated([{foo,1,\"use bar/1 instead\"}]).\n"
"%% -deprecated_type([{gadget,1,\"use widget/1 instead\"}]).\n"
"%% -removed([{hello,2,\"use there/2 instead\"}]).\n"
"%% -removed_type([{frobnitz,1,\"use grunka/1 instead\"}]).\n"
"%%\n"
"%% Descriptions cannot be given with the `f/1` shorthand, and\n"
"%% it will fall back to a generic description referring the\n"
"%% user to the documentation.\n"
"%%\n"
"%% Use `./otp_build update_deprecations` to update this file\n"
"%% after adding an attribute.\n"
"%%\n"
"-module(otp_internal).\n"
"-include(\"otp_internal.hrl\").\n"
"%%\n"),

emit_function("obsolete", Fs),
emit_function("obsolete_type", Ts),

ok.

emit_function(FuncName, Entries) ->
io:format("-dialyzer({no_match, ~ts/3}).\n", [FuncName]),
[emit_clause(FuncName, E) || E <- sort_clauses(Entries)],

io:format("~ts(_,_,_) -> no.\n\n", [FuncName]).

sort_clauses(Entries) ->
Tagged = [{clause_order(E), E} || E <- Entries],
[E || {_, E} <- sort(Tagged)].

%% Wildcard matches must be emitted *after* specific matches to avoid
%% losing descriptions.
clause_order({_Tag, _Module, F, A, _Info}) when F =/= '_', A =/= '_' -> 0;
clause_order({_Tag, _Module, F, '_', _Info}) when F =/= '_' -> 1;
clause_order({_Tag, _Module, '_', A, _Info}) when A =/= '_' -> 2;
clause_order({_Tag, _Module, '_', '_', _Info}) -> 3.

emit_clause(FuncName, {Tag, M, F, A, Info}) ->
io:format("~ts(~ts, ~ts, ~ts) ->\n"
" {~p, ~p};\n",
[FuncName, match_string(M), match_string(F), match_string(A),
Tag, info_string(Info)]).

%%

info_string(undefined) ->
"see the documentation for details";
info_string(next_version) ->
"will be removed in the next version. "
"See the documentation for details";
info_string(next_major_release) ->
"will be removed in the next major release. "
"See the documentation for details";
info_string(eventually) ->
"will be removed in a future release. "
"See the documentation for details";
info_string(String) when is_list(String) ->
String.

match_string('_') -> "_";
match_string(Term) -> io_lib:format("~p", [Term]).
2 changes: 1 addition & 1 deletion lib/stdlib/src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ HRL_FILES= \
../include/qlc.hrl \
../include/zip.hrl

INTERNAL_HRL_FILES= dets.hrl erl_tar.hrl
INTERNAL_HRL_FILES= dets.hrl erl_tar.hrl otp_internal.hrl

ERL_FILES= $(MODULES:%=%.erl)

Expand Down
34 changes: 23 additions & 11 deletions lib/stdlib/src/erl_lint.erl
Original file line number Diff line number Diff line change
Expand Up @@ -248,18 +248,18 @@ format_error({redefine_bif_import,{F,A}}) ->
format_error({deprecated, MFA, ReplacementMFA, Rel}) ->
io_lib:format("~s is deprecated and will be removed in ~s; use ~s",
[format_mfa(MFA), Rel, format_mfa(ReplacementMFA)]);
format_error({deprecated, {M1, F1, A1}, String}) when is_list(String) ->
io_lib:format("~p:~p/~p: ~s", [M1, F1, A1, String]);
format_error({deprecated, MFA, String}) when is_list(String) ->
io_lib:format("~s is deprecated; ~s", [format_mfa(MFA), String]);
format_error({deprecated_type, {M1, F1, A1}, String}) when is_list(String) ->
io_lib:format("~p:~p~s: ~s", [M1, F1, gen_type_paren(A1), String]);
io_lib:format("the type ~p:~p~s is deprecated; ~s",
[M1, F1, gen_type_paren(A1), String]);
format_error({removed, MFA, ReplacementMFA, Rel}) ->
io_lib:format("call to ~s will fail, since it was removed in ~s; "
"use ~s", [format_mfa(MFA), Rel, format_mfa(ReplacementMFA)]);
format_error({removed, MFA, String}) when is_list(String) ->
io_lib:format("~s: ~s", [format_mfa(MFA), String]);
format_error({removed_type, MNA, ReplacementMNA, Rel}) ->
io_lib:format("the type ~s was removed in ~s; use ~s instead",
[format_mna(MNA), Rel, format_mna(ReplacementMNA)]);
io_lib:format("~s is removed; ~s", [format_mfa(MFA), String]);
format_error({removed_type, MNA, String}) ->
io_lib:format("the type ~s is removed; ~s", [format_mna(MNA), String]);
format_error({obsolete_guard, {F, A}}) ->
io_lib:format("~p/~p obsolete (use is_~p/~p)", [F, A, F, A]);
format_error({obsolete_guard_overridden,Test}) ->
Expand Down Expand Up @@ -1062,7 +1062,7 @@ check_deprecated(Forms, St0) ->
true -> St0#lint.defined;
false -> St0#lint.exports
end,
X = gb_sets:to_list(Exports),
X = ignore_predefined_funcs(gb_sets:to_list(Exports)),
#lint{module = Mod} = St0,
Bad = [{E,L} || {attribute, L, deprecated, Depr} <- Forms,
D <- lists:flatten([Depr]),
Expand Down Expand Up @@ -1120,7 +1120,7 @@ check_removed(Forms, St0) ->
true -> St0#lint.defined;
false -> St0#lint.exports
end,
X = gb_sets:to_list(Exports),
X = ignore_predefined_funcs(gb_sets:to_list(Exports)),
#lint{module = Mod} = St0,
Bad = [{E,L} || {attribute, L, removed, Removed} <- Forms,
R <- lists:flatten([Removed]),
Expand Down Expand Up @@ -1169,6 +1169,18 @@ removed_desc([Char | Str]) when is_integer(Char) -> removed_desc(Str);
removed_desc([]) -> true;
removed_desc(_) -> false.

%% Ignores functions added by erl_internal:add_predefined_functions/1
ignore_predefined_funcs([{behaviour_info,1} | Fs]) ->
ignore_predefined_funcs(Fs);
ignore_predefined_funcs([{module_info,0} | Fs]) ->
ignore_predefined_funcs(Fs);
ignore_predefined_funcs([{module_info,1} | Fs]) ->
ignore_predefined_funcs(Fs);
ignore_predefined_funcs([Other | Fs]) ->
[Other | ignore_predefined_funcs(Fs)];
ignore_predefined_funcs([]) ->
[].

%% check_imports(Forms, State0) -> State

check_imports(Forms, St0) ->
Expand Down Expand Up @@ -3904,8 +3916,8 @@ deprecated_type(L, M, N, As, St) ->
false ->
St
end;
{removed, Replacement, Rel} ->
add_warning(L, {removed_type, {M,N,NAs}, Replacement, Rel}, St);
{removed, String} ->
add_warning(L, {removed_type, {M,N,NAs}, String}, St);
no ->
St
end.
Expand Down
Loading

0 comments on commit 3b2e2f9

Please sign in to comment.