aboutsummaryrefslogtreecommitdiffstats
path: root/lib/stdlib/src/erl_lint.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/stdlib/src/erl_lint.erl')
-rw-r--r--lib/stdlib/src/erl_lint.erl1517
1 files changed, 875 insertions, 642 deletions
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index 7c064ce902..f58cb35cea 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -2,18 +2,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2014. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -26,12 +27,10 @@
-export([module/1,module/2,module/3,format_error/1]).
-export([exprs/2,exprs_opt/3,used_vars/2]). % Used from erl_eval.erl.
--export([is_pattern_expr/1,is_guard_test/1,is_guard_test/2]).
+-export([is_pattern_expr/1,is_guard_test/1,is_guard_test/2,is_guard_test/3]).
-export([is_guard_expr/1]).
-export([bool_option/4,value_option/3,value_option/7]).
--export([modify_line/2]).
-
-import(lists, [member/2,map/2,foldl/3,foldr/3,mapfoldl/3,all/2,reverse/1]).
%% bool_option(OnOpt, OffOpt, Default, Options) -> boolean().
@@ -76,7 +75,7 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) ->
%%-define(DEBUGF(X,Y), io:format(X, Y)).
-define(DEBUGF(X,Y), void).
--type line() :: erl_scan:line(). % a convenient alias
+-type line() :: erl_anno:anno(). % a convenient alias
-type fa() :: {atom(), arity()}. % function+arity
-type ta() :: {atom(), arity()}. % type+arity
@@ -93,14 +92,22 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) ->
:: dict:dict(ta(), line())
}).
+
+%% Are we outside or inside a catch or try/catch?
+-type catch_scope() :: 'none'
+ | 'after_old_catch'
+ | 'after_try'
+ | 'wrong_part_of_try'
+ | 'try_catch'.
+
%% Define the lint state record.
%% 'called' and 'exports' contain {Line, {Function, Arity}},
%% the other function collections contain {Function, Arity}.
-record(lint, {state=start :: 'start' | 'attribute' | 'function',
- module=[], %Module
+ module='', %Module
behaviour=[], %Behaviour
exports=gb_sets:empty() :: gb_sets:set(fa()),%Exports
- imports=[] :: [fa()], %Imports, an orddict()
+ imports=[] :: orddict:orddict(fa(), module()),%Imports
compile=[], %Compile flags
records=dict:new() %Record definitions
:: dict:dict(atom(), {line(),Fields :: term()}),
@@ -111,34 +118,39 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) ->
defined=gb_sets:empty() %Defined fuctions
:: gb_sets:set(fa()),
on_load=[] :: [fa()], %On-load function
- on_load_line=0 :: line(), %Line for on_load
+ on_load_line=erl_anno:new(0) %Line for on_load
+ :: erl_anno:anno(),
clashes=[], %Exported functions named as BIFs
not_deprecated=[], %Not considered deprecated
func=[], %Current function
warn_format=0, %Warn format calls
enabled_warnings=[], %All enabled warnings (ordset).
+ nowarn_bif_clash=[], %All no warn bif clashes (ordset).
errors=[], %Current errors
warnings=[], %Current warnings
file = "" :: string(), %From last file attribute
recdef_top=false :: boolean(), %true in record initialisation
%outside any fun or lc
xqlc= false :: boolean(), %true if qlc.hrl included
- new = false :: boolean(), %Has user-defined 'new/N'
called= [] :: [{fa(),line()}], %Called functions
usage = #usage{} :: #usage{},
specs = dict:new() %Type specifications
:: dict:dict(mfa(), line()),
callbacks = dict:new() %Callback types
:: dict:dict(mfa(), line()),
+ optional_callbacks = dict:new() %Optional callbacks
+ :: dict:dict(mfa(), line()),
types = dict:new() %Type definitions
:: dict:dict(ta(), #typeinfo{}),
exp_types=gb_sets:empty() %Exported types
- :: gb_sets:set(ta())
+ :: gb_sets:set(ta()),
+ catch_scope = none %Inside/outside try or catch
+ :: catch_scope()
}).
-type lint_state() :: #lint{}.
-type error_description() :: term().
--type error_info() :: {erl_scan:line(), module(), error_description()}.
+-type error_info() :: {erl_anno:line(), module(), error_description()}.
%% format_error(Error)
%% Return a string describing the error.
@@ -154,6 +166,8 @@ format_error(pmod_unsupported) ->
"parameterized modules are no longer supported";
%% format_error({redefine_mod_import, M, P}) ->
%% io_lib:format("module '~s' already imported from package '~s'", [M, P]);
+format_error(non_latin1_module_unsupported) ->
+ "module names with non-latin1 characters are not supported";
format_error(invalid_call) ->
"invalid function call";
@@ -161,49 +175,48 @@ format_error(invalid_record) ->
"invalid record expression";
format_error({attribute,A}) ->
- io_lib:format("attribute '~w' after function definitions", [A]);
+ io_lib:format("attribute ~tw after function definitions", [A]);
format_error({missing_qlc_hrl,A}) ->
io_lib:format("qlc:q/~w called, but \"qlc.hrl\" not included", [A]);
format_error({redefine_import,{{F,A},M}}) ->
- io_lib:format("function ~w/~w already imported from ~w", [F,A,M]);
+ io_lib:format("function ~tw/~w already imported from ~w", [F,A,M]);
format_error({bad_inline,{F,A}}) ->
- io_lib:format("inlined function ~w/~w undefined", [F,A]);
+ io_lib:format("inlined function ~tw/~w undefined", [F,A]);
format_error({invalid_deprecated,D}) ->
- io_lib:format("badly formed deprecated attribute ~w", [D]);
+ io_lib:format("badly formed deprecated attribute ~tw", [D]);
format_error({bad_deprecated,{F,A}}) ->
- io_lib:format("deprecated function ~w/~w undefined or not exported", [F,A]);
+ io_lib:format("deprecated function ~tw/~w undefined or not exported",
+ [F,A]);
format_error({bad_nowarn_unused_function,{F,A}}) ->
- io_lib:format("function ~w/~w undefined", [F,A]);
+ io_lib:format("function ~tw/~w undefined", [F,A]);
format_error({bad_nowarn_bif_clash,{F,A}}) ->
- io_lib:format("function ~w/~w undefined", [F,A]);
+ io_lib:format("function ~tw/~w undefined", [F,A]);
format_error(disallowed_nowarn_bif_clash) ->
io_lib:format("compile directive nowarn_bif_clash is no longer allowed,~n"
" - use explicit module names or -compile({no_auto_import, [F/A]})", []);
-format_error({bad_nowarn_deprecated_function,{M,F,A}}) ->
- io_lib:format("~w:~w/~w is not a deprecated function", [M,F,A]);
format_error({bad_on_load,Term}) ->
- io_lib:format("badly formed on_load attribute: ~w", [Term]);
+ io_lib:format("badly formed on_load attribute: ~tw", [Term]);
format_error(multiple_on_loads) ->
"more than one on_load attribute";
format_error({bad_on_load_arity,{F,A}}) ->
- io_lib:format("function ~w/~w has wrong arity (must be 0)", [F,A]);
+ io_lib:format("function ~tw/~w has wrong arity (must be 0)", [F,A]);
format_error({undefined_on_load,{F,A}}) ->
- io_lib:format("function ~w/~w undefined", [F,A]);
+ io_lib:format("function ~tw/~w undefined", [F,A]);
format_error(export_all) ->
"export_all flag enabled - all functions will be exported";
format_error({duplicated_export, {F,A}}) ->
- io_lib:format("function ~w/~w already exported", [F,A]);
+ io_lib:format("function ~tw/~w already exported", [F,A]);
format_error({unused_import,{{F,A},M}}) ->
- io_lib:format("import ~w:~w/~w is unused", [M,F,A]);
+ io_lib:format("import ~w:~tw/~w is unused", [M,F,A]);
format_error({undefined_function,{F,A}}) ->
- io_lib:format("function ~w/~w undefined", [F,A]);
+ io_lib:format("function ~tw/~w undefined", [F,A]);
format_error({redefine_function,{F,A}}) ->
- io_lib:format("function ~w/~w already defined", [F,A]);
+ io_lib:format("function ~tw/~w already defined", [F,A]);
format_error({define_import,{F,A}}) ->
- io_lib:format("defining imported function ~w/~w", [F,A]);
+ io_lib:format("defining imported function ~tw/~w", [F,A]);
format_error({unused_function,{F,A}}) ->
- io_lib:format("function ~w/~w is unused", [F,A]);
+ io_lib:format("function ~tw/~w is unused", [F,A]);
format_error({call_to_redefined_bif,{F,A}}) ->
io_lib:format("ambiguous call of overridden auto-imported BIF ~w/~w~n"
" - use erlang:~w/~w or \"-compile({no_auto_import,[~w/~w]}).\" "
@@ -219,33 +232,47 @@ format_error({redefine_old_bif_import,{F,A}}) ->
format_error({redefine_bif_import,{F,A}}) ->
io_lib:format("import directive overrides auto-imported BIF ~w/~w~n"
" - use \"-compile({no_auto_import,[~w/~w]}).\" to resolve name clash", [F,A,F,A]);
-
+format_error({get_stacktrace,wrong_part_of_try}) ->
+ "erlang:get_stacktrace/0 used in the wrong part of 'try' expression. "
+ "(Use it in the block between 'catch' and 'end'.)";
+format_error({get_stacktrace,after_old_catch}) ->
+ "erlang:get_stacktrace/0 used following an old-style 'catch' "
+ "may stop working in a future release. (Use it inside 'try'.)";
+format_error({get_stacktrace,after_try}) ->
+ "erlang:get_stacktrace/0 used following a 'try' expression "
+ "may stop working in a future release. (Use it inside 'try'.)";
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_type, {M1, F1, A1}, String}) when is_list(String) ->
+ io_lib:format("~p:~p~s: ~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)]);
format_error({obsolete_guard, {F, A}}) ->
- io_lib:format("~p/~p obsolete", [F, A]);
+ io_lib:format("~p/~p obsolete (use is_~p/~p)", [F, A, F, A]);
+format_error({obsolete_guard_overridden,Test}) ->
+ io_lib:format("obsolete ~s/1 (meaning is_~s/1) is illegal when "
+ "there is a local/imported function named is_~p/1 ",
+ [Test,Test,Test]);
format_error({too_many_arguments,Arity}) ->
io_lib:format("too many arguments (~w) - "
"maximum allowed is ~w", [Arity,?MAX_ARGUMENTS]);
%% --- patterns and guards ---
format_error(illegal_pattern) -> "illegal pattern";
-format_error(illegal_map_key) ->
- "illegal map key";
-format_error({illegal_map_key_variable,K}) ->
- io_lib:format("illegal use of variable ~w in map",[K]);
+format_error(illegal_map_key) -> "illegal map key in pattern";
format_error(illegal_bin_pattern) ->
"binary patterns cannot be matched in parallel using '='";
format_error(illegal_expr) -> "illegal expression";
format_error({illegal_guard_local_call, {F,A}}) ->
- io_lib:format("call to local/imported function ~w/~w is illegal in guard",
+ io_lib:format("call to local/imported function ~tw/~w is illegal in guard",
[F,A]);
format_error(illegal_guard_expr) -> "illegal guard expression";
%% --- maps ---
@@ -253,23 +280,23 @@ format_error(illegal_map_construction) ->
"only association operators '=>' are allowed in map construction";
%% --- records ---
format_error({undefined_record,T}) ->
- io_lib:format("record ~w undefined", [T]);
+ io_lib:format("record ~tw undefined", [T]);
format_error({redefine_record,T}) ->
- io_lib:format("record ~w already defined", [T]);
+ io_lib:format("record ~tw already defined", [T]);
format_error({redefine_field,T,F}) ->
- io_lib:format("field ~w already defined in record ~w", [F,T]);
+ io_lib:format("field ~tw already defined in record ~tw", [F,T]);
format_error({undefined_field,T,F}) ->
- io_lib:format("field ~w undefined in record ~w", [F,T]);
+ io_lib:format("field ~tw undefined in record ~tw", [F,T]);
format_error(illegal_record_info) ->
"illegal record info";
format_error({field_name_is_variable,T,F}) ->
- io_lib:format("field ~w is not an atom or _ in record ~w", [F,T]);
+ io_lib:format("field ~tw is not an atom or _ in record ~tw", [F,T]);
format_error({wildcard_in_update,T}) ->
- io_lib:format("meaningless use of _ in update of record ~w", [T]);
+ io_lib:format("meaningless use of _ in update of record ~tw", [T]);
format_error({unused_record,T}) ->
- io_lib:format("record ~w is unused", [T]);
+ io_lib:format("record ~tw is unused", [T]);
format_error({untyped_record,T}) ->
- io_lib:format("record ~w has field(s) without type information", [T]);
+ io_lib:format("record ~tw has field(s) without type information", [T]);
%% --- variables ----
format_error({unbound_var,V}) ->
io_lib:format("variable ~w is unbound", [V]);
@@ -287,7 +314,10 @@ format_error({variable_in_record_def,V}) ->
io_lib:format("variable ~w in record definition", [V]);
%% --- binaries ---
format_error({undefined_bittype,Type}) ->
- io_lib:format("bit type ~w undefined", [Type]);
+ io_lib:format("bit type ~tw undefined", [Type]);
+format_error({bittype_mismatch,Val1,Val2,What}) ->
+ io_lib:format("conflict in ~s specification for bit field: '~p' and '~p'",
+ [What,Val1,Val2]);
format_error(bittype_unit) ->
"a bit unit size must not be specified unless a size is specified too";
format_error(illegal_bitsize) ->
@@ -304,41 +334,44 @@ format_error(unsized_binary_in_bin_gen_pattern) ->
"binary fields without size are not allowed in patterns of bit string generators";
%% --- behaviours ---
format_error({conflicting_behaviours,{Name,Arity},B,FirstL,FirstB}) ->
- io_lib:format("conflicting behaviours - callback ~w/~w required by both '~p' "
+ io_lib:format("conflicting behaviours - callback ~tw/~w required by both '~p' "
"and '~p' ~s", [Name,Arity,B,FirstB,format_where(FirstL)]);
format_error({undefined_behaviour_func, {Func,Arity}, Behaviour}) ->
- io_lib:format("undefined callback function ~w/~w (behaviour '~w')",
+ io_lib:format("undefined callback function ~tw/~w (behaviour '~w')",
[Func,Arity,Behaviour]);
format_error({undefined_behaviour,Behaviour}) ->
- io_lib:format("behaviour ~w undefined", [Behaviour]);
+ io_lib:format("behaviour ~tw undefined", [Behaviour]);
format_error({undefined_behaviour_callbacks,Behaviour}) ->
io_lib:format("behaviour ~w callback functions are undefined",
- [Behaviour]);
+ [Behaviour]);
format_error({ill_defined_behaviour_callbacks,Behaviour}) ->
io_lib:format("behaviour ~w callback functions erroneously defined",
[Behaviour]);
+format_error({ill_defined_optional_callbacks,Behaviour}) ->
+ io_lib:format("behaviour ~w optional callback functions erroneously defined",
+ [Behaviour]);
format_error({behaviour_info, {_M,F,A}}) ->
- io_lib:format("cannot define callback attibute for ~w/~w when "
+ io_lib:format("cannot define callback attibute for ~tw/~w when "
"behaviour_info is defined",[F,A]);
+format_error({redefine_optional_callback, {F, A}}) ->
+ io_lib:format("optional callback ~tw/~w duplicated", [F, A]);
+format_error({undefined_callback, {_M, F, A}}) ->
+ io_lib:format("callback ~tw/~w is undefined", [F, A]);
%% --- types and specs ---
format_error({singleton_typevar, Name}) ->
io_lib:format("type variable ~w is only used once (is unbound)", [Name]);
format_error({bad_export_type, _ETs}) ->
io_lib:format("bad export_type declaration", []);
format_error({duplicated_export_type, {T, A}}) ->
- io_lib:format("type ~w/~w already exported", [T, A]);
+ io_lib:format("type ~tw/~w already exported", [T, A]);
format_error({undefined_type, {TypeName, Arity}}) ->
- io_lib:format("type ~w~s undefined", [TypeName, gen_type_paren(Arity)]);
+ io_lib:format("type ~tw~s undefined", [TypeName, gen_type_paren(Arity)]);
format_error({unused_type, {TypeName, Arity}}) ->
- io_lib:format("type ~w~s is unused", [TypeName, gen_type_paren(Arity)]);
-%% format_error({new_builtin_type, {TypeName, Arity}}) ->
-%% io_lib:format("type ~w~s is a new builtin type; "
-%% "its (re)definition is allowed only until the next release",
-%% [TypeName, gen_type_paren(Arity)]);
-format_error({new_var_arity_type, TypeName}) ->
- io_lib:format("type ~w is a new builtin type; "
+ io_lib:format("type ~tw~s is unused", [TypeName, gen_type_paren(Arity)]);
+format_error({new_builtin_type, {TypeName, Arity}}) ->
+ io_lib:format("type ~w~s is a new builtin type; "
"its (re)definition is allowed only until the next release",
- [TypeName]);
+ [TypeName, gen_type_paren(Arity)]);
format_error({builtin_type, {TypeName, Arity}}) ->
io_lib:format("type ~w~s is a builtin type; it cannot be redefined",
[TypeName, gen_type_paren(Arity)]);
@@ -346,22 +379,30 @@ format_error({renamed_type, OldName, NewName}) ->
io_lib:format("type ~w() is now called ~w(); "
"please use the new name instead", [OldName, NewName]);
format_error({redefine_type, {TypeName, Arity}}) ->
- io_lib:format("type ~w~s already defined",
+ io_lib:format("type ~tw~s already defined",
[TypeName, gen_type_paren(Arity)]);
format_error({type_syntax, Constr}) ->
- io_lib:format("bad ~w type", [Constr]);
+ io_lib:format("bad ~tw type", [Constr]);
+format_error(old_abstract_code) ->
+ io_lib:format("abstract code generated before Erlang/OTP 19.0 and "
+ "having typed record fields cannot be compiled", []);
format_error({redefine_spec, {M, F, A}}) ->
- io_lib:format("spec for ~w:~w/~w already defined", [M, F, A]);
-format_error({redefine_callback, {M, F, A}}) ->
- io_lib:format("callback ~w:~w/~w already defined", [M, F, A]);
-format_error({spec_fun_undefined, {M, F, A}}) ->
- io_lib:format("spec for undefined function ~w:~w/~w", [M, F, A]);
+ io_lib:format("spec for ~tw:~tw/~w already defined", [M, F, A]);
+format_error({redefine_spec, {F, A}}) ->
+ io_lib:format("spec for ~tw/~w already defined", [F, A]);
+format_error({redefine_callback, {F, A}}) ->
+ io_lib:format("callback ~tw/~w already defined", [F, A]);
+format_error({bad_callback, {M, F, A}}) ->
+ io_lib:format("explicit module not allowed for callback ~tw:~tw/~w",
+ [M, F, A]);
+format_error({spec_fun_undefined, {F, A}}) ->
+ io_lib:format("spec for undefined function ~tw/~w", [F, A]);
format_error({missing_spec, {F,A}}) ->
- io_lib:format("missing specification for function ~w/~w", [F, A]);
+ io_lib:format("missing specification for function ~tw/~w", [F, A]);
format_error(spec_wrong_arity) ->
- "spec has the wrong arity";
+ "spec has wrong arity";
format_error(callback_wrong_arity) ->
- "callback has the wrong arity";
+ "callback has wrong arity";
format_error({deprecated_builtin_type, {Name, Arity},
Replacement, Rel}) ->
UseS = case Replacement of
@@ -376,16 +417,18 @@ format_error({deprecated_builtin_type, {Name, Arity},
"removed in ~s; use ~s",
[Name, Arity, Rel, UseS]);
format_error({not_exported_opaque, {TypeName, Arity}}) ->
- io_lib:format("opaque type ~w~s is not exported",
+ io_lib:format("opaque type ~tw~s is not exported",
[TypeName, gen_type_paren(Arity)]);
format_error({underspecified_opaque, {TypeName, Arity}}) ->
- io_lib:format("opaque type ~w~s is underspecified and therefore meaningless",
+ io_lib:format("opaque type ~tw~s is underspecified and therefore meaningless",
[TypeName, gen_type_paren(Arity)]);
+format_error({bad_dialyzer_attribute,Term}) ->
+ io_lib:format("badly formed dialyzer attribute: ~tw", [Term]);
+format_error({bad_dialyzer_option,Term}) ->
+ io_lib:format("unknown dialyzer warning option: ~tw", [Term]);
%% --- obsolete? unused? ---
format_error({format_error, {Fmt, Args}}) ->
- io_lib:format(Fmt, Args);
-format_error({mnemosyne, What}) ->
- "mnemosyne " ++ What ++ ", missing transformation".
+ io_lib:format(Fmt, Args).
gen_type_paren(Arity) when is_integer(Arity), Arity >= 0 ->
gen_type_paren_1(Arity, ")").
@@ -403,6 +446,9 @@ format_mfa({M, F, A}) when is_integer(A) ->
format_mf(M, F, ArityString) when is_atom(M), is_atom(F) ->
atom_to_list(M) ++ ":" ++ atom_to_list(F) ++ "/" ++ ArityString.
+format_mna({M, N, A}) when is_integer(A) ->
+ atom_to_list(M) ++ ":" ++ atom_to_list(N) ++ gen_type_paren(A).
+
format_where(L) when is_integer(L) ->
io_lib:format("(line ~p)", [L]);
format_where({L,C}) when is_integer(L), is_integer(C) ->
@@ -421,13 +467,13 @@ exprs(Exprs, BindingsList) ->
exprs_opt(Exprs, BindingsList, Opts) ->
{St0,Vs} = foldl(fun({{record,_SequenceNumber,_Name},Attr0}, {St1,Vs1}) ->
- Attr = zip_file_and_line(Attr0, "none"),
+ Attr = set_file(Attr0, "none"),
{attribute_state(Attr, St1),Vs1};
({V,_}, {St1,Vs1}) ->
{St1,[{V,{bound,unused,[]}} | Vs1]}
end, {start("nofile",Opts),[]}, BindingsList),
Vt = orddict:from_list(Vs),
- {_Evt,St} = exprs(zip_file_and_line(Exprs, "nofile"), Vt, St0),
+ {_Evt,St} = exprs(set_file(Exprs, "nofile"), Vt, St0),
return_status(St).
used_vars(Exprs, BindingsList) ->
@@ -435,7 +481,7 @@ used_vars(Exprs, BindingsList) ->
({V,_Val}, Vs0) -> [{V,{bound,unused,[]}} | Vs0]
end, [], BindingsList),
Vt = orddict:from_list(Vs),
- {Evt,_St} = exprs(zip_file_and_line(Exprs, "nofile"), Vt, start()),
+ {Evt,_St} = exprs(set_file(Exprs, "nofile"), Vt, start()),
{ok, foldl(fun({V,{_,used,_}}, L) -> [V | L];
(_, L) -> L
end, [], Evt)}.
@@ -449,7 +495,7 @@ used_vars(Exprs, BindingsList) ->
%% really all ordsets!
-spec(module(AbsForms) -> {ok, Warnings} | {error, Errors, Warnings} when
- AbsForms :: [erl_parse:abstract_form()],
+ AbsForms :: [erl_parse:abstract_form() | erl_parse:form_info()],
Warnings :: [{file:filename(),[ErrorInfo]}],
Errors :: [{FileName2 :: file:filename(),[ErrorInfo]}],
ErrorInfo :: error_info()).
@@ -461,7 +507,7 @@ module(Forms) ->
-spec(module(AbsForms, FileName) ->
{ok, Warnings} | {error, Errors, Warnings} when
- AbsForms :: [erl_parse:abstract_form()],
+ AbsForms :: [erl_parse:abstract_form() | erl_parse:form_info()],
FileName :: atom() | string(),
Warnings :: [{file:filename(),[ErrorInfo]}],
Errors :: [{FileName2 :: file:filename(),[ErrorInfo]}],
@@ -474,7 +520,7 @@ module(Forms, FileName) ->
-spec(module(AbsForms, FileName, CompileOptions) ->
{ok, Warnings} | {error, Errors, Warnings} when
- AbsForms :: [erl_parse:abstract_form()],
+ AbsForms :: [erl_parse:abstract_form() | erl_parse:form_info()],
FileName :: atom() | string(),
CompileOptions :: [compile:option()],
Warnings :: [{file:filename(),[ErrorInfo]}],
@@ -504,7 +550,7 @@ start(File, Opts) ->
true, Opts)},
{export_all,
bool_option(warn_export_all, nowarn_export_all,
- false, Opts)},
+ true, Opts)},
{export_vars,
bool_option(warn_export_vars, nowarn_export_vars,
false, Opts)},
@@ -540,7 +586,10 @@ start(File, Opts) ->
false, Opts)},
{missing_spec_all,
bool_option(warn_missing_spec_all, nowarn_missing_spec_all,
- false, Opts)}
+ false, Opts)},
+ {get_stacktrace,
+ bool_option(warn_get_stacktrace, nowarn_get_stacktrace,
+ true, Opts)}
],
Enabled1 = [Category || {Category,true} <- Enabled0],
Enabled = ordsets:from_list(Enabled1),
@@ -560,6 +609,7 @@ start(File, Opts) ->
warn_format = value_option(warn_format, 1, warn_format, 1,
nowarn_format, 0, Opts),
enabled_warnings = Enabled,
+ nowarn_bif_clash = nowarn_function(nowarn_bif_clash, Opts),
file = File
}.
@@ -599,34 +649,39 @@ pack_warnings(Ws) ->
%% add_warning(ErrorDescriptor, State) -> State'
%% add_warning(Line, Error, State) -> State'
-add_error(E, St) -> St#lint{errors=[{St#lint.file,E}|St#lint.errors]}.
+add_error(E, St) -> add_lint_error(E, St#lint.file, St).
+
+add_error(Anno, E, St) ->
+ {File,Location} = loc(Anno, St),
+ add_lint_error({Location,erl_lint,E}, File, St).
-add_error(FileLine, E, St) ->
- {File,Location} = loc(FileLine),
- add_error({Location,erl_lint,E}, St#lint{file = File}).
+add_lint_error(E, File, St) ->
+ St#lint{errors=[{File,E}|St#lint.errors]}.
-add_warning(W, St) -> St#lint{warnings=[{St#lint.file,W}|St#lint.warnings]}.
+add_warning(W, St) -> add_lint_warning(W, St#lint.file, St).
add_warning(FileLine, W, St) ->
- {File,Location} = loc(FileLine),
- add_warning({Location,erl_lint,W}, St#lint{file = File}).
-
-loc(L) ->
- case erl_parse:get_attribute(L, location) of
- {location,{{File,Line},Column}} ->
- {File,{Line,Column}};
- {location,{File,Line}} ->
- {File,Line}
+ {File,Location} = loc(FileLine, St),
+ add_lint_warning({Location,erl_lint,W}, File, St).
+
+add_lint_warning(W, File, St) ->
+ St#lint{warnings=[{File,W}|St#lint.warnings]}.
+
+loc(Anno, St) ->
+ Location = erl_anno:location(Anno),
+ case erl_anno:file(Anno) of
+ undefined -> {St#lint.file,Location};
+ File -> {File,Location}
end.
%% forms([Form], State) -> State'
forms(Forms0, St0) ->
Forms = eval_file_attribute(Forms0, St0),
+ %% Annotations from now on include the 'file' item.
Locals = local_functions(Forms),
AutoImportSuppressed = auto_import_suppressed(St0#lint.compile),
StDeprecated = disallowed_compile_flags(Forms,St0),
- %% Line numbers are from now on pairs {File,Line}.
St1 = includes_qlc_hrl(Forms, StDeprecated#lint{locals = Locals,
no_auto = AutoImportSuppressed}),
St2 = bif_clashes(Forms, St1),
@@ -634,8 +689,6 @@ forms(Forms0, St0) ->
St4 = foldl(fun form/2, pre_scan(Forms, St3), Forms),
post_traversal_check(Forms, St4).
-pre_scan([{function,_L,new,_A,_Cs} | Fs], St) ->
- pre_scan(Fs, St#lint{new=true});
pre_scan([{attribute,L,compile,C} | Fs], St) ->
case is_warn_enabled(export_all, St) andalso
member(export_all, lists:flatten([C])) of
@@ -662,15 +715,29 @@ eval_file_attribute(Forms, St) ->
eval_file_attr([{attribute,_L,file,{File,_Line}}=Form | Forms], _File) ->
[Form | eval_file_attr(Forms, File)];
eval_file_attr([Form0 | Forms], File) ->
- Form = zip_file_and_line(Form0, File),
+ Form = set_form_file(Form0, File),
[Form | eval_file_attr(Forms, File)];
eval_file_attr([], _File) ->
[].
-zip_file_and_line(T, File) ->
- F0 = fun(Line) -> {File,Line} end,
- F = fun(L) -> erl_parse:set_line(L, F0) end,
- modify_line(T, F).
+%% Sets the file only on the form. This is used on post-traversal.
+%% For the remaining of the AST we rely on #lint.file.
+
+set_form_file({attribute,L,K,V}, File) ->
+ {attribute,erl_anno:set_file(File, L),K,V};
+set_form_file({function,L,N,A,C}, File) ->
+ {function,erl_anno:set_file(File, L),N,A,C};
+set_form_file(Form, _File) ->
+ Form.
+
+set_file(Ts, File) when is_list(Ts) ->
+ [anno_set_file(T, File) || T <- Ts];
+set_file(T, File) ->
+ anno_set_file(T, File).
+
+anno_set_file(T, File) ->
+ F = fun(Anno) -> erl_anno:set_file(File, Anno) end,
+ erl_parse:map_anno(F, T).
%% form(Form, State) -> State'
%% Check a form returning the updated State. Handle generic cases here.
@@ -693,17 +760,23 @@ form(Form, #lint{state=State}=St) ->
start_state({attribute,Line,module,{_,_}}=Form, St0) ->
St1 = add_error(Line, pmod_unsupported, St0),
attribute_state(Form, St1#lint{state=attribute});
-start_state({attribute,_,module,M}, St0) ->
+start_state({attribute,Line,module,M}, St0) ->
St1 = St0#lint{module=M},
- St1#lint{state=attribute};
+ St2 = St1#lint{state=attribute},
+ check_module_name(M, Line, St2);
start_state(Form, St) ->
- St1 = add_error(element(2, Form), undefined_module, St),
+ Anno = case Form of
+ {eof, L} -> erl_anno:new(L);
+ %% {warning, Warning} and {error, Error} not possible here.
+ _ -> element(2, Form)
+ end,
+ St1 = add_error(Anno, undefined_module, St),
attribute_state(Form, St1#lint{state=attribute}).
%% attribute_state(Form, State) ->
%% State'
-attribute_state({attribute,_L,module,_M}, #lint{module=[]}=St) ->
+attribute_state({attribute,_L,module,_M}, #lint{module=''}=St) ->
St;
attribute_state({attribute,L,module,_M}, St) ->
add_error(L, redefine_module, St);
@@ -727,6 +800,8 @@ attribute_state({attribute,L,spec,{Fun,Types}}, St) ->
spec_decl(L, Fun, Types, St);
attribute_state({attribute,L,callback,{Fun,Types}}, St) ->
callback_decl(L, Fun, Types, St);
+attribute_state({attribute,L,optional_callbacks,Es}, St) ->
+ optional_callbacks(L, Es, St);
attribute_state({attribute,L,on_load,Val}, St) ->
on_load(L, Val, St);
attribute_state({attribute,_L,_Other,_Val}, St) -> % Ignore others
@@ -738,6 +813,7 @@ attribute_state(Form, St) ->
%% State'
%% Allow for record, type and opaque type definitions and spec
%% declarations to be intersperced within function definitions.
+%% Dialyzer attributes are also allowed everywhere.
function_state({attribute,L,record,{Name,Fields}}, St) ->
record_def(L, Name, Fields, St);
@@ -747,12 +823,12 @@ function_state({attribute,L,opaque,{TypeName,TypeDef,Args}}, St) ->
type_def(opaque, L, TypeName, TypeDef, Args, St);
function_state({attribute,L,spec,{Fun,Types}}, St) ->
spec_decl(L, Fun, Types, St);
+function_state({attribute,_L,dialyzer,_Val}, St) ->
+ St;
function_state({attribute,La,Attr,_Val}, St) ->
add_error(La, {attribute,Attr}, St);
function_state({function,L,N,A,Cs}, St) ->
function(L, N, A, Cs, St);
-function_state({rule,L,_N,_A,_Cs}, St) ->
- add_error(L, {mnemosyne,"rule"}, St);
function_state({eof,L}, St) -> eof(L, St).
%% eof(LastLine, State) ->
@@ -763,8 +839,7 @@ eof(_Line, St0) ->
%% bif_clashes(Forms, State0) -> State.
-bif_clashes(Forms, St) ->
- Nowarn = nowarn_function(nowarn_bif_clash, St#lint.compile),
+bif_clashes(Forms, #lint{nowarn_bif_clash=Nowarn} = St) ->
Clashes0 = [{Name,Arity} || {function,_L,Name,Arity,_Cs} <- Forms,
erl_internal:bif(Name, Arity)],
Clashes = ordsets:subtract(ordsets:from_list(Clashes0), Nowarn),
@@ -779,18 +854,21 @@ not_deprecated(Forms, St0) ->
{nowarn_deprecated_function, MFAs0} <- lists:flatten([Args]),
MFA <- lists:flatten([MFAs0])],
Nowarn = [MFA || {MFA,_L} <- MFAsL],
- Bad = [MFAL || {{M,F,A},_L}=MFAL <- MFAsL,
- otp_internal:obsolete(M, F, A) =:= no],
- St1 = func_line_warning(bad_nowarn_deprecated_function, Bad, St0),
+ ML = [{M,L} || {{M,_F,_A},L} <- MFAsL, is_atom(M)],
+ St1 = foldl(fun ({M,L}, St2) ->
+ check_module_name(M, L, St2)
+ end, St0, ML),
St1#lint{not_deprecated = ordsets:from_list(Nowarn)}.
%% The nowarn_bif_clash directive is not only deprecated, it's actually an error from R14A
disallowed_compile_flags(Forms, St0) ->
%% There are (still) no line numbers in St0#lint.compile.
Errors0 = [ {St0#lint.file,{L,erl_lint,disallowed_nowarn_bif_clash}} ||
- {attribute,[{line,{_,L}}],compile,nowarn_bif_clash} <- Forms ],
+ {attribute,A,compile,nowarn_bif_clash} <- Forms,
+ {_,L} <- [loc(A, St0)] ],
Errors1 = [ {St0#lint.file,{L,erl_lint,disallowed_nowarn_bif_clash}} ||
- {attribute,[{line,{_,L}}],compile,{nowarn_bif_clash, {_,_}}} <- Forms ],
+ {attribute,A,compile,{nowarn_bif_clash, {_,_}}} <- Forms,
+ {_,L} <- [loc(A, St0)] ],
Disabled = (not is_warn_enabled(bif_clash, St0)),
Errors = if
Disabled andalso Errors0 =:= [] ->
@@ -822,7 +900,8 @@ post_traversal_check(Forms, St0) ->
StD = check_on_load(StC),
StE = check_unused_records(Forms, StD),
StF = check_local_opaque_types(StE),
- check_callback_information(StF).
+ StG = check_dialyzer_attribute(Forms, StF),
+ check_callback_information(StG).
%% check_behaviour(State0) -> State
%% Check that the behaviour attribute is valid.
@@ -834,57 +913,74 @@ check_behaviour(St0) ->
%% Check behaviours for existence and defined functions.
behaviour_check(Bs, St0) ->
- {AllBfs,St1} = all_behaviour_callbacks(Bs, [], St0),
- St = behaviour_missing_callbacks(AllBfs, St1),
+ {AllBfs0, St1} = all_behaviour_callbacks(Bs, [], St0),
+ St = behaviour_missing_callbacks(AllBfs0, St1),
+ Exports = exports(St0),
+ F = fun(Bfs, OBfs) ->
+ [B || B <- Bfs,
+ not lists:member(B, OBfs)
+ orelse gb_sets:is_member(B, Exports)]
+ end,
+ %% After fixing missing callbacks new warnings may be emitted.
+ AllBfs = [{Item,F(Bfs0, OBfs0)} || {Item,Bfs0,OBfs0} <- AllBfs0],
behaviour_conflicting(AllBfs, St).
all_behaviour_callbacks([{Line,B}|Bs], Acc, St0) ->
- {Bfs0,St} = behaviour_callbacks(Line, B, St0),
- all_behaviour_callbacks(Bs, [{{Line,B},Bfs0}|Acc], St);
+ {Bfs0,OBfs0,St} = behaviour_callbacks(Line, B, St0),
+ all_behaviour_callbacks(Bs, [{{Line,B},Bfs0,OBfs0}|Acc], St);
all_behaviour_callbacks([], Acc, St) -> {reverse(Acc),St}.
behaviour_callbacks(Line, B, St0) ->
try B:behaviour_info(callbacks) of
- Funcs when is_list(Funcs) ->
- All = all(fun({FuncName, Arity}) ->
- is_atom(FuncName) andalso is_integer(Arity);
- ({FuncName, Arity, Spec}) ->
- is_atom(FuncName) andalso is_integer(Arity)
- andalso is_list(Spec);
- (_Other) ->
- false
- end,
- Funcs),
- MaybeRemoveSpec = fun({_F,_A}=FA) -> FA;
- ({F,A,_S}) -> {F,A};
- (Other) -> Other
- end,
- if
- All =:= true ->
- {[MaybeRemoveSpec(F) || F <- Funcs], St0};
+ undefined ->
+ St1 = add_warning(Line, {undefined_behaviour_callbacks, B}, St0),
+ {[], [], St1};
+ Funcs ->
+ case is_fa_list(Funcs) of
true ->
+ try B:behaviour_info(optional_callbacks) of
+ undefined ->
+ {Funcs, [], St0};
+ OptFuncs ->
+ %% OptFuncs should always be OK thanks to
+ %% sys_pre_expand.
+ case is_fa_list(OptFuncs) of
+ true ->
+ {Funcs, OptFuncs, St0};
+ false ->
+ W = {ill_defined_optional_callbacks, B},
+ St1 = add_warning(Line, W, St0),
+ {Funcs, [], St1}
+ end
+ catch
+ _:_ ->
+ {Funcs, [], St0}
+ end;
+ false ->
St1 = add_warning(Line,
- {ill_defined_behaviour_callbacks,B},
+ {ill_defined_behaviour_callbacks, B},
St0),
- {[], St1}
- end;
- undefined ->
- St1 = add_warning(Line, {undefined_behaviour_callbacks,B}, St0),
- {[], St1};
- _Other ->
- St1 = add_warning(Line, {ill_defined_behaviour_callbacks,B}, St0),
- {[], St1}
+ {[], [], St1}
+ end
catch
_:_ ->
- St1 = add_warning(Line, {undefined_behaviour,B}, St0),
- {[], St1}
+ St1 = add_warning(Line, {undefined_behaviour, B}, St0),
+ St2 = check_module_name(B, Line, St1),
+ {[], [], St2}
end.
-behaviour_missing_callbacks([{{Line,B},Bfs}|T], St0) ->
+behaviour_missing_callbacks([{{Line,B},Bfs0,OBfs}|T], St0) ->
+ Bfs = ordsets:subtract(ordsets:from_list(Bfs0), ordsets:from_list(OBfs)),
Exports = gb_sets:to_list(exports(St0)),
- Missing = ordsets:subtract(ordsets:from_list(Bfs), Exports),
+ Missing = ordsets:subtract(Bfs, Exports),
St = foldl(fun (F, S0) ->
- add_warning(Line, {undefined_behaviour_func,F,B}, S0)
+ case is_fa(F) of
+ true ->
+ M = {undefined_behaviour_func,F,B},
+ add_warning(Line, M, S0);
+ false ->
+ S0 % ill_defined_behaviour_callbacks
+ end
end, St0, Missing),
behaviour_missing_callbacks(T, St);
behaviour_missing_callbacks([], St) -> St.
@@ -899,7 +995,7 @@ behaviour_conflicting(AllBfs, St) ->
behaviour_add_conflicts(R, St).
behaviour_add_conflicts([{Cb,[{FirstLoc,FirstB}|Cs]}|T], St0) ->
- FirstL = element(2, loc(FirstLoc)),
+ FirstL = element(2, loc(FirstLoc, St0)),
St = behaviour_add_conflict(Cs, Cb, FirstL, FirstB, St0),
behaviour_add_conflicts(T, St);
behaviour_add_conflicts([], St) -> St.
@@ -1089,7 +1185,7 @@ check_untyped_records(Forms, St0) ->
RecNames = dict:fetch_keys(St0#lint.records),
%% these are the records with field(s) containing type info
TRecNames = [Name ||
- {attribute,_,type,{{record,Name},Fields,_}} <- Forms,
+ {attribute,_,record,{Name,Fields}} <- Forms,
lists:all(fun ({typed_record_field,_,_}) -> true;
(_) -> false
end, Fields)],
@@ -1099,7 +1195,8 @@ check_untyped_records(Forms, St0) ->
[] -> St; % exclude records with no fields
[_|_] -> add_warning(L, {untyped_record, N}, St)
end
- end, St0, RecNames -- TRecNames);
+ end, St0, ordsets:subtract(ordsets:from_list(RecNames),
+ ordsets:from_list(TRecNames)));
false ->
St0
end.
@@ -1117,7 +1214,7 @@ check_unused_records(Forms, St0) ->
end, St0#lint.records, UsedRecords),
Unused = [{Name,FileLine} ||
{Name,{FileLine,_Fields}} <- dict:to_list(URecs),
- element(1, loc(FileLine)) =:= FirstFile],
+ element(1, loc(FileLine, St0)) =:= FirstFile],
foldl(fun ({N,L}, St) ->
add_warning(L, {unused_record, N}, St)
end, St0, Unused);
@@ -1126,19 +1223,29 @@ check_unused_records(Forms, St0) ->
end.
check_callback_information(#lint{callbacks = Callbacks,
- defined = Defined} = State) ->
- case gb_sets:is_member({behaviour_info,1}, Defined) of
- false -> State;
+ optional_callbacks = OptionalCbs,
+ defined = Defined} = St0) ->
+ OptFun = fun({MFA, Line}, St) ->
+ case dict:is_key(MFA, Callbacks) of
+ true ->
+ St;
+ false ->
+ add_error(Line, {undefined_callback, MFA}, St)
+ end
+ end,
+ St1 = lists:foldl(OptFun, St0, dict:to_list(OptionalCbs)),
+ case gb_sets:is_member({behaviour_info, 1}, Defined) of
+ false -> St1;
true ->
case dict:size(Callbacks) of
- 0 -> State;
+ 0 -> St1;
_ ->
CallbacksList = dict:to_list(Callbacks),
FoldL =
- fun({Fa,Line},St) ->
+ fun({Fa, Line}, St) ->
add_error(Line, {behaviour_info, Fa}, St)
end,
- lists:foldl(FoldL, State, CallbacksList)
+ lists:foldl(FoldL, St1, CallbacksList)
end
end.
@@ -1197,7 +1304,8 @@ exports(#lint{compile = Opts, defined = Defs, exports = Es}) ->
-type import() :: {module(), [fa()]} | module().
-spec import(line(), import(), lint_state()) -> lint_state().
-import(Line, {Mod,Fs}, St) ->
+import(Line, {Mod,Fs}, St00) ->
+ St = check_module_name(Mod, Line, St00),
Mfs = ordsets:from_list(Fs),
case check_imports(Line, Mfs, St#lint.imports) of
[] ->
@@ -1265,7 +1373,7 @@ imported(F, A, St) ->
error -> no
end.
--spec on_load(line(), fa(), lint_state()) -> lint_state().
+-spec on_load(erl_anno:anno(), fa(), lint_state()) -> lint_state().
%% Check an on_load directive and remember it.
on_load(Line, {Name,Arity}=Fa, #lint{on_load=OnLoad0}=St0)
@@ -1300,20 +1408,22 @@ check_on_load(St) -> St.
-spec call_function(line(), atom(), arity(), lint_state()) -> lint_state().
%% Add to both called and calls.
-call_function(Line, F, A, #lint{usage=Usage0,called=Cd,func=Func}=St) ->
+call_function(Line, F, A, #lint{usage=Usage0,called=Cd,func=Func,file=File}=St) ->
#usage{calls = Cs} = Usage0,
NA = {F,A},
Usage = case Cs of
undefined -> Usage0;
_ -> Usage0#usage{calls=dict:append(Func, NA, Cs)}
end,
- St#lint{called=[{NA,Line}|Cd], usage=Usage}.
+ Anno = erl_anno:set_file(File, Line),
+ St#lint{called=[{NA,Anno}|Cd], usage=Usage}.
%% function(Line, Name, Arity, Clauses, State) -> State.
function(Line, Name, Arity, Cs, St0) ->
- St1 = define_function(Line, Name, Arity, St0#lint{func={Name,Arity}}),
- clauses(Cs, St1).
+ St1 = St0#lint{func={Name,Arity},catch_scope=none},
+ St2 = define_function(Line, Name, Arity, St1),
+ clauses(Cs, St2).
-spec define_function(line(), atom(), arity(), lint_state()) -> lint_state().
@@ -1403,20 +1513,7 @@ pattern({cons,_Line,H,T}, Vt, Old, Bvt, St0) ->
pattern({tuple,_Line,Ps}, Vt, Old, Bvt, St) ->
pattern_list(Ps, Vt, Old, Bvt, St);
pattern({map,_Line,Ps}, Vt, Old, Bvt, St) ->
- foldl(fun
- ({map_field_assoc,L,_,_}, {Psvt,Bvt0,St0}) ->
- {Psvt,Bvt0,add_error(L, illegal_pattern, St0)};
- ({map_field_exact,L,KP,VP}, {Psvt,Bvt0,St0}) ->
- case is_valid_map_key(KP, pattern, St0) of
- true ->
- {Pvt,Bvt1,St1} = pattern(VP, Vt, Old, Bvt, St0),
- {vtmerge_pat(Pvt, Psvt),vtmerge_pat(Bvt0, Bvt1), St1};
- false ->
- {Psvt,Bvt0,add_error(L, illegal_map_key, St0)};
- {false,variable,Var} ->
- {Psvt,Bvt0,add_error(L, {illegal_map_key_variable,Var}, St0)}
- end
- end, {[],[],St}, Ps);
+ pattern_map(Ps, Vt, Old, Bvt, St);
%%pattern({struct,_Line,_Tag,Ps}, Vt, Old, Bvt, St) ->
%% pattern_list(Ps, Vt, Old, Bvt, St);
pattern({record_index,Line,Name,Field}, _Vt, _Old, _Bvt, St) ->
@@ -1446,7 +1543,7 @@ pattern({op,_Line,'++',{string,_Li,_S},R}, Vt, Old, Bvt, St) ->
pattern({match,_Line,Pat1,Pat2}, Vt, Old, Bvt, St0) ->
{Lvt,Bvt1,St1} = pattern(Pat1, Vt, Old, Bvt, St0),
{Rvt,Bvt2,St2} = pattern(Pat2, Vt, Old, Bvt, St1),
- St3 = reject_bin_alias(Pat1, Pat2, St2),
+ St3 = reject_invalid_alias(Pat1, Pat2, Vt, St2),
{vtmerge_pat(Lvt, Rvt),vtmerge_pat(Bvt1,Bvt2),St3};
%% Catch legal constant expressions, including unary +,-.
pattern(Pat, _Vt, _Old, _Bvt, St) ->
@@ -1461,56 +1558,77 @@ pattern_list(Ps, Vt, Old, Bvt0, St) ->
{vtmerge_pat(Pvt, Psvt),vtmerge_pat(Bvt,Bvt1),St1}
end, {[],[],St}, Ps).
-%% reject_bin_alias(Pat, Expr, St) -> St'
+
+
+%% reject_invalid_alias(Pat, Expr, Vt, St) -> St'
%% Reject aliases for binary patterns at the top level.
+%% Reject aliases for maps patterns at the top level.
+%% The variables table (Vt) are for maps checkking.
+
+reject_invalid_alias_expr({bin,_,_}=P, {match,_,P0,E}, Vt, St0) ->
+ St = reject_invalid_alias(P, P0, Vt, St0),
+ reject_invalid_alias_expr(P, E, Vt, St);
+reject_invalid_alias_expr({map,_,_}=P, {match,_,P0,E}, Vt, St0) ->
+ St = reject_invalid_alias(P, P0, Vt, St0),
+ reject_invalid_alias_expr(P, E, Vt, St);
+reject_invalid_alias_expr({match,_,_,_}=P, {match,_,P0,E}, Vt, St0) ->
+ St = reject_invalid_alias(P, P0, Vt, St0),
+ reject_invalid_alias_expr(P, E, Vt, St);
+reject_invalid_alias_expr(_, _, _, St) -> St.
-reject_bin_alias_expr({bin,_,_}=P, {match,_,P0,E}, St0) ->
- St = reject_bin_alias(P, P0, St0),
- reject_bin_alias_expr(P, E, St);
-reject_bin_alias_expr({match,_,_,_}=P, {match,_,P0,E}, St0) ->
- St = reject_bin_alias(P, P0, St0),
- reject_bin_alias_expr(P, E, St);
-reject_bin_alias_expr(_, _, St) -> St.
-%% reject_bin_alias(Pat1, Pat2, St) -> St'
+%% reject_invalid_alias(Pat1, Pat2, St) -> St'
%% Aliases of binary patterns, such as <<A:8>> = <<B:4,C:4>> or even
%% <<A:8>> = <<A:8>>, are not allowed. Traverse the patterns in parallel
%% and generate an error if any binary aliases are found.
%% We generate an error even if is obvious that the overall pattern can't
%% possibly match, for instance, {a,<<A:8>>,c}={x,<<A:8>>} WILL generate an
%% error.
+%% Maps should reject unbound variables here.
-reject_bin_alias({bin,Line,_}, {bin,_,_}, St) ->
+reject_invalid_alias({bin,Line,_}, {bin,_,_}, _, St) ->
add_error(Line, illegal_bin_pattern, St);
-reject_bin_alias({cons,_,H1,T1}, {cons,_,H2,T2}, St0) ->
- St = reject_bin_alias(H1, H2, St0),
- reject_bin_alias(T1, T2, St);
-reject_bin_alias({tuple,_,Es1}, {tuple,_,Es2}, St) ->
- reject_bin_alias_list(Es1, Es2, St);
-reject_bin_alias({record,_,Name1,Pfs1}, {record,_,Name2,Pfs2},
+reject_invalid_alias({map,_Line,Ps1}, {map,_,Ps2}, Vt, St0) ->
+ Fun = fun ({map_field_exact,L,{var,_,K},_V}, Sti) ->
+ case is_var_bound(K,Vt) of
+ true ->
+ Sti;
+ false ->
+ add_error(L, {unbound_var,K}, Sti)
+ end;
+ ({map_field_exact,_L,_K,_V}, Sti) ->
+ Sti
+ end,
+ foldl(Fun, foldl(Fun, St0, Ps1), Ps2);
+reject_invalid_alias({cons,_,H1,T1}, {cons,_,H2,T2}, Vt, St0) ->
+ St = reject_invalid_alias(H1, H2, Vt, St0),
+ reject_invalid_alias(T1, T2, Vt, St);
+reject_invalid_alias({tuple,_,Es1}, {tuple,_,Es2}, Vt, St) ->
+ reject_invalid_alias_list(Es1, Es2, Vt, St);
+reject_invalid_alias({record,_,Name1,Pfs1}, {record,_,Name2,Pfs2}, Vt,
#lint{records=Recs}=St) ->
case {dict:find(Name1, Recs),dict:find(Name2, Recs)} of
{{ok,{_Line1,Fields1}},{ok,{_Line2,Fields2}}} ->
- reject_bin_alias_rec(Pfs1, Pfs2, Fields1, Fields2, St);
+ reject_invalid_alias_rec(Pfs1, Pfs2, Fields1, Fields2, Vt, St);
{_,_} ->
%% One or more non-existing records. (An error messages has
%% already been generated, so we are done here.)
St
end;
-reject_bin_alias({match,_,P1,P2}, P, St0) ->
- St = reject_bin_alias(P1, P, St0),
- reject_bin_alias(P2, P, St);
-reject_bin_alias(P, {match,_,_,_}=M, St) ->
- reject_bin_alias(M, P, St);
-reject_bin_alias(_P1, _P2, St) -> St.
-
-reject_bin_alias_list([E1|Es1], [E2|Es2], St0) ->
- St = reject_bin_alias(E1, E2, St0),
- reject_bin_alias_list(Es1, Es2, St);
-reject_bin_alias_list(_, _, St) -> St.
-
-reject_bin_alias_rec(PfsA0, PfsB0, FieldsA0, FieldsB0, St) ->
+reject_invalid_alias({match,_,P1,P2}, P, Vt, St0) ->
+ St = reject_invalid_alias(P1, P, Vt, St0),
+ reject_invalid_alias(P2, P, Vt, St);
+reject_invalid_alias(P, {match,_,_,_}=M, Vt, St) ->
+ reject_invalid_alias(M, P, Vt, St);
+reject_invalid_alias(_P1, _P2, _Vt, St) -> St.
+
+reject_invalid_alias_list([E1|Es1], [E2|Es2], Vt, St0) ->
+ St = reject_invalid_alias(E1, E2, Vt, St0),
+ reject_invalid_alias_list(Es1, Es2, Vt, St);
+reject_invalid_alias_list(_, _, _, St) -> St.
+
+reject_invalid_alias_rec(PfsA0, PfsB0, FieldsA0, FieldsB0, Vt, St) ->
%% We treat records as if they have been converted to tuples.
PfsA1 = rbia_field_vars(PfsA0),
PfsB1 = rbia_field_vars(PfsB0),
@@ -1526,7 +1644,7 @@ reject_bin_alias_rec(PfsA0, PfsB0, FieldsA0, FieldsB0, St) ->
D = sofs:projection({external,fun({_,_,P1,_,P2}) -> {P1,P2} end}, C),
E = sofs:to_external(D),
{Ps1,Ps2} = lists:unzip(E),
- reject_bin_alias_list(Ps1, Ps2, St).
+ reject_invalid_alias_list(Ps1, Ps2, Vt, St).
rbia_field_vars(Fs) ->
[{Name,Pat} || {record_field,_,{atom,_,Name},Pat} <- Fs].
@@ -1570,6 +1688,21 @@ is_pattern_expr_1({op,_Line,Op,A1,A2}) ->
erl_internal:arith_op(Op, 2) andalso all(fun is_pattern_expr/1, [A1,A2]);
is_pattern_expr_1(_Other) -> false.
+pattern_map(Ps, Vt, Old, Bvt, St) ->
+ foldl(fun
+ ({map_field_assoc,L,_,_}, {Psvt,Bvt0,St0}) ->
+ {Psvt,Bvt0,add_error(L, illegal_pattern, St0)};
+ ({map_field_exact,L,K,V}, {Psvt,Bvt0,St0}) ->
+ case is_valid_map_key(K) of
+ true ->
+ {Kvt,St1} = expr(K, Vt, St0),
+ {Vvt,Bvt2,St2} = pattern(V, Vt, Old, Bvt, St1),
+ {vtmerge_pat(vtmerge_pat(Kvt, Vvt), Psvt), vtmerge_pat(Bvt0, Bvt2), St2};
+ false ->
+ {Psvt,Bvt0,add_error(L, illegal_map_key, St0)}
+ end
+ end, {[],[],St}, Ps).
+
%% pattern_bin([Element], VarTable, Old, BinVarTable, State) ->
%% {UpdVarTable,UpdBinVarTable,State}.
%% Check a pattern group. BinVarTable are used binsize variables.
@@ -1673,7 +1806,8 @@ bit_size({atom,_Line,all}, _Vt, St, _Check) -> {all,[],St};
bit_size(Size, Vt, St, Check) ->
%% Try to safely evaluate Size if constant to get size,
%% otherwise just treat it as an expression.
- case is_gexpr(Size, St#lint.records) of
+ Info = is_guard_test2_info(St),
+ case is_gexpr(Size, Info) of
true ->
case erl_eval:partial_eval(Size) of
{integer,_ILn,I} -> {I,[],St};
@@ -1908,77 +2042,104 @@ gexpr_list(Es, Vt, St) ->
%% is_guard_test(Expression) -> boolean().
%% Test if a general expression is a guard test.
+%%
+%% Note: Only use this function in contexts where there can be
+%% no definition of a local function that may override a guard BIF
+%% (for example, in the shell).
-spec is_guard_test(Expr) -> boolean() when
Expr :: erl_parse:abstract_expr().
is_guard_test(E) ->
- is_guard_test2(E, dict:new()).
+ is_guard_test2(E, {dict:new(),fun(_) -> false end}).
%% is_guard_test(Expression, Forms) -> boolean().
is_guard_test(Expression, Forms) ->
+ is_guard_test(Expression, Forms, fun(_) -> false end).
+
+
+%% is_guard_test(Expression, Forms, IsOverridden) -> boolean().
+%% Test if a general expression is a guard test.
+%%
+%% IsOverridden({Name,Arity}) should return 'true' if Name/Arity is
+%% a local or imported function in the module. If the abstract code has
+%% passed through erl_expand_records, any call without an explicit
+%% module is to a local function, so IsOverridden can be defined as:
+%%
+%% fun(_) -> true end
+%%
+-spec is_guard_test(Expr, Forms, IsOverridden) -> boolean() when
+ Expr :: erl_parse:abstract_expr(),
+ Forms :: [erl_parse:abstract_form() | erl_parse:form_info()],
+ IsOverridden :: fun((fa()) -> boolean()).
+
+is_guard_test(Expression, Forms, IsOverridden) ->
RecordAttributes = [A || A = {attribute, _, record, _D} <- Forms],
St0 = foldl(fun(Attr0, St1) ->
- Attr = zip_file_and_line(Attr0, "none"),
+ Attr = set_file(Attr0, "none"),
attribute_state(Attr, St1)
end, start(), RecordAttributes),
- is_guard_test2(zip_file_and_line(Expression, "nofile"), St0#lint.records).
+ is_guard_test2(set_file(Expression, "nofile"),
+ {St0#lint.records,IsOverridden}).
%% is_guard_test2(Expression, RecordDefs :: dict:dict()) -> boolean().
-is_guard_test2({call,Line,{atom,Lr,record},[E,A]}, RDs) ->
- is_gexpr({call,Line,{atom,Lr,is_record},[E,A]}, RDs);
-is_guard_test2({call,_Line,{atom,_La,Test},As}=Call, RDs) ->
- case erl_internal:type_test(Test, length(As)) of
- true -> is_gexpr_list(As, RDs);
- false -> is_gexpr(Call, RDs)
- end;
-is_guard_test2(G, RDs) ->
+is_guard_test2({call,Line,{atom,Lr,record},[E,A]}, Info) ->
+ is_gexpr({call,Line,{atom,Lr,is_record},[E,A]}, Info);
+is_guard_test2({call,_Line,{atom,_La,Test},As}=Call, {_,IsOverridden}=Info) ->
+ A = length(As),
+ not IsOverridden({Test,A}) andalso
+ case erl_internal:type_test(Test, A) of
+ true -> is_gexpr_list(As, Info);
+ false -> is_gexpr(Call, Info)
+ end;
+is_guard_test2(G, Info) ->
%%Everything else is a guard expression.
- is_gexpr(G, RDs).
+ is_gexpr(G, Info).
%% is_guard_expr(Expression) -> boolean().
%% Test if an expression is a guard expression.
is_guard_expr(E) -> is_gexpr(E, []).
-is_gexpr({var,_L,_V}, _RDs) -> true;
-is_gexpr({char,_L,_C}, _RDs) -> true;
-is_gexpr({integer,_L,_I}, _RDs) -> true;
-is_gexpr({float,_L,_F}, _RDs) -> true;
-is_gexpr({atom,_L,_A}, _RDs) -> true;
-is_gexpr({string,_L,_S}, _RDs) -> true;
-is_gexpr({nil,_L}, _RDs) -> true;
-is_gexpr({cons,_L,H,T}, RDs) -> is_gexpr_list([H,T], RDs);
-is_gexpr({tuple,_L,Es}, RDs) -> is_gexpr_list(Es, RDs);
-%%is_gexpr({struct,_L,_Tag,Es}, RDs) ->
-%% is_gexpr_list(Es, RDs);
-is_gexpr({record_index,_L,_Name,Field}, RDs) ->
- is_gexpr(Field, RDs);
-is_gexpr({record_field,_L,Rec,_Name,Field}, RDs) ->
- is_gexpr_list([Rec,Field], RDs);
-is_gexpr({record,L,Name,Inits}, RDs) ->
- is_gexpr_fields(Inits, L, Name, RDs);
-is_gexpr({bin,_L,Fs}, RDs) ->
+is_gexpr({var,_L,_V}, _Info) -> true;
+is_gexpr({char,_L,_C}, _Info) -> true;
+is_gexpr({integer,_L,_I}, _Info) -> true;
+is_gexpr({float,_L,_F}, _Info) -> true;
+is_gexpr({atom,_L,_A}, _Info) -> true;
+is_gexpr({string,_L,_S}, _Info) -> true;
+is_gexpr({nil,_L}, _Info) -> true;
+is_gexpr({cons,_L,H,T}, Info) -> is_gexpr_list([H,T], Info);
+is_gexpr({tuple,_L,Es}, Info) -> is_gexpr_list(Es, Info);
+%%is_gexpr({struct,_L,_Tag,Es}, Info) ->
+%% is_gexpr_list(Es, Info);
+is_gexpr({record_index,_L,_Name,Field}, Info) ->
+ is_gexpr(Field, Info);
+is_gexpr({record_field,_L,Rec,_Name,Field}, Info) ->
+ is_gexpr_list([Rec,Field], Info);
+is_gexpr({record,L,Name,Inits}, Info) ->
+ is_gexpr_fields(Inits, L, Name, Info);
+is_gexpr({bin,_L,Fs}, Info) ->
all(fun ({bin_element,_Line,E,Sz,_Ts}) ->
- is_gexpr(E, RDs) and (Sz =:= default orelse is_gexpr(Sz, RDs))
+ is_gexpr(E, Info) and (Sz =:= default orelse is_gexpr(Sz, Info))
end, Fs);
-is_gexpr({call,_L,{atom,_Lf,F},As}, RDs) ->
+is_gexpr({call,_L,{atom,_Lf,F},As}, {_,IsOverridden}=Info) ->
A = length(As),
- erl_internal:guard_bif(F, A) andalso is_gexpr_list(As, RDs);
-is_gexpr({call,_L,{remote,_Lr,{atom,_Lm,erlang},{atom,_Lf,F}},As}, RDs) ->
+ not IsOverridden({F,A}) andalso erl_internal:guard_bif(F, A)
+ andalso is_gexpr_list(As, Info);
+is_gexpr({call,_L,{remote,_Lr,{atom,_Lm,erlang},{atom,_Lf,F}},As}, Info) ->
A = length(As),
(erl_internal:guard_bif(F, A) orelse is_gexpr_op(F, A))
- andalso is_gexpr_list(As, RDs);
-is_gexpr({call,L,{tuple,Lt,[{atom,Lm,erlang},{atom,Lf,F}]},As}, RDs) ->
- is_gexpr({call,L,{remote,Lt,{atom,Lm,erlang},{atom,Lf,F}},As}, RDs);
-is_gexpr({op,_L,Op,A}, RDs) ->
- is_gexpr_op(Op, 1) andalso is_gexpr(A, RDs);
-is_gexpr({op,_L,'andalso',A1,A2}, RDs) ->
- is_gexpr_list([A1,A2], RDs);
-is_gexpr({op,_L,'orelse',A1,A2}, RDs) ->
- is_gexpr_list([A1,A2], RDs);
-is_gexpr({op,_L,Op,A1,A2}, RDs) ->
- is_gexpr_op(Op, 2) andalso is_gexpr_list([A1,A2], RDs);
-is_gexpr(_Other, _RDs) -> false.
+ andalso is_gexpr_list(As, Info);
+is_gexpr({call,L,{tuple,Lt,[{atom,Lm,erlang},{atom,Lf,F}]},As}, Info) ->
+ is_gexpr({call,L,{remote,Lt,{atom,Lm,erlang},{atom,Lf,F}},As}, Info);
+is_gexpr({op,_L,Op,A}, Info) ->
+ is_gexpr_op(Op, 1) andalso is_gexpr(A, Info);
+is_gexpr({op,_L,'andalso',A1,A2}, Info) ->
+ is_gexpr_list([A1,A2], Info);
+is_gexpr({op,_L,'orelse',A1,A2}, Info) ->
+ is_gexpr_list([A1,A2], Info);
+is_gexpr({op,_L,Op,A1,A2}, Info) ->
+ is_gexpr_op(Op, 2) andalso is_gexpr_list([A1,A2], Info);
+is_gexpr(_Other, _Info) -> false.
is_gexpr_op(Op, A) ->
try erl_internal:op_type(Op, A) of
@@ -1990,14 +2151,14 @@ is_gexpr_op(Op, A) ->
catch _:_ -> false
end.
-is_gexpr_list(Es, RDs) -> all(fun (E) -> is_gexpr(E, RDs) end, Es).
+is_gexpr_list(Es, Info) -> all(fun (E) -> is_gexpr(E, Info) end, Es).
-is_gexpr_fields(Fs, L, Name, RDs) ->
+is_gexpr_fields(Fs, L, Name, {RDs,_}=Info) ->
IFs = case dict:find(Name, RDs) of
{ok,{_Line,Fields}} -> Fs ++ init_fields(Fs, L, Fields);
error -> Fs
end,
- all(fun ({record_field,_Lf,_Name,V}) -> is_gexpr(V, RDs);
+ all(fun ({record_field,_Lf,_Name,V}) -> is_gexpr(V, Info);
(_Other) -> false end, IFs).
%% exprs(Sequence, VarTable, State) ->
@@ -2084,8 +2245,8 @@ expr({'receive',Line,Cs,To,ToEs}, Vt, St0) ->
{Cvt,St3} = icrt_clauses(Cs, Vt, St2),
%% Csvts = [vtnew(Tevt, Vt)|Cvt], %This is just NEW variables!
Csvts = [Tevt|Cvt],
- {Rvt,St4} = icrt_export(Csvts, Vt, {'receive',Line}, St3),
- {vtmerge([Tvt,Tevt,Rvt]),St4};
+ Rvt = icrt_export(Csvts, Vt, {'receive',Line}, St3),
+ {vtmerge([Tvt,Tevt,Rvt]),St3};
expr({'fun',Line,Body}, Vt, St) ->
%%No one can think funs export!
case Body of
@@ -2128,11 +2289,18 @@ expr({call,L,{tuple,Lt,[{atom,Lm,erlang},{atom,Lf,is_record}]},As}, Vt, St) ->
expr({call,Line,{remote,_Lr,{atom,_Lm,M},{atom,Lf,F}},As}, Vt, St0) ->
St1 = keyword_warning(Lf, F, St0),
St2 = check_remote_function(Line, M, F, As, St1),
- expr_list(As, Vt, St2);
+ St3 = check_module_name(M, Line, St2),
+ expr_list(As, Vt, St3);
expr({call,Line,{remote,_Lr,M,F},As}, Vt, St0) ->
St1 = keyword_warning(Line, M, St0),
St2 = keyword_warning(Line, F, St1),
- expr_list([M,F|As], Vt, St2);
+ St3 = case M of
+ {atom,Lm,Mod} ->
+ check_module_name(Mod, Lm, St2);
+ _ ->
+ St2
+ end,
+ expr_list([M,F|As], Vt, St3);
expr({call,Line,{atom,La,F},As}, Vt, St0) ->
St1 = keyword_warning(La, F, St0),
{Asvt,St2} = expr_list(As, Vt, St1),
@@ -2194,27 +2362,28 @@ expr({call,Line,F,As}, Vt, St0) ->
expr({'try',Line,Es,Scs,Ccs,As}, Vt, St0) ->
%% Currently, we don't allow any exports because later
%% passes cannot handle exports in combination with 'after'.
- {Evt0,St1} = exprs(Es, Vt, St0),
+ {Evt0,St1} = exprs(Es, Vt, St0#lint{catch_scope=wrong_part_of_try}),
TryLine = {'try',Line},
- Uvt = vtunsafe(vtnames(vtnew(Evt0, Vt)), TryLine, []),
- Evt1 = vtupdate(Uvt, vtsubtract(Evt0, Uvt)),
- {Sccs,St2} = icrt_clauses(Scs++Ccs, TryLine, vtupdate(Evt1, Vt), St1),
+ Uvt = vtunsafe(TryLine, Evt0, Vt),
+ Evt1 = vtupdate(Uvt, Evt0),
+ {Sccs,St2} = try_clauses(Scs, Ccs, TryLine,
+ vtupdate(Evt1, Vt), St1),
Rvt0 = Sccs,
- Rvt1 = vtupdate(vtunsafe(vtnames(vtnew(Rvt0, Vt)), TryLine, []), Rvt0),
+ Rvt1 = vtupdate(vtunsafe(TryLine, Rvt0, Vt), Rvt0),
Evt2 = vtmerge(Evt1, Rvt1),
{Avt0,St} = exprs(As, vtupdate(Evt2, Vt), St2),
- Avt1 = vtupdate(vtunsafe(vtnames(vtnew(Avt0, Vt)), TryLine, []), Avt0),
+ Avt1 = vtupdate(vtunsafe(TryLine, Avt0, Vt), Avt0),
Avt = vtmerge(Evt2, Avt1),
- {Avt,St};
+ {Avt,St#lint{catch_scope=after_try}};
expr({'catch',Line,E}, Vt, St0) ->
%% No new variables added, flag new variables as unsafe.
- {Evt,St1} = expr(E, Vt, St0),
- Uvt = vtunsafe(vtnames(vtnew(Evt, Vt)), {'catch',Line}, []),
- {vtupdate(Uvt,vtupdate(Evt, Vt)),St1};
+ {Evt,St} = expr(E, Vt, St0),
+ {vtupdate(vtunsafe({'catch',Line}, Evt, Vt), Evt),
+ St#lint{catch_scope=after_old_catch}};
expr({match,_Line,P,E}, Vt, St0) ->
{Evt,St1} = expr(E, Vt, St0),
{Pvt,Bvt,St2} = pattern(P, vtupdate(Evt, Vt), St1),
- St = reject_bin_alias_expr(P, E, St2),
+ St = reject_invalid_alias_expr(P, E, Vt, St2),
{vtupdate(Bvt, vtmerge(Evt, Pvt)),St};
%% No comparison or boolean operators yet.
expr({op,_Line,_Op,A}, Vt, St) ->
@@ -2223,9 +2392,8 @@ expr({op,Line,Op,L,R}, Vt, St0) when Op =:= 'orelse'; Op =:= 'andalso' ->
{Evt1,St1} = expr(L, Vt, St0),
Vt1 = vtupdate(Evt1, Vt),
{Evt2,St2} = expr(R, Vt1, St1),
- Vt2 = vtmerge(Evt2, Vt1),
- {Vt3,St3} = icrt_export([Vt1,Vt2], Vt1, {Op,Line}, St2),
- {vtmerge(Evt1, Vt3),St3};
+ Evt3 = vtupdate(vtunsafe({Op,Line}, Evt2, Vt1), Evt2),
+ {vtmerge(Evt1, Evt3),St2};
expr({op,_Line,_Op,L,R}, Vt, St) ->
expr_list([L,R], Vt, St); %They see the same variables
%% The following are not allowed to occur anywhere!
@@ -2236,11 +2404,10 @@ expr({remote,Line,_M,_F}, _Vt, St) ->
%% {UsedVarTable,State}
expr_list(Es, Vt, St) ->
- {Vt1,St1} = foldl(fun (E, {Esvt,St0}) ->
- {Evt,St1} = expr(E, Vt, St0),
- {vtmerge_pat(Evt, Esvt),St1}
- end, {[],St}, Es),
- {vtmerge(vtnew(Vt1, Vt), vtold(Vt1, Vt)),St1}.
+ foldl(fun (E, {Esvt,St0}) ->
+ {Evt,St1} = expr(E, Vt, St0),
+ {vtmerge_pat(Evt, Esvt),St1}
+ end, {[],St}, Es).
record_expr(Line, Rec, Vt, St0) ->
St1 = warn_invalid_record(Line, Rec, St0),
@@ -2253,18 +2420,13 @@ check_assoc_fields([{map_field_assoc,_,_,_}|Fs], St) ->
check_assoc_fields([], St) ->
St.
-map_fields([{Tag,Line,K,V}|Fs], Vt, St, F) when Tag =:= map_field_assoc;
- Tag =:= map_field_exact ->
- St1 = case is_valid_map_key(K, St) of
- true -> St;
- false -> add_error(Line, illegal_map_key, St);
- {false,variable,Var} -> add_error(Line, {illegal_map_key_variable,Var}, St)
- end,
- {Pvt,St2} = F([K,V], Vt, St1),
+map_fields([{Tag,_,K,V}|Fs], Vt, St, F) when Tag =:= map_field_assoc;
+ Tag =:= map_field_exact ->
+ {Pvt,St2} = F([K,V], Vt, St),
{Vts,St3} = map_fields(Fs, Vt, St2, F),
{vtupdate(Pvt, Vts),St3};
-map_fields([], Vt, St, _) ->
- {Vt,St}.
+map_fields([], _, St, _) ->
+ {[],St}.
%% warn_invalid_record(Line, Record, State0) -> State
%% Adds warning if the record is invalid.
@@ -2318,21 +2480,14 @@ is_valid_call(Call) ->
_ -> true
end.
-%% is_valid_map_key(K,St) -> true | false | {false, Var::atom()}
-%% check for value expression without variables
-
-is_valid_map_key(K,St) ->
- is_valid_map_key(K,expr,St).
-is_valid_map_key(K,Ctx,St) ->
- case expr(K,[],St) of
- {[],_} ->
- is_valid_map_key_value(K,Ctx);
- {[Var|_],_} ->
- {false,variable,element(1,Var)}
- end.
+%% is_valid_map_key(K) -> true | false
+%% variables are allowed for patterns only at the top of the tree
-is_valid_map_key_value(K,Ctx) ->
+is_valid_map_key({var,_,_}) -> true;
+is_valid_map_key(K) -> is_valid_map_key_value(K).
+is_valid_map_key_value(K) ->
case K of
+ {var,_,_} -> false;
{char,_,_} -> true;
{integer,_,_} -> true;
{float,_,_} -> true;
@@ -2340,36 +2495,36 @@ is_valid_map_key_value(K,Ctx) ->
{nil,_} -> true;
{atom,_,_} -> true;
{cons,_,H,T} ->
- is_valid_map_key_value(H,Ctx) andalso
- is_valid_map_key_value(T,Ctx);
+ is_valid_map_key_value(H) andalso
+ is_valid_map_key_value(T);
{tuple,_,Es} ->
foldl(fun(E,B) ->
- B andalso is_valid_map_key_value(E,Ctx)
+ B andalso is_valid_map_key_value(E)
end,true,Es);
{map,_,Arg,Ps} ->
% only check for value expressions to be valid
% invalid map expressions are later checked in
% core and kernel
- is_valid_map_key_value(Arg,Ctx) andalso foldl(fun
+ is_valid_map_key_value(Arg) andalso foldl(fun
({Tag,_,Ke,Ve},B) when Tag =:= map_field_assoc;
- Tag =:= map_field_exact, Ctx =:= expr ->
- B andalso is_valid_map_key_value(Ke,Ctx)
- andalso is_valid_map_key_value(Ve,Ctx);
+ Tag =:= map_field_exact ->
+ B andalso is_valid_map_key_value(Ke)
+ andalso is_valid_map_key_value(Ve);
(_,_) -> false
end,true,Ps);
{map,_,Ps} ->
foldl(fun
({Tag,_,Ke,Ve},B) when Tag =:= map_field_assoc;
- Tag =:= map_field_exact, Ctx =:= expr ->
- B andalso is_valid_map_key_value(Ke,Ctx)
- andalso is_valid_map_key_value(Ve,Ctx);
+ Tag =:= map_field_exact ->
+ B andalso is_valid_map_key_value(Ke)
+ andalso is_valid_map_key_value(Ve);
(_,_) -> false
end, true, Ps);
{record,_,_,Fs} ->
foldl(fun
({record_field,_,Ke,Ve},B) ->
- B andalso is_valid_map_key_value(Ke,Ctx)
- andalso is_valid_map_key_value(Ve,Ctx)
+ B andalso is_valid_map_key_value(Ke)
+ andalso is_valid_map_key_value(Ve)
end,true,Fs);
{bin,_,Es} ->
% only check for value expressions to be valid
@@ -2377,9 +2532,9 @@ is_valid_map_key_value(K,Ctx) ->
% core and kernel
foldl(fun
({bin_element,_,E,_,_},B) ->
- B andalso is_valid_map_key_value(E,Ctx)
+ B andalso is_valid_map_key_value(E)
end,true,Es);
- _ -> false
+ Val -> is_pattern_expr(Val)
end.
%% record_def(Line, RecordName, [RecField], State) -> State.
@@ -2391,7 +2546,10 @@ record_def(Line, Name, Fs0, St0) ->
true -> add_error(Line, {redefine_record,Name}, St0);
false ->
{Fs1,St1} = def_fields(normalise_fields(Fs0), Name, St0),
- St1#lint{records=dict:store(Name, {Line,Fs1}, St1#lint.records)}
+ St2 = St1#lint{records=dict:store(Name, {Line,Fs1},
+ St1#lint.records)},
+ Types = [T || {typed_record_field, _, T} <- Fs0],
+ check_type({type, nowarn(), product, Types}, St2)
end.
%% def_fields([RecDef], RecordName, State) -> {[DefField],State}.
@@ -2594,11 +2752,8 @@ find_field(_F, []) -> error.
%% Attr :: 'type' | 'opaque'
%% Checks that a type definition is valid.
-type_def(_Attr, _Line, {record, _RecName}, Fields, [], St0) ->
- %% The record field names and such are checked in the record format.
- %% We only need to check the types.
- Types = [T || {typed_record_field, _, T} <- Fields],
- check_type({type, -1, product, Types}, St0);
+-dialyzer({no_match, type_def/6}).
+
type_def(Attr, Line, TypeName, ProtoType, Args, St0) ->
TypeDefs = St0#lint.types,
Arity = length(Args),
@@ -2607,37 +2762,28 @@ type_def(Attr, Line, TypeName, ProtoType, Args, St0) ->
StoreType =
fun(St) ->
NewDefs = dict:store(TypePair, Info, TypeDefs),
- CheckType = {type, -1, product, [ProtoType|Args]},
+ CheckType = {type, nowarn(), product, [ProtoType|Args]},
check_type(CheckType, St#lint{types=NewDefs})
end,
case is_default_type(TypePair) of
true ->
case is_obsolete_builtin_type(TypePair) of
true -> StoreType(St0);
- false -> add_error(Line, {builtin_type, TypePair}, St0)
-%% case is_newly_introduced_builtin_type(TypePair) of
-%% %% allow some types just for bootstrapping
-%% true ->
-%% Warn = {new_builtin_type, TypePair},
-%% St1 = add_warning(Line, Warn, St0),
-%% StoreType(St1);
-%% false ->
-%% add_error(Line, {builtin_type, TypePair}, St0)
-%% end
+ false ->
+ case is_newly_introduced_builtin_type(TypePair) of
+ %% allow some types just for bootstrapping
+ true ->
+ Warn = {new_builtin_type, TypePair},
+ St1 = add_warning(Line, Warn, St0),
+ StoreType(St1);
+ false ->
+ add_error(Line, {builtin_type, TypePair}, St0)
+ end
end;
false ->
- case
- dict:is_key(TypePair, TypeDefs) orelse
- is_var_arity_type(TypeName)
- of
+ case dict:is_key(TypePair, TypeDefs) of
true ->
- case is_newly_introduced_var_arity_type(TypeName) of
- true ->
- Warn = {new_var_arity_type, TypeName},
- add_warning(Line, Warn, St0);
- false ->
- add_error(Line, {redefine_type, TypePair}, St0)
- end;
+ add_error(Line, {redefine_type, TypePair}, St0);
false ->
St1 = case
Attr =:= opaque andalso
@@ -2669,12 +2815,13 @@ check_type(Types, St) ->
check_type({ann_type, _L, [_Var, Type]}, SeenVars, St) ->
check_type(Type, SeenVars, St);
-check_type({paren_type, _L, [Type]}, SeenVars, St) ->
- check_type(Type, SeenVars, St);
check_type({remote_type, L, [{atom, _, Mod}, {atom, _, Name}, Args]},
- SeenVars, #lint{module=CurrentMod} = St) ->
+ SeenVars, St00) ->
+ St0 = check_module_name(Mod, L, St00),
+ St = deprecated_type(L, Mod, Name, Args, St0),
+ CurrentMod = St#lint.module,
case Mod =:= CurrentMod of
- true -> check_type({type, L, Name, Args}, SeenVars, St);
+ true -> check_type({user_type, L, Name, Args}, SeenVars, St);
false ->
lists:foldl(fun(T, {AccSeenVars, AccSt}) ->
check_type(T, AccSeenVars, AccSt)
@@ -2700,7 +2847,7 @@ check_type({type, L, 'fun', [Dom, Range]}, SeenVars, St) ->
{type, _, any} -> St;
_ -> add_error(L, {type_syntax, 'fun'}, St)
end,
- check_type({type, -1, product, [Dom, Range]}, SeenVars, St1);
+ check_type({type, nowarn(), product, [Dom, Range]}, SeenVars, St1);
check_type({type, L, range, [From, To]}, SeenVars, St) ->
St1 =
case {erl_eval:partial_eval(From), erl_eval:partial_eval(To)} of
@@ -2708,13 +2855,14 @@ check_type({type, L, range, [From, To]}, SeenVars, St) ->
_ -> add_error(L, {type_syntax, range}, St)
end,
{SeenVars, St1};
-check_type({type, _L, map, any}, SeenVars, St) -> {SeenVars, St};
+check_type({type, _L, map, any}, SeenVars, St) ->
+ {SeenVars, St};
check_type({type, _L, map, Pairs}, SeenVars, St) ->
lists:foldl(fun(Pair, {AccSeenVars, AccSt}) ->
check_type(Pair, AccSeenVars, AccSt)
end, {SeenVars, St}, Pairs);
-check_type({type, _L, map_field_assoc, Dom, Range}, SeenVars, St) ->
- check_type({type, -1, product, [Dom, Range]}, SeenVars, St);
+check_type({type, _L, map_field_assoc, [Dom, Range]}, SeenVars, St) ->
+ check_type({type, nowarn(), product, [Dom, Range]}, SeenVars, St);
check_type({type, _L, tuple, any}, SeenVars, St) -> {SeenVars, St};
check_type({type, _L, any}, SeenVars, St) -> {SeenVars, St};
check_type({type, L, binary, [Base, Unit]}, SeenVars, St) ->
@@ -2732,41 +2880,41 @@ check_type({type, L, record, [Name|Fields]}, SeenVars, St) ->
check_record_types(L, Atom, Fields, SeenVars, St1);
_ -> {SeenVars, add_error(L, {type_syntax, record}, St)}
end;
-check_type({type, _L, product, Args}, SeenVars, St) ->
+check_type({type, _L, Tag, Args}, SeenVars, St) when Tag =:= product;
+ Tag =:= union;
+ Tag =:= tuple ->
lists:foldl(fun(T, {AccSeenVars, AccSt}) ->
check_type(T, AccSeenVars, AccSt)
end, {SeenVars, St}, Args);
check_type({type, La, TypeName, Args}, SeenVars, St) ->
- #lint{usage=Usage, module = Module, types=Types} = St,
+ #lint{module = Module, types=Types} = St,
Arity = length(Args),
TypePair = {TypeName, Arity},
- St1 = case is_var_arity_type(TypeName) of
- true -> St;
- false ->
- Obsolete = (is_warn_enabled(deprecated_type, St)
- andalso obsolete_builtin_type(TypePair)),
- IsObsolete =
- case Obsolete of
- {deprecated, Repl, _} when element(1, Repl) =/= Module ->
- case dict:find(TypePair, Types) of
- {ok, _} -> false;
- error -> true
- end;
- _ -> false
- end,
- case IsObsolete of
- true ->
+ Obsolete = (is_warn_enabled(deprecated_type, St)
+ andalso obsolete_builtin_type(TypePair)),
+ St1 = case Obsolete of
+ {deprecated, Repl, _} when element(1, Repl) =/= Module ->
+ case dict:find(TypePair, Types) of
+ {ok, _} ->
+ used_type(TypePair, La, St);
+ error ->
{deprecated, Replacement, Rel} = Obsolete,
Tag = deprecated_builtin_type,
W = {Tag, TypePair, Replacement, Rel},
- add_warning(La, W, St);
- false ->
- OldUsed = Usage#usage.used_types,
- UsedTypes = dict:store(TypePair, La, OldUsed),
- St#lint{usage=Usage#usage{used_types=UsedTypes}}
- end
- end,
- check_type({type, -1, product, Args}, SeenVars, St1);
+ add_warning(La, W, St)
+ end;
+ _ -> St
+ end,
+ check_type({type, nowarn(), product, Args}, SeenVars, St1);
+check_type({user_type, L, TypeName, Args}, SeenVars, St) ->
+ Arity = length(Args),
+ TypePair = {TypeName, Arity},
+ St1 = used_type(TypePair, L, St),
+ lists:foldl(fun(T, {AccSeenVars, AccSt}) ->
+ check_type(T, AccSeenVars, AccSt)
+ end, {SeenVars, St1}, Args);
+check_type([{typed_record_field,Field,_T}|_], SeenVars, St) ->
+ {SeenVars, add_error(element(2, Field), old_abstract_code, St)};
check_type(I, SeenVars, St) ->
case erl_eval:partial_eval(I) of
{integer,_ILn,_Integer} -> {SeenVars, St};
@@ -2808,125 +2956,100 @@ check_record_types([{type, _, field_type, [{atom, AL, FName}, Type]}|Left],
check_record_types([], _Name, _DefFields, SeenVars, St, _SeenFields) ->
{SeenVars, St}.
-is_var_arity_type(tuple) -> true;
-is_var_arity_type(map) -> true;
-is_var_arity_type(product) -> true;
-is_var_arity_type(union) -> true;
-is_var_arity_type(record) -> true;
-is_var_arity_type(_) -> false.
-
-is_default_type({any, 0}) -> true;
-is_default_type({arity, 0}) -> true;
-is_default_type({array, 0}) -> true;
-is_default_type({atom, 0}) -> true;
-is_default_type({atom, 1}) -> true;
-is_default_type({binary, 0}) -> true;
-is_default_type({binary, 2}) -> true;
-is_default_type({bitstring, 0}) -> true;
-is_default_type({bool, 0}) -> true;
-is_default_type({boolean, 0}) -> true;
-is_default_type({byte, 0}) -> true;
-is_default_type({char, 0}) -> true;
-is_default_type({dict, 0}) -> true;
-is_default_type({digraph, 0}) -> true;
-is_default_type({float, 0}) -> true;
-is_default_type({'fun', 0}) -> true;
-is_default_type({'fun', 2}) -> true;
-is_default_type({function, 0}) -> true;
-is_default_type({gb_set, 0}) -> true;
-is_default_type({gb_tree, 0}) -> true;
-is_default_type({identifier, 0}) -> true;
-is_default_type({integer, 0}) -> true;
-is_default_type({integer, 1}) -> true;
-is_default_type({iodata, 0}) -> true;
-is_default_type({iolist, 0}) -> true;
-is_default_type({list, 0}) -> true;
-is_default_type({list, 1}) -> true;
-is_default_type({maybe_improper_list, 0}) -> true;
-is_default_type({maybe_improper_list, 2}) -> true;
-is_default_type({mfa, 0}) -> true;
-is_default_type({module, 0}) -> true;
-is_default_type({neg_integer, 0}) -> true;
-is_default_type({nil, 0}) -> true;
-is_default_type({no_return, 0}) -> true;
-is_default_type({node, 0}) -> true;
-is_default_type({non_neg_integer, 0}) -> true;
-is_default_type({none, 0}) -> true;
-is_default_type({nonempty_list, 0}) -> true;
-is_default_type({nonempty_list, 1}) -> true;
-is_default_type({nonempty_improper_list, 2}) -> true;
-is_default_type({nonempty_maybe_improper_list, 0}) -> true;
-is_default_type({nonempty_maybe_improper_list, 2}) -> true;
-is_default_type({nonempty_string, 0}) -> true;
-is_default_type({number, 0}) -> true;
-is_default_type({pid, 0}) -> true;
-is_default_type({port, 0}) -> true;
-is_default_type({pos_integer, 0}) -> true;
-is_default_type({queue, 0}) -> true;
-is_default_type({range, 2}) -> true;
-is_default_type({reference, 0}) -> true;
-is_default_type({set, 0}) -> true;
-is_default_type({string, 0}) -> true;
-is_default_type({term, 0}) -> true;
-is_default_type({timeout, 0}) -> true;
-is_default_type({var, 1}) -> true;
-is_default_type(_) -> false.
-
-is_newly_introduced_var_arity_type(map) -> true;
-is_newly_introduced_var_arity_type(_) -> false.
-
-%% is_newly_introduced_builtin_type({Name, _}) when is_atom(Name) -> false.
+used_type(TypePair, L, #lint{usage = Usage, file = File} = St) ->
+ OldUsed = Usage#usage.used_types,
+ UsedTypes = dict:store(TypePair, erl_anno:set_file(File, L), OldUsed),
+ St#lint{usage=Usage#usage{used_types=UsedTypes}}.
+
+is_default_type({Name, NumberOfTypeVariables}) ->
+ erl_internal:is_type(Name, NumberOfTypeVariables).
+
+is_newly_introduced_builtin_type({Name, _}) when is_atom(Name) -> false.
is_obsolete_builtin_type(TypePair) ->
obsolete_builtin_type(TypePair) =/= no.
-%% Obsolete in OTP 17.0.
-obsolete_builtin_type({array, 0}) ->
- {deprecated, {array, array, 1}, "OTP 18.0"};
-obsolete_builtin_type({dict, 0}) ->
- {deprecated, {dict, dict, 2}, "OTP 18.0"};
-obsolete_builtin_type({digraph, 0}) ->
- {deprecated, {digraph, graph}, "OTP 18.0"};
-obsolete_builtin_type({gb_set, 0}) ->
- {deprecated, {gb_sets, set, 1}, "OTP 18.0"};
-obsolete_builtin_type({gb_tree, 0}) ->
- {deprecated, {gb_trees, tree, 2}, "OTP 18.0"};
-obsolete_builtin_type({queue, 0}) ->
- {deprecated, {queue, queue, 1}, "OTP 18.0"};
-obsolete_builtin_type({set, 0}) ->
- {deprecated, {sets, set, 1}, "OTP 18.0"};
-obsolete_builtin_type({tid, 0}) ->
- {deprecated, {ets, tid}, "OTP 18.0"};
+%% To keep Dialyzer silent...
+obsolete_builtin_type({1, 255}) ->
+ {deprecated, {2, 255}, ""};
obsolete_builtin_type({Name, A}) when is_atom(Name), is_integer(A) -> no.
%% spec_decl(Line, Fun, Types, State) -> State.
-spec_decl(Line, MFA0, TypeSpecs, St0 = #lint{specs = Specs, module = Mod}) ->
+spec_decl(Line, MFA0, TypeSpecs, St00 = #lint{specs = Specs, module = Mod}) ->
MFA = case MFA0 of
{F, Arity} -> {Mod, F, Arity};
{_M, _F, Arity} -> MFA0
end,
+ St0 = check_module_name(element(1, MFA), Line, St00),
St1 = St0#lint{specs = dict:store(MFA, Line, Specs)},
case dict:is_key(MFA, Specs) of
- true -> add_error(Line, {redefine_spec, MFA}, St1);
- false -> check_specs(TypeSpecs, Arity, St1)
+ true -> add_error(Line, {redefine_spec, MFA0}, St1);
+ false -> check_specs(TypeSpecs, spec_wrong_arity, Arity, St1)
end.
%% callback_decl(Line, Fun, Types, State) -> State.
callback_decl(Line, MFA0, TypeSpecs,
St0 = #lint{callbacks = Callbacks, module = Mod}) ->
- MFA = case MFA0 of
- {F, Arity} -> {Mod, F, Arity};
- {_M, _F, Arity} -> MFA0
- end,
- St1 = St0#lint{callbacks = dict:store(MFA, Line, Callbacks)},
- case dict:is_key(MFA, Callbacks) of
- true -> add_error(Line, {redefine_callback, MFA}, St1);
- false -> check_specs(TypeSpecs, Arity, St1)
+ case MFA0 of
+ {M, _F, _A} ->
+ St1 = check_module_name(M, Line, St0),
+ add_error(Line, {bad_callback, MFA0}, St1);
+ {F, Arity} ->
+ MFA = {Mod, F, Arity},
+ St1 = St0#lint{callbacks = dict:store(MFA, Line, Callbacks)},
+ case dict:is_key(MFA, Callbacks) of
+ true -> add_error(Line, {redefine_callback, MFA0}, St1);
+ false -> check_specs(TypeSpecs, callback_wrong_arity,
+ Arity, St1)
+ end
+ end.
+
+%% optional_callbacks(Line, FAs, State) -> State.
+
+optional_callbacks(Line, Term, St0) ->
+ try true = is_fa_list(Term), Term of
+ FAs ->
+ optional_cbs(Line, FAs, St0)
+ catch
+ _:_ ->
+ St0 % ignore others
+ end.
+
+optional_cbs(_Line, [], St) ->
+ St;
+optional_cbs(Line, [{F,A}|FAs], St0) ->
+ #lint{optional_callbacks = OptionalCbs, module = Mod} = St0,
+ MFA = {Mod, F, A},
+ St1 = St0#lint{optional_callbacks = dict:store(MFA, Line, OptionalCbs)},
+ St2 = case dict:is_key(MFA, OptionalCbs) of
+ true ->
+ add_error(Line, {redefine_optional_callback, {F,A}}, St1);
+ false ->
+ St1
+ end,
+ optional_cbs(Line, FAs, St2).
+
+is_fa_list([E|L]) -> is_fa(E) andalso is_fa_list(L);
+is_fa_list([]) -> true;
+is_fa_list(_) -> false.
+
+is_fa({FuncName, Arity})
+ when is_atom(FuncName), is_integer(Arity), Arity >= 0 -> true;
+is_fa(_) -> false.
+
+check_module_name(M, Line, St) ->
+ case is_latin1_name(M) of
+ true -> St;
+ false ->
+ add_error(Line, non_latin1_module_unsupported, St)
end.
-check_specs([FunType|Left], Arity, St0) ->
+is_latin1_name(Name) ->
+ io_lib:latin1_char_list(atom_to_list(Name)).
+
+check_specs([FunType|Left], ETag, Arity, St0) ->
{FunType1, CTypes} =
case FunType of
{type, _, bounded_fun, [FT = {type, _, 'fun', _}, Cs]} ->
@@ -2934,25 +3057,29 @@ check_specs([FunType|Left], Arity, St0) ->
{FT, lists:append(Types0)};
{type, _, 'fun', _} = FT -> {FT, []}
end,
- SpecArity =
- case FunType1 of
- {type, L, 'fun', [any, _]} -> any;
- {type, L, 'fun', [{type, _, product, D}, _]} -> length(D)
- end,
+ {type, L, 'fun', [{type, _, product, D}, _]} = FunType1,
+ SpecArity = length(D),
St1 = case Arity =:= SpecArity of
true -> St0;
- false -> add_error(L, spec_wrong_arity, St0)
+ false -> %% Cannot happen if called from the compiler.
+ add_error(L, ETag, St0)
end,
- St2 = check_type({type, -1, product, [FunType1|CTypes]}, St1),
- check_specs(Left, Arity, St2);
-check_specs([], _Arity, St) ->
+ St2 = check_type({type, nowarn(), product, [FunType1|CTypes]}, St1),
+ check_specs(Left, ETag, Arity, St2);
+check_specs([], _ETag, _Arity, St) ->
St.
+nowarn() ->
+ A0 = erl_anno:new(0),
+ A1 = erl_anno:set_generated(true, A0),
+ erl_anno:set_file("", A1).
+
check_specs_without_function(#lint{module=Mod,defined=Funcs,specs=Specs}=St) ->
- Fun = fun({M, F, A} = MFA, Line, AccSt) when M =:= Mod ->
- case gb_sets:is_element({F, A}, Funcs) of
+ Fun = fun({M, F, A}, Line, AccSt) when M =:= Mod ->
+ FA = {F, A},
+ case gb_sets:is_element(FA, Funcs) of
true -> AccSt;
- false -> add_error(Line, {spec_fun_undefined, MFA}, AccSt)
+ false -> add_error(Line, {spec_fun_undefined, FA}, AccSt)
end;
({_M, _F, _A}, _Line, AccSt) -> AccSt
end,
@@ -2981,9 +3108,10 @@ add_missing_spec_warnings(Forms, St0, Type) ->
[{FA,L} || {function,L,F,A,_} <- Forms,
not lists:member(FA = {F,A}, Specs)];
exported ->
- Exps = gb_sets:to_list(St0#lint.exports) -- pseudolocals(),
+ Exps0 = gb_sets:to_list(St0#lint.exports) -- pseudolocals(),
+ Exps = Exps0 -- Specs,
[{FA,L} || {function,L,F,A,_} <- Forms,
- member(FA = {F,A}, Exps -- Specs)]
+ member(FA = {F,A}, Exps)]
end,
foldl(fun ({FA,L}, St) ->
add_warning(L, {missing_spec,FA}, St)
@@ -2996,8 +3124,10 @@ check_unused_types(Forms, #lint{usage=Usage, types=Ts, exp_types=ExpTs}=St) ->
L = gb_sets:to_list(ExpTs) ++ dict:fetch_keys(D),
UsedTypes = gb_sets:from_list(L),
FoldFun =
- fun(Type, #typeinfo{line = FileLine}, AccSt) ->
- case loc(FileLine) of
+ fun({{record, _}=_Type, 0}, _, AccSt) ->
+ AccSt; % Before Erlang/OTP 19.0
+ (Type, #typeinfo{line = FileLine}, AccSt) ->
+ case loc(FileLine, AccSt) of
{FirstFile, _} ->
case gb_sets:is_member(Type, UsedTypes) of
true -> AccSt;
@@ -3030,12 +3160,77 @@ check_local_opaque_types(St) ->
end,
dict:fold(FoldFun, St, Ts).
+check_dialyzer_attribute(Forms, St0) ->
+ Vals = [{L,V} ||
+ {attribute,L,dialyzer,Val} <- Forms,
+ V0 <- lists:flatten([Val]),
+ V <- case V0 of
+ {O,F} ->
+ [{A,B} ||
+ A <- lists:flatten([O]),
+ B <- lists:flatten([F])];
+ T -> [T]
+ end],
+ {Wellformed, Bad} =
+ lists:partition(fun ({_,{Option,FA}}) when is_atom(Option) ->
+ is_fa(FA);
+ ({_,Option}) when is_atom(Option) -> true;
+ (_) -> false
+ end, Vals),
+ St1 = foldl(fun ({L,Term}, St) ->
+ add_error(L, {bad_dialyzer_attribute,Term}, St)
+ end, St0, Bad),
+ DefFunctions = (gb_sets:to_list(St0#lint.defined) -- pseudolocals()),
+ Fun = fun ({L,{Option,FA}}, St) ->
+ case is_function_dialyzer_option(Option) of
+ true ->
+ case lists:member(FA, DefFunctions) of
+ true -> St;
+ false ->
+ add_error(L, {undefined_function,FA}, St)
+ end;
+ false ->
+ add_error(L, {bad_dialyzer_option,Option}, St)
+ end;
+ ({L,Option}, St) ->
+ case is_module_dialyzer_option(Option) of
+ true -> St;
+ false ->
+ add_error(L, {bad_dialyzer_option,Option}, St)
+ end
+ end,
+ foldl(Fun, St1, Wellformed).
+
+is_function_dialyzer_option(nowarn_function) -> true;
+is_function_dialyzer_option(Option) ->
+ is_module_dialyzer_option(Option).
+
+is_module_dialyzer_option(Option) ->
+ lists:member(Option,
+ [no_return,no_unused,no_improper_lists,no_fun_app,
+ no_match,no_opaque,no_fail_call,no_contracts,
+ no_behaviours,no_undefined_callbacks,unmatched_returns,
+ error_handling,race_conditions,no_missing_calls,
+ specdiffs,overspecs,underspecs,unknown]).
+
+%% try_catch_clauses(Scs, Ccs, In, ImportVarTable, State) ->
+%% {UpdVt,State}.
+
+try_clauses(Scs, Ccs, In, Vt, St0) ->
+ {Csvt0,St1} = icrt_clauses(Scs, Vt, St0),
+ St2 = St1#lint{catch_scope=try_catch},
+ {Csvt1,St3} = icrt_clauses(Ccs, Vt, St2),
+ Csvt = Csvt0 ++ Csvt1,
+ UpdVt = icrt_export(Csvt, Vt, In, St3),
+ {UpdVt,St3}.
+
%% icrt_clauses(Clauses, In, ImportVarTable, State) ->
-%% {NewVts,State}.
+%% {UpdVt,State}.
icrt_clauses(Cs, In, Vt, St0) ->
{Csvt,St1} = icrt_clauses(Cs, Vt, St0),
- icrt_export(Csvt, Vt, In, St1).
+ UpdVt = icrt_export(Csvt, Vt, In, St1),
+ {UpdVt,St1}.
%% icrt_clauses(Clauses, ImportVarTable, State) ->
%% {NewVts,State}.
@@ -3043,28 +3238,75 @@ icrt_clauses(Cs, In, Vt, St0) ->
icrt_clauses(Cs, Vt, St) ->
mapfoldl(fun (C, St0) -> icrt_clause(C, Vt, St0) end, St, Cs).
-icrt_clause({clause,_Line,H,G,B}, Vt0, St0) ->
+icrt_clause({clause,_Line,H,G,B}, Vt0, #lint{catch_scope=Scope}=St0) ->
{Hvt,Binvt,St1} = head(H, Vt0, St0),
- Vt1 = vtupdate(Hvt, vtupdate(Binvt, Vt0)),
- {Gvt,St2} = guard(G, Vt1, St1),
+ Vt1 = vtupdate(Hvt, Binvt),
+ {Gvt,St2} = guard(G, vtupdate(Vt1, Vt0), St1),
Vt2 = vtupdate(Gvt, Vt1),
- {Bvt,St3} = exprs(B, Vt2, St2),
- {vtupdate(Bvt, Vt2),St3}.
-
-icrt_export(Csvt, Vt, In, St) ->
- Vt1 = vtmerge(Csvt),
- All = ordsets:subtract(vintersection(Csvt), vtnames(Vt)),
- Some = ordsets:subtract(vtnames(Vt1), vtnames(Vt)),
- Xvt = vtexport(All, In, []),
- Evt = vtunsafe(ordsets:subtract(Some, All), In, Xvt),
- Unused = vtmerge([unused_vars(Vt0, Vt, St) || Vt0 <- Csvt]),
- %% Exported and unsafe variables may be unused:
- Uvt = vtmerge(Evt, Unused),
- %% Make exported and unsafe unused variables unused in subsequent code:
- Vt2 = vtmerge(Uvt, vtsubtract(Vt1, Uvt)),
- %% Forget about old variables which were not used:
- Vt3 = vtmerge(vtnew(Vt2, Vt), vt_no_unused(vtold(Vt2, Vt))),
- {Vt3,St}.
+ {Bvt,St3} = exprs(B, vtupdate(Vt2, Vt0), St2),
+ {vtupdate(Bvt, Vt2),St3#lint{catch_scope=Scope}}.
+
+icrt_export(Vts, Vt, {Tag,Attrs}, St) ->
+ {_File,Loc} = loc(Attrs, St),
+ icrt_export(lists:merge(Vts), Vt, {Tag,Loc}, length(Vts), []).
+
+icrt_export([{V,{{export,_},_,_}}|Vs0], [{V,{{export,_}=S0,_,Ls}}|Vt],
+ In, I, Acc) ->
+ %% V was an exported variable and has been used in an expression in at least
+ %% one clause. Its state needs to be merged from all clauses to silence any
+ %% exported var warning already emitted.
+ {VVs,Vs} = lists:partition(fun ({K,_}) -> K =:= V end, Vs0),
+ S = foldl(fun ({_,{S1,_,_}}, AccS) -> merge_state(AccS, S1) end, S0, VVs),
+ icrt_export(Vs, Vt, In, I, [{V,{S,used,Ls}}|Acc]);
+icrt_export([{V,_}|Vs0], [{V,{_,_,Ls}}|Vt], In, I, Acc) ->
+ %% V was either unsafe or bound and has now been reused. It may also have
+ %% been an export but as it was not matched by the previous clause, it means
+ %% it has been changed to 'bound' in at least one clause because it was used
+ %% in a pattern.
+ Vs = lists:dropwhile(fun ({K,_}) -> K =:= V end, Vs0),
+ icrt_export(Vs, Vt, In, I, [{V,{bound,used,Ls}}|Acc]);
+icrt_export([{V1,_}|_]=Vs, [{V2,_}|Vt], In, I, Acc) when V1 > V2 ->
+ %% V2 was already in scope and has not been reused in any clause.
+ icrt_export(Vs, Vt, In, I, Acc);
+icrt_export([{V,_}|_]=Vs0, Vt, In, I, Acc) ->
+ %% V is a new variable.
+ {VVs,Vs} = lists:partition(fun ({K,_}) -> K =:= V end, Vs0),
+ F = fun ({_,{S,U,Ls}}, {AccI,AccS0,AccLs0}) ->
+ AccS = case {S,AccS0} of
+ {{unsafe,_},{unsafe,_}} ->
+ %% V was found unsafe in a previous clause, mark
+ %% it as unsafe for the whole parent expression.
+ {unsafe,In};
+ {{unsafe,_},_} ->
+ %% V was unsafe in a clause, keep that state and
+ %% generalize it to the whole expression if it
+ %% is found unsafe in another one.
+ S;
+ _ ->
+ %% V is either bound or exported, keep original
+ %% state.
+ AccS0
+ end,
+ AccLs = case U of
+ used -> AccLs0;
+ unused -> merge_lines(AccLs0, Ls)
+ end,
+ {AccI + 1,AccS,AccLs}
+ end,
+ %% Initial state is exported from the current expression.
+ {Count,S1,Ls} = foldl(F, {0,{export,In},[]}, VVs),
+ S = case Count of
+ I ->
+ %% V was found in all clauses, keep computed state.
+ S1;
+ _ ->
+ %% V was not bound in some clauses, mark as unsafe.
+ {unsafe,In}
+ end,
+ U = case Ls of [] -> used; _ -> unused end,
+ icrt_export(Vs, Vt, In, I, [{V,{S,U,Ls}}|Acc]);
+icrt_export([], _, _, _, Acc) ->
+ reverse(Acc).
handle_comprehension(E, Qs, Vt0, St0) ->
{Vt1, Uvt, St1} = lc_quals(Qs, Vt0, St0),
@@ -3107,7 +3349,8 @@ lc_quals([{b_generate,_Line,P,E} | Qs], Vt0, Uvt0, St0) ->
{Vt,Uvt,St} = handle_generator(P,E,Vt0,Uvt0,St1),
lc_quals(Qs, Vt, Uvt, St);
lc_quals([F|Qs], Vt, Uvt, St0) ->
- {Fvt,St1} = case is_guard_test2(F, St0#lint.records) of
+ Info = is_guard_test2_info(St0),
+ {Fvt,St1} = case is_guard_test2(F, Info) of
true -> guard_test(F, Vt, St0);
false -> expr(F, Vt, St0)
end,
@@ -3115,6 +3358,12 @@ lc_quals([F|Qs], Vt, Uvt, St0) ->
lc_quals([], Vt, Uvt, St) ->
{Vt, Uvt, St}.
+is_guard_test2_info(#lint{records=RDs,locals=Locals,imports=Imports}) ->
+ {RDs,fun(FA) ->
+ is_local_function(Locals, FA) orelse
+ is_imported_function(Imports, FA)
+ end}.
+
handle_generator(P,E,Vt,Uvt,St0) ->
{Evt,St1} = expr(E, Vt, St0),
%% Forget variables local to E immediately.
@@ -3135,8 +3384,8 @@ handle_generator(P,E,Vt,Uvt,St0) ->
handle_bitstring_gen_pat({bin,_,Segments=[_|_]},St) ->
case lists:last(Segments) of
{bin_element,Line,{var,_,_},default,Flags} when is_list(Flags) ->
- case member(binary, Flags) orelse member(bits, Flags)
- orelse member(bitstring, Flags) of
+ case member(binary, Flags) orelse member(bytes, Flags)
+ orelse member(bits, Flags) orelse member(bitstring, Flags) of
true ->
add_error(Line, unsized_binary_in_bin_gen_pattern, St);
false ->
@@ -3162,7 +3411,8 @@ fun_clauses(Cs, Vt, St) ->
{Cvt,St1} = fun_clause(C, Vt, St0),
{vtmerge(Cvt, Bvt0),St1}
end, {[],St#lint{recdef_top = false}}, Cs),
- {vt_no_unused(vtold(Bvt, Vt)),St2#lint{recdef_top = OldRecDef}}.
+ Uvt = vt_no_unsafe(vt_no_unused(vtold(Bvt, Vt))),
+ {Uvt,St2#lint{recdef_top = OldRecDef}}.
fun_clause({clause,_Line,H,G,B}, Vt0, St0) ->
{Hvt,Binvt,St1} = head(H, Vt0, [], St0), % No imported pattern variables
@@ -3276,19 +3526,24 @@ pat_binsize_var(V, Line, Vt, Bvt, St) ->
%% exported vars are probably safe, warn only if warn_export_vars is
%% set.
-expr_var(V, Line, Vt, St0) ->
+expr_var(V, Line, Vt, St) ->
case orddict:find(V, Vt) of
{ok,{bound,_Usage,Ls}} ->
- {[{V,{bound,used,Ls}}],St0};
+ {[{V,{bound,used,Ls}}],St};
{ok,{{unsafe,In},_Usage,Ls}} ->
{[{V,{bound,used,Ls}}],
- add_error(Line, {unsafe_var,V,In}, St0)};
+ add_error(Line, {unsafe_var,V,In}, St)};
{ok,{{export,From},_Usage,Ls}} ->
- {[{V,{bound,used,Ls}}],
- exported_var(Line, V, From, St0)};
+ case is_warn_enabled(export_vars, St) of
+ true ->
+ {[{V,{bound,used,Ls}}],
+ add_warning(Line, {exported_var,V,From}, St)};
+ false ->
+ {[{V,{{export,From},used,Ls}}],St}
+ end;
error ->
{[{V,{bound,used,[Line]}}],
- add_error(Line, {unbound_var,V}, St0)}
+ add_error(Line, {unbound_var,V}, St)}
end.
exported_var(Line, V, From, St) ->
@@ -3342,6 +3597,14 @@ warn_unused_vars(U, Vt, St0) ->
UVt = map(fun ({V,{State,_,Ls}}) -> {V,{State,used,Ls}} end, U),
{vtmerge(Vt, UVt), St1}.
+
+is_var_bound(V, Vt) ->
+ case orddict:find(V, Vt) of
+ {ok,{bound,_Usage,_}} -> true;
+ _ -> false
+ end.
+
+
%% vtupdate(UpdVarTable, VarTable) -> VarTable.
%% Add the variables in the updated vartable to VarTable. The variables
%% will be updated with their property in UpdVarTable. The state of
@@ -3352,17 +3615,12 @@ vtupdate(Uvt, Vt0) ->
{S, merge_used(U1, U2), merge_lines(L1, L2)}
end, Uvt, Vt0).
-%% vtexport([Variable], From, VarTable) -> VarTable.
-%% vtunsafe([Variable], From, VarTable) -> VarTable.
-%% Add the variables to VarTable either as exported from From or as unsafe.
-
-vtexport(Vs, {InTag,FileLine}, Vt0) ->
- {_File,Line} = loc(FileLine),
- vtupdate([{V,{{export,{InTag,Line}},unused,[]}} || V <- Vs], Vt0).
+%% vtunsafe(From, UpdVarTable, VarTable) -> UnsafeVarTable.
+%% Return all new variables in UpdVarTable as unsafe.
-vtunsafe(Vs, {InTag,FileLine}, Vt0) ->
- {_File,Line} = loc(FileLine),
- vtupdate([{V,{{unsafe,{InTag,Line}},unused,[]}} || V <- Vs], Vt0).
+vtunsafe({Tag,FileLine}, Uvt, Vt) ->
+ Line = erl_anno:location(FileLine),
+ [{V,{{unsafe,{Tag,Line}},U,Ls}} || {V,{_,U,Ls}} <- vtnew(Uvt, Vt)].
%% vtmerge(VarTable, VarTable) -> VarTable.
%% Merge two variables tables generating a new vartable. Give priority to
@@ -3415,8 +3673,6 @@ vtsubtract(New, Old) ->
vtold(New, Old) ->
orddict:filter(fun (V, _How) -> orddict:is_key(V, Old) end, New).
-vtnames(Vt) -> [ V || {V,_How} <- Vt ].
-
vt_no_unsafe(Vt) -> [V || {_,{S,_U,_L}}=V <- Vt,
case S of
{unsafe,_} -> false;
@@ -3425,84 +3681,11 @@ vt_no_unsafe(Vt) -> [V || {_,{S,_U,_L}}=V <- Vt,
vt_no_unused(Vt) -> [V || {_,{_,U,_L}}=V <- Vt, U =/= unused].
-%% vunion(VarTable1, VarTable2) -> [VarName].
-%% vunion([VarTable]) -> [VarName].
-%% vintersection(VarTable1, VarTable2) -> [VarName].
-%% vintersection([VarTable]) -> [VarName].
-%% Union/intersection of names of vars in VarTable.
-
--ifdef(NOTUSED).
-vunion(Vs1, Vs2) -> ordsets:union(vtnames(Vs1), vtnames(Vs2)).
-
-vunion(Vss) -> foldl(fun (Vs, Uvs) ->
- ordsets:union(vtnames(Vs), Uvs)
- end, [], Vss).
-
-vintersection(Vs1, Vs2) -> ordsets:intersection(vtnames(Vs1), vtnames(Vs2)).
--endif.
-
-vintersection([Vs]) ->
- vtnames(Vs); %Boundary conditions!!!
-vintersection([Vs|Vss]) ->
- ordsets:intersection(vtnames(Vs), vintersection(Vss));
-vintersection([]) ->
- [].
-
%% copy_expr(Expr, Line) -> Expr.
%% Make a copy of Expr converting all line numbers to Line.
-copy_expr(Expr, Line) ->
- modify_line(Expr, fun(_L) -> Line end).
-
-%% modify_line(Form, Fun) -> Form
-%% modify_line(Expression, Fun) -> Expression
-%% Applies Fun to each line number occurrence.
-
-modify_line(T, F0) ->
- modify_line1(T, F0).
-
-%% Forms.
-modify_line1({function,F,A}, _Mf) -> {function,F,A};
-modify_line1({function,M,F,A}, Mf) ->
- {function,modify_line1(M, Mf),modify_line1(F, Mf),modify_line1(A, Mf)};
-modify_line1({attribute,L,record,{Name,Fields}}, Mf) ->
- {attribute,Mf(L),record,{Name,modify_line1(Fields, Mf)}};
-modify_line1({attribute,L,spec,{Fun,Types}}, Mf) ->
- {attribute,Mf(L),spec,{Fun,modify_line1(Types, Mf)}};
-modify_line1({attribute,L,callback,{Fun,Types}}, Mf) ->
- {attribute,Mf(L),callback,{Fun,modify_line1(Types, Mf)}};
-modify_line1({attribute,L,type,{TypeName,TypeDef,Args}}, Mf) ->
- {attribute,Mf(L),type,{TypeName,modify_line1(TypeDef, Mf),
- modify_line1(Args, Mf)}};
-modify_line1({attribute,L,opaque,{TypeName,TypeDef,Args}}, Mf) ->
- {attribute,Mf(L),opaque,{TypeName,modify_line1(TypeDef, Mf),
- modify_line1(Args, Mf)}};
-modify_line1({attribute,L,Attr,Val}, Mf) -> {attribute,Mf(L),Attr,Val};
-modify_line1({warning,W}, _Mf) -> {warning,W};
-modify_line1({error,W}, _Mf) -> {error,W};
-%% Expressions.
-modify_line1({clauses,Cs}, Mf) -> {clauses,modify_line1(Cs, Mf)};
-modify_line1({typed_record_field,Field,Type}, Mf) ->
- {typed_record_field,modify_line1(Field, Mf),modify_line1(Type, Mf)};
-modify_line1({Tag,L}, Mf) -> {Tag,Mf(L)};
-modify_line1({Tag,L,E1}, Mf) ->
- {Tag,Mf(L),modify_line1(E1, Mf)};
-modify_line1({Tag,L,E1,E2}, Mf) ->
- {Tag,Mf(L),modify_line1(E1, Mf),modify_line1(E2, Mf)};
-modify_line1({bin_element,L,E1,E2,TSL}, Mf) ->
- {bin_element,Mf(L),modify_line1(E1, Mf),modify_line1(E2, Mf), TSL};
-modify_line1({Tag,L,E1,E2,E3}, Mf) ->
- {Tag,Mf(L),modify_line1(E1, Mf),modify_line1(E2, Mf),modify_line1(E3, Mf)};
-modify_line1({Tag,L,E1,E2,E3,E4}, Mf) ->
- {Tag,Mf(L),
- modify_line1(E1, Mf),
- modify_line1(E2, Mf),
- modify_line1(E3, Mf),
- modify_line1(E4, Mf)};
-modify_line1([H|T], Mf) ->
- [modify_line1(H, Mf)|modify_line1(T, Mf)];
-modify_line1([], _Mf) -> [];
-modify_line1(E, _Mf) when not is_tuple(E), not is_list(E) -> E.
+copy_expr(Expr, Anno) ->
+ erl_parse:map_anno(fun(_A) -> Anno end, Expr).
%% Check a record_info call. We have already checked that it is not
%% shadowed by an import.
@@ -3525,7 +3708,8 @@ has_wildcard_field([]) -> false.
check_remote_function(Line, M, F, As, St0) ->
St1 = deprecated_function(Line, M, F, As, St0),
St2 = check_qlc_hrl(Line, M, F, As, St1),
- format_function(Line, M, F, As, St2).
+ St3 = check_get_stacktrace(Line, M, F, As, St2),
+ format_function(Line, M, F, As, St3).
%% check_qlc_hrl(Line, ModName, FuncName, [Arg], State) -> State
%% Add warning if qlc:q/1,2 has been called but qlc.hrl has not
@@ -3544,6 +3728,8 @@ check_qlc_hrl(Line, M, F, As, St) ->
%% deprecated_function(Line, ModName, FuncName, [Arg], State) -> State.
%% Add warning for calls to deprecated functions.
+-dialyzer({no_match, deprecated_function/5}).
+
deprecated_function(Line, M, F, As, St) ->
Arity = length(As),
MFA = {M, F, Arity},
@@ -3572,22 +3758,67 @@ deprecated_function(Line, M, F, As, St) ->
St
end.
+check_get_stacktrace(Line, erlang, get_stacktrace, [], St) ->
+ case St of
+ #lint{catch_scope=none} ->
+ St;
+ #lint{catch_scope=try_catch} ->
+ St;
+ #lint{catch_scope=Scope} ->
+ case is_warn_enabled(get_stacktrace, St) of
+ false ->
+ St;
+ true ->
+ add_warning(Line, {get_stacktrace,Scope}, St)
+ end
+ end;
+check_get_stacktrace(_, _, _, _, St) ->
+ St.
+
+-dialyzer({no_match, deprecated_type/5}).
+
+deprecated_type(L, M, N, As, St) ->
+ NAs = length(As),
+ case otp_internal:obsolete_type(M, N, NAs) of
+ {deprecated, String} when is_list(String) ->
+ case is_warn_enabled(deprecated_type, St) of
+ true ->
+ add_warning(L, {deprecated_type, {M,N,NAs}, String}, St);
+ false ->
+ St
+ end;
+ {removed, Replacement, Rel} ->
+ add_warning(L, {removed_type, {M,N,NAs}, Replacement, Rel}, St);
+ no ->
+ St
+ end.
+
obsolete_guard({call,Line,{atom,Lr,F},As}, St0) ->
Arity = length(As),
case erl_internal:old_type_test(F, Arity) of
false ->
deprecated_function(Line, erlang, F, As, St0);
true ->
- case is_warn_enabled(obsolete_guard, St0) of
- true ->
- add_warning(Lr,{obsolete_guard, {F, Arity}}, St0);
- false ->
- St0
- end
+ St = case is_warn_enabled(obsolete_guard, St0) of
+ true ->
+ add_warning(Lr, {obsolete_guard, {F, Arity}}, St0);
+ false ->
+ St0
+ end,
+ test_overriden_by_local(Lr, F, Arity, St)
end;
obsolete_guard(_G, St) ->
St.
+test_overriden_by_local(Line, OldTest, Arity, St) ->
+ ModernTest = list_to_atom("is_"++atom_to_list(OldTest)),
+ case is_local_function(St#lint.locals, {ModernTest, Arity}) of
+ true ->
+ add_error(Line, {obsolete_guard_overridden,OldTest}, St);
+ false ->
+ St
+ end.
+
%% keyword_warning(Line, Atom, State) -> State.
%% Add warning for atoms that will be reserved keywords in the future.
%% (Currently, no such keywords to warn for.)
@@ -3679,10 +3910,9 @@ check_format_string(Fmt) ->
extract_sequences(Fmt, []).
extract_sequences(Fmt, Need0) ->
- case string:chr(Fmt, $~) of
- 0 -> {ok,lists:reverse(Need0)}; %That's it
- Pos ->
- Fmt1 = string:substr(Fmt, Pos+1), %Skip ~
+ case string:find(Fmt, [$~]) of
+ nomatch -> {ok,lists:reverse(Need0)}; %That's it
+ [$~|Fmt1] ->
case extract_sequence(1, Fmt1, Need0) of
{ok,Need1,Rest} -> extract_sequences(Rest, Need1);
Error -> Error
@@ -3721,6 +3951,10 @@ extract_sequence(4, [$t, $p | Fmt], Need) ->
extract_sequence(5, [$p|Fmt], Need);
extract_sequence(4, [$t, $P | Fmt], Need) ->
extract_sequence(5, [$P|Fmt], Need);
+extract_sequence(4, [$t, $w | Fmt], Need) ->
+ extract_sequence(5, [$w|Fmt], Need);
+extract_sequence(4, [$t, $W | Fmt], Need) ->
+ extract_sequence(5, [$W|Fmt], Need);
extract_sequence(4, [$t, C | _Fmt], _Need) ->
{error,"invalid control ~t" ++ [C]};
extract_sequence(4, [$l, $p | Fmt], Need) ->
@@ -3799,8 +4033,7 @@ is_autoimport_suppressed(NoAutoSet,{Func,Arity}) ->
gb_sets:is_element({Func,Arity},NoAutoSet).
%% Predicate to find out if a function specific bif-clash suppression (old deprecated) is present
bif_clash_specifically_disabled(St,{F,A}) ->
- Nowarn = nowarn_function(nowarn_bif_clash, St#lint.compile),
- lists:member({F,A},Nowarn).
+ lists:member({F,A},St#lint.nowarn_bif_clash).
%% Predicate to find out if an autoimported guard_bif is not overriden in some way
%% Guard Bif without module name is disallowed if