summaryrefslogtreecommitdiff
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.erl485
1 files changed, 242 insertions, 243 deletions
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index b9b58d6576..55e53cad3d 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -2,7 +2,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
%% Do necessary checking of Erlang code.
-module(erl_lint).
+-feature(maybe_expr, enable).
-export([module/1,module/2,module/3,format_error/1]).
-export([exprs/2,exprs_opt/3,used_vars/2]). % Used from erl_eval.erl.
@@ -314,8 +315,6 @@ format_error({too_many_arguments,Arity}) ->
%% --- patterns and guards ---
format_error(illegal_pattern) -> "illegal pattern";
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 ~tw/~w is illegal in guard",
@@ -436,12 +435,8 @@ format_error({undefined_type, {TypeName, Arity}}) ->
io_lib:format("type ~tw~s undefined", [TypeName, gen_type_paren(Arity)]);
format_error({unused_type, {TypeName, Arity}}) ->
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, gen_type_paren(Arity)]);
-format_error({builtin_type, {TypeName, Arity}}) ->
- io_lib:format("type ~w~s is a builtin type; it cannot be redefined",
+format_error({redefine_builtin_type, {TypeName, Arity}}) ->
+ io_lib:format("local redefinition of built-in type: ~w~s",
[TypeName, gen_type_paren(Arity)]);
format_error({renamed_type, OldName, NewName}) ->
io_lib:format("type ~w() is now called ~w(); "
@@ -489,9 +484,6 @@ format_error({deprecated_builtin_type, {Name, Arity},
format_error({not_exported_opaque, {TypeName, Arity}}) ->
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 ~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}) ->
@@ -674,13 +666,19 @@ start(File, Opts) ->
true, Opts)},
{keyword_warning,
bool_option(warn_keywords, nowarn_keywords,
- false, Opts)}
+ false, Opts)},
+ {redefined_builtin_type,
+ bool_option(warn_redefined_builtin_type, nowarn_redefined_builtin_type,
+ true, Opts)},
+ {singleton_typevar,
+ bool_option(warn_singleton_typevar, nowarn_singleton_typevar,
+ true, Opts)}
],
Enabled1 = [Category || {Category,true} <- Enabled0],
Enabled = ordsets:from_list(Enabled1),
Calls = case ordsets:is_element(unused_function, Enabled) of
true ->
- maps:from_list([{{module_info,1},pseudolocals()}]);
+ #{{module_info,1} => pseudolocals()};
false ->
undefined
end,
@@ -1416,7 +1414,7 @@ check_unused_records(Forms, St0) ->
maps:remove(Used, Recs)
end, St1#lint.records, UsedRecords),
Unused = [{Name,Anno} ||
- {Name,{Anno,_Fields}} <- maps:to_list(URecs),
+ Name := {Anno,_Fields} <- URecs,
element(1, loc(Anno, St1)) =:= FirstFile],
foldl(fun ({N,Anno}, St) ->
add_warning(Anno, {unused_record, N}, St)
@@ -1749,10 +1747,9 @@ pattern({op,_Anno,'++',{string,_Ai,_S},R}, Vt, Old, St) ->
pattern({match,_Anno,Pat1,Pat2}, Vt0, Old, St0) ->
{Lvt, Lnew, St1} = pattern(Pat1, Vt0, Old, St0),
{Rvt, Rnew, St2} = pattern(Pat2, Vt0, Old, St1),
- St3 = reject_invalid_alias(Pat1, Pat2, Vt0, St2),
- {Vt1, St4} = vtmerge_pat(Lvt, Rvt, St3),
- {New, St5} = vtmerge_pat(Lnew, Rnew, St4),
- {Vt1, New, St5};
+ {Vt1, St3} = vtmerge_pat(Lvt, Rvt, St2),
+ {New, St4} = vtmerge_pat(Lnew, Rnew, St3),
+ {Vt1, New, St4};
%% Catch legal constant expressions, including unary +,-.
pattern(Pat, _Vt, _Old, St) ->
case is_pattern_expr(Pat) of
@@ -1779,101 +1776,6 @@ check_multi_field_init(Fs, Anno, Fields, St) ->
false -> St
end.
-%% 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_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_invalid_alias({bin,Anno,_}, {bin,_,_}, _, St) ->
- add_error(Anno, illegal_bin_pattern, St);
-reject_invalid_alias({map,_Anno,Ps1}, {map,_,Ps2}, Vt, St0) ->
- Fun = fun ({map_field_exact,_,{var,A,K},_V}, Sti) ->
- case is_var_bound(K,Vt) of
- true ->
- Sti;
- false ->
- add_error(A, {unbound_var,K}, Sti)
- end;
- ({map_field_exact,_A,_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 Recs of
- #{Name1 := {_Anno1,Fields1}, Name2 := {_Anno2,Fields2}} ->
- 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_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),
- FieldsA1 = rbia_fields(lists:reverse(FieldsA0), 0, []),
- FieldsB1 = rbia_fields(lists:reverse(FieldsB0), 0, []),
- FieldsA = sofs:relation(FieldsA1),
- PfsA = sofs:relation(PfsA1),
- A = sofs:join(FieldsA, 1, PfsA, 1),
- FieldsB = sofs:relation(FieldsB1),
- PfsB = sofs:relation(PfsB1),
- B = sofs:join(FieldsB, 1, PfsB, 1),
- C = sofs:join(A, 2, B, 2),
- D = sofs:projection({external,fun({_,_,P1,_,P2}) -> {P1,P2} end}, C),
- E = sofs:to_external(D),
- {Ps1,Ps2} = lists:unzip(E),
- reject_invalid_alias_list(Ps1, Ps2, Vt, St).
-
-rbia_field_vars(Fs) ->
- [{Name,Pat} || {record_field,_,{atom,_,Name},Pat} <- Fs].
-
-rbia_fields([{record_field,_,{atom,_,Name},_}|Fs], I, Acc) ->
- rbia_fields(Fs, I+1, [{Name,I}|Acc]);
-rbia_fields([_|Fs], I, Acc) ->
- rbia_fields(Fs, I+1, Acc);
-rbia_fields([], _, Acc) -> Acc.
-
%% is_pattern_expr(Expression) -> boolean().
%% Test if a general expression is a valid pattern expression.
@@ -1897,14 +1799,14 @@ is_pattern_expr_1({integer,_Anno,_I}) -> true;
is_pattern_expr_1({float,_Anno,_F}) -> true;
is_pattern_expr_1({atom,_Anno,_A}) -> true;
is_pattern_expr_1({tuple,_Anno,Es}) ->
- all(fun is_pattern_expr/1, Es);
+ all(fun is_pattern_expr_1/1, Es);
is_pattern_expr_1({nil,_Anno}) -> true;
is_pattern_expr_1({cons,_Anno,H,T}) ->
is_pattern_expr_1(H) andalso is_pattern_expr_1(T);
is_pattern_expr_1({op,_Anno,Op,A}) ->
erl_internal:arith_op(Op, 1) andalso is_pattern_expr_1(A);
is_pattern_expr_1({op,_Anno,Op,A1,A2}) ->
- erl_internal:arith_op(Op, 2) andalso all(fun is_pattern_expr/1, [A1,A2]);
+ erl_internal:arith_op(Op, 2) andalso all(fun is_pattern_expr_1/1, [A1,A2]);
is_pattern_expr_1(_Other) -> false.
pattern_map(Ps, Vt0, Old, St0) ->
@@ -2084,7 +1986,8 @@ bit_size_check(Anno, all, #bittype{type=Type}, St) ->
binary -> {all,St};
_ -> {unknown,add_error(Anno, illegal_bitsize, St)}
end;
-bit_size_check(Anno, Size, #bittype{type=Type,unit=Unit}, St) ->
+bit_size_check(Anno, Size, #bittype{type=Type,unit=Unit}, St)
+ when is_integer(Size), is_integer(Unit) ->
Sz = Unit * Size, %Total number of bits!
St2 = elemtype_check(Anno, Type, Sz, St),
{Sz,St2}.
@@ -2445,6 +2348,8 @@ expr({lc,_Anno,E,Qs}, Vt, St) ->
handle_comprehension(E, Qs, Vt, St);
expr({bc,_Anno,E,Qs}, Vt, St) ->
handle_comprehension(E, Qs, Vt, St);
+expr({mc,_Anno,E,Qs}, Vt, St) ->
+ handle_comprehension(E, Qs, Vt, St);
expr({tuple,_Anno,Es}, Vt, St) ->
expr_list(Es, Vt, St);
expr({map,_Anno,Es}, Vt, St) ->
@@ -2632,8 +2537,7 @@ expr({'catch',Anno,E}, Vt, St0) ->
{vtupdate(vtunsafe({'catch',Anno}, Evt, Vt), Evt),St};
expr({match,_Anno,P,E}, Vt, St0) ->
{Evt,St1} = expr(E, Vt, St0),
- {Pvt,Pnew,St2} = pattern(P, vtupdate(Evt, Vt), St1),
- St = reject_invalid_alias_expr(P, E, Vt, St2),
+ {Pvt,Pnew,St} = pattern(P, vtupdate(Evt, Vt), St1),
{vtupdate(Pnew, vtmerge(Evt, Pvt)),St};
expr({maybe_match,Anno,P,E}, Vt, St0) ->
expr({match,Anno,P,E}, Vt, St0);
@@ -2665,7 +2569,10 @@ expr({op,_Anno,_Op,L,R}, Vt, St) ->
expr_list([L,R], Vt, St); %They see the same variables
%% The following are not allowed to occur anywhere!
expr({remote,_Anno,M,_F}, _Vt, St) ->
- {[],add_error(erl_parse:first_anno(M), illegal_expr, St)}.
+ {[],add_error(erl_parse:first_anno(M), illegal_expr, St)};
+expr({ssa_check_when,_Anno,_WantedResult,_Args,_Tag,_Exprs}, _Vt, St) ->
+ {[], St}.
+
%% expr_list(Expressions, Variables, State) ->
%% {UsedVarTable,State}
@@ -2986,104 +2893,130 @@ type_def(Attr, Anno, TypeName, ProtoType, Args, St0) ->
not member(no_auto_import_types, St0#lint.compile) of
true ->
case is_obsolete_builtin_type(TypePair) of
- true -> StoreType(St0);
+ true ->
+ StoreType(St0);
false ->
- case is_newly_introduced_builtin_type(TypePair) of
- %% allow some types just for bootstrapping
- true ->
- Warn = {new_builtin_type, TypePair},
- St1 = add_warning(Anno, Warn, St0),
- StoreType(St1);
- false ->
- add_error(Anno, {builtin_type, TypePair}, St0)
- end
+ %% Starting from OTP 26, redefining built-in types
+ %% is allowed.
+ St1 = StoreType(St0),
+ warn_redefined_builtin_type(Anno, TypePair, St1)
end;
false ->
case is_map_key(TypePair, TypeDefs) of
true ->
add_error(Anno, {redefine_type, TypePair}, St0);
false ->
- St1 = case
- Attr =:= opaque andalso
- is_underspecified(ProtoType, Arity)
- of
- true ->
- Warn = {underspecified_opaque, TypePair},
- add_warning(Anno, Warn, St0);
- false -> St0
- end,
- StoreType(St1)
+ StoreType(St0)
end
end.
-is_underspecified({type,_,term,[]}, 0) -> true;
-is_underspecified({type,_,any,[]}, 0) -> true;
-is_underspecified(_ProtType, _Arity) -> false.
+warn_redefined_builtin_type(Anno, TypePair, #lint{compile=Opts}=St) ->
+ case is_warn_enabled(redefined_builtin_type, St) of
+ true ->
+ NoWarn = [Type ||
+ {nowarn_redefined_builtin_type, Type0} <- Opts,
+ Type <- lists:flatten([Type0])],
+ case lists:member(TypePair, NoWarn) of
+ true ->
+ St;
+ false ->
+ Warn = {redefine_builtin_type, TypePair},
+ add_warning(Anno, Warn, St)
+ end;
+ false ->
+ St
+ end.
check_type(Types, St) ->
- {SeenVars, St1} = check_type(Types, maps:new(), St),
+ {SeenVars, St1} = check_type_1(Types, maps:new(), St),
maps:fold(fun(Var, {seen_once, Anno}, AccSt) ->
case atom_to_list(Var) of
"_"++_ -> AccSt;
_ -> add_error(Anno, {singleton_typevar, Var}, AccSt)
end;
+ (Var, {seen_once_union, Anno}, AccSt) ->
+ case is_warn_enabled(singleton_typevar, AccSt) of
+ true ->
+ case atom_to_list(Var) of
+ "_"++_ -> AccSt;
+ _ -> add_warning(Anno, {singleton_typevar, Var}, AccSt)
+ end;
+ false ->
+ AccSt
+ end;
(_Var, seen_multiple, AccSt) ->
AccSt
end, St1, SeenVars).
-check_type({ann_type, _A, [_Var, Type]}, SeenVars, St) ->
- check_type(Type, SeenVars, St);
-check_type({remote_type, A, [{atom, _, Mod}, {atom, _, Name}, Args]},
+check_type_1({type, Anno, TypeName, Args}=Type, SeenVars, #lint{types=Types}=St) ->
+ TypePair = {TypeName,
+ if
+ is_list(Args) -> length(Args);
+ true -> 0
+ end},
+ case is_map_key(TypePair, Types) of
+ true ->
+ check_type_2(Type, SeenVars, used_type(TypePair, Anno, St));
+ false ->
+ check_type_2(Type, SeenVars, St)
+ end;
+check_type_1(Types, SeenVars, St) ->
+ check_type_2(Types, SeenVars, St).
+
+check_type_2({ann_type, _A, [_Var, Type]}, SeenVars, St) ->
+ check_type_1(Type, SeenVars, St);
+check_type_2({remote_type, A, [{atom, _, Mod}, {atom, _, Name}, Args]},
SeenVars, St00) ->
St0 = check_module_name(Mod, A, St00),
St = deprecated_type(A, Mod, Name, Args, St0),
CurrentMod = St#lint.module,
case Mod =:= CurrentMod of
- true -> check_type({user_type, A, Name, Args}, SeenVars, St);
+ true -> check_type_2({user_type, A, Name, Args}, SeenVars, St);
false ->
lists:foldl(fun(T, {AccSeenVars, AccSt}) ->
- check_type(T, AccSeenVars, AccSt)
+ check_type_1(T, AccSeenVars, AccSt)
end, {SeenVars, St}, Args)
end;
-check_type({integer, _A, _}, SeenVars, St) -> {SeenVars, St};
-check_type({atom, _A, _}, SeenVars, St) -> {SeenVars, St};
-check_type({var, _A, '_'}, SeenVars, St) -> {SeenVars, St};
-check_type({var, A, Name}, SeenVars, St) ->
+check_type_2({integer, _A, _}, SeenVars, St) -> {SeenVars, St};
+check_type_2({atom, _A, _}, SeenVars, St) -> {SeenVars, St};
+check_type_2({var, _A, '_'}, SeenVars, St) -> {SeenVars, St};
+check_type_2({var, A, Name}, SeenVars, St) ->
NewSeenVars =
case maps:find(Name, SeenVars) of
{ok, {seen_once, _}} -> maps:put(Name, seen_multiple, SeenVars);
+ {ok, {seen_once_union, _}} -> maps:put(Name, seen_multiple, SeenVars);
{ok, seen_multiple} -> SeenVars;
error -> maps:put(Name, {seen_once, A}, SeenVars)
end,
{NewSeenVars, St};
-check_type({type, A, bool, []}, SeenVars, St) ->
+check_type_2({type, A, bool, []}, SeenVars, St) ->
{SeenVars, add_warning(A, {renamed_type, bool, boolean}, St)};
-check_type({type, A, 'fun', [Dom, Range]}, SeenVars, St) ->
+check_type_2({type, A, 'fun', [Dom, Range]}, SeenVars, St) ->
St1 =
case Dom of
{type, _, product, _} -> St;
{type, _, any} -> St;
_ -> add_error(A, {type_syntax, 'fun'}, St)
end,
- check_type({type, nowarn(), product, [Dom, Range]}, SeenVars, St1);
-check_type({type, A, range, [From, To]}, SeenVars, St) ->
+ check_type_2({type, nowarn(), product, [Dom, Range]}, SeenVars, St1);
+check_type_2({type, A, range, [From, To]}, SeenVars, St) ->
St1 =
case {erl_eval:partial_eval(From), erl_eval:partial_eval(To)} of
{{integer, _, X}, {integer, _, Y}} when X < Y -> St;
_ -> add_error(A, {type_syntax, range}, St)
end,
{SeenVars, St1};
-check_type({type, _A, map, any}, SeenVars, St) ->
+check_type_2({type, _A, map, any}, SeenVars, St) ->
{SeenVars, St};
-check_type({type, _A, map, Pairs}, SeenVars, St) ->
+check_type_2({type, _A, map, Pairs}, SeenVars, St) ->
lists:foldl(fun(Pair, {AccSeenVars, AccSt}) ->
- check_type(Pair, AccSeenVars, AccSt)
+ check_type_2(Pair, AccSeenVars, AccSt)
end, {SeenVars, St}, Pairs);
-check_type({type, _A, map_field_assoc, [Dom, Range]}, SeenVars, St) ->
- check_type({type, nowarn(), product, [Dom, Range]}, SeenVars, St);
-check_type({type, _A, tuple, any}, SeenVars, St) -> {SeenVars, St};
-check_type({type, _A, any}, SeenVars, St) -> {SeenVars, St};
-check_type({type, A, binary, [Base, Unit]}, SeenVars, St) ->
+check_type_2({type, _A, map_field_assoc, [Dom, Range]}, SeenVars, St) ->
+ check_type_2({type, nowarn(), product, [Dom, Range]}, SeenVars, St);
+check_type_2({type, _A, tuple, any}, SeenVars, St) -> {SeenVars, St};
+check_type_2({type, _A, any}, SeenVars, St) -> {SeenVars, St};
+check_type_2({type, A, binary, [Base, Unit]}, SeenVars, St) ->
St1 =
case {erl_eval:partial_eval(Base), erl_eval:partial_eval(Unit)} of
{{integer, _, BaseVal},
@@ -3091,20 +3024,43 @@ check_type({type, A, binary, [Base, Unit]}, SeenVars, St) ->
_ -> add_error(A, {type_syntax, binary}, St)
end,
{SeenVars, St1};
-check_type({type, A, record, [Name|Fields]}, SeenVars, St) ->
+check_type_2({type, A, record, [Name|Fields]}, SeenVars, St) ->
case Name of
{atom, _, Atom} ->
St1 = used_record(Atom, St),
check_record_types(A, Atom, Fields, SeenVars, St1);
_ -> {SeenVars, add_error(A, {type_syntax, record}, St)}
end;
-check_type({type, _A, Tag, Args}, SeenVars, St) when Tag =:= product;
- Tag =:= union;
- Tag =:= tuple ->
+check_type_2({type, _A, Tag, Args}=_F, SeenVars, St) when Tag =:= product;
+ Tag =:= tuple ->
lists:foldl(fun(T, {AccSeenVars, AccSt}) ->
- check_type(T, AccSeenVars, AccSt)
- end, {SeenVars, St}, Args);
-check_type({type, Anno, TypeName, Args}, SeenVars, St) ->
+ check_type_1(T, AccSeenVars, AccSt)
+ end, {SeenVars, St}, Args);
+check_type_2({type, _A, union, Args}=_F, SeenVars0, St) ->
+ lists:foldl(fun(T, {AccSeenVars0, AccSt}) ->
+ {SeenVars1, St0} = check_type_1(T, SeenVars0, AccSt),
+ AccSeenVars = maps:merge_with(
+ fun (K, {seen_once, Anno}, {seen_once, _}) ->
+ case SeenVars0 of
+ #{K := _} ->
+ %% Unused outside of this union.
+ {seen_once, Anno};
+ #{} ->
+ {seen_once_union, Anno}
+ end;
+ (_K, {seen_once, Anno}, {seen_once_union, _}) ->
+ {seen_once_union, Anno};
+ (_K, {seen_once_union, _}=R, {seen_once, _}) -> R;
+ (_K, {seen_once_union, _}=R, {seen_once_union, _}) -> R;
+ (_K, {seen_once_union, _}, Else) -> Else;
+ (_K, {seen_once, _}, Else) -> Else;
+ (_K, Else, {seen_once_union, _}) -> Else;
+ (_K, Else, {seen_once, _}) -> Else;
+ (_K, Else1, _Else2) -> Else1
+ end, AccSeenVars0, SeenVars1),
+ {AccSeenVars, St0}
+ end, {SeenVars0, St}, Args);
+check_type_2({type, Anno, TypeName, Args}, SeenVars, St) ->
#lint{module = Module, types=Types} = St,
Arity = length(Args),
TypePair = {TypeName, Arity},
@@ -3120,20 +3076,26 @@ check_type({type, Anno, TypeName, Args}, SeenVars, St) ->
Tag = deprecated_builtin_type,
W = {Tag, TypePair, Replacement, Rel},
add_warning(Anno, W, St)
- end;
- _ -> St
- end,
- check_type({type, nowarn(), product, Args}, SeenVars, St1);
-check_type({user_type, A, TypeName, Args}, SeenVars, St) ->
+ end;
+ _ ->
+ case is_default_type(TypePair) of
+ true ->
+ used_type(TypePair, Anno, St);
+ false ->
+ St
+ end
+ end,
+ check_type_2({type, nowarn(), product, Args}, SeenVars, St1);
+check_type_2({user_type, A, TypeName, Args}, SeenVars, St) ->
Arity = length(Args),
TypePair = {TypeName, Arity},
St1 = used_type(TypePair, A, St),
lists:foldl(fun(T, {AccSeenVars, AccSt}) ->
- check_type(T, AccSeenVars, AccSt)
+ check_type_1(T, AccSeenVars, AccSt)
end, {SeenVars, St1}, Args);
-check_type([{typed_record_field,Field,_T}|_], SeenVars, St) ->
+check_type_2([{typed_record_field,Field,_T}|_], SeenVars, St) ->
{SeenVars, add_error(element(2, Field), old_abstract_code, St)};
-check_type(I, SeenVars, St) ->
+check_type_2(I, SeenVars, St) ->
case erl_eval:partial_eval(I) of
{integer,_A,_Integer} -> {SeenVars, St};
_Other ->
@@ -3168,7 +3130,7 @@ check_record_types([{type, _, field_type, [{atom, Anno, FName}, Type]}|Left],
false -> St1
end,
%% Check Type
- {NewSeenVars, St3} = check_type(Type, SeenVars, St2),
+ {NewSeenVars, St3} = check_type_2(Type, SeenVars, St2),
NewSeenFields = ordsets:add_element(FName, SeenFields),
check_record_types(Left, Name, DefFields, NewSeenVars, St3, NewSeenFields);
check_record_types([], _Name, _DefFields, SeenVars, St, _SeenFields) ->
@@ -3184,8 +3146,6 @@ used_type(TypePair, Anno, #lint{usage = Usage, file = File} = St) ->
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.
@@ -3406,7 +3366,7 @@ check_unused_types_1(Forms, #lint{types=Ts}=St) ->
reached_types(#lint{usage = Usage}) ->
Es = [{From, {type, To}} ||
- {To, UsedTs} <- maps:to_list(Usage#usage.used_types),
+ To := UsedTs <- Usage#usage.used_types,
#used_type{at = From} <- UsedTs],
Initial = initially_reached_types(Es),
G = sofs:family_to_digraph(sofs:rel2fam(sofs:relation(Es))),
@@ -3480,12 +3440,12 @@ is_function_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_match,no_opaque,no_fail_call,no_contracts,no_unknown,
no_behaviours,no_undefined_callbacks,unmatched_returns,
error_handling,race_conditions,no_missing_calls,
specdiffs,overspecs,underspecs,unknown,
no_underspecs,extra_return,no_extra_return,
- missing_return,no_missing_return
+ missing_return,no_missing_return,overlapping_contract
]).
%% try_catch_clauses(Scs, Ccs, In, ImportVarTable, State) ->
@@ -3612,7 +3572,7 @@ icrt_export([], _, _, _, Acc) ->
handle_comprehension(E, Qs, Vt0, St0) ->
{Vt1, Uvt, St1} = lc_quals(Qs, Vt0, St0),
- {Evt,St2} = expr(E, Vt1, St1),
+ {Evt,St2} = comprehension_expr(E, Vt1, St1),
Vt2 = vtupdate(Evt, Vt1),
%% Shadowed global variables.
{_,St3} = check_old_unused_vars(Vt2, Uvt, St2),
@@ -3629,6 +3589,11 @@ handle_comprehension(E, Qs, Vt0, St0) ->
Vt = vt_no_unsafe(vt_no_unused(Vt4)),
{Vt, St}.
+comprehension_expr({map_field_assoc,_,K,V}, Vt0, St0) ->
+ expr_list([K,V], Vt0, St0);
+comprehension_expr(E, Vt, St) ->
+ expr(E, Vt, St).
+
%% lc_quals(Qualifiers, ImportVarTable, State) ->
%% {VarTable,ShadowedVarTable,State}
%% Test list comprehension qualifiers, return all variables. Allow
@@ -3652,6 +3617,9 @@ lc_quals([{b_generate,_Anno,P,E} | Qs], Vt0, Uvt0, St0) ->
St1 = handle_bitstring_gen_pat(P,St0),
{Vt,Uvt,St} = handle_generator(P,E,Vt0,Uvt0,St1),
lc_quals(Qs, Vt, Uvt, St);
+lc_quals([{m_generate,_Anno,P,E} | Qs], Vt0, Uvt0, St0) ->
+ {Vt,Uvt,St} = handle_generator(P,E,Vt0,Uvt0,St0),
+ lc_quals(Qs, Vt, Uvt, St);
lc_quals([F|Qs], Vt, Uvt, St0) ->
Info = is_guard_test2_info(St0),
{Fvt,St1} = case is_guard_test2(F, Info) of
@@ -3673,7 +3641,7 @@ handle_generator(P,E,Vt,Uvt,St0) ->
%% Forget variables local to E immediately.
Vt1 = vtupdate(vtold(Evt, Vt), Vt),
{_, St2} = check_unused_vars(Evt, Vt, St1),
- {Pvt,Pnew,St3} = pattern(P, Vt1, [], St2),
+ {Pvt,Pnew,St3} = comprehension_pattern(P, Vt1, St2),
%% Have to keep fresh variables separated from used variables somehow
%% in order to handle for example X = foo(), [X || <<X:X>> <- bar()].
%% 1 2 2 1
@@ -3685,6 +3653,11 @@ handle_generator(P,E,Vt,Uvt,St0) ->
Vt3 = vtupdate(vtsubtract(Vt2, Pnew), Pnew),
{Vt3,NUvt,St5}.
+comprehension_pattern({map_field_exact,_,K,V}, Vt, St) ->
+ pattern_list([K,V], Vt, [], St);
+comprehension_pattern(P, Vt, St) ->
+ pattern(P, Vt, [], St).
+
handle_bitstring_gen_pat({bin,_,Segments=[_|_]},St) ->
case lists:last(Segments) of
{bin_element,Anno,_,default,Flags} when is_list(Flags) ->
@@ -3957,14 +3930,6 @@ warn_unused_vars(U, Vt, St0) ->
UVt = map(fun ({V,{State,_,As}}) -> {V,{State,used,As}} 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
@@ -4252,19 +4217,18 @@ keyword_warning(Anno, Atom, St) ->
%% Add warning for bad calls to io:fwrite/format functions.
format_function(DefAnno, M, F, As, St) ->
- case is_format_function(M, F) of
- true ->
- case St#lint.warn_format of
- Lev when Lev > 0 ->
- case check_format_1(As) of
- {warn,Level,Fmt,Fas} when Level =< Lev ->
- add_warning(DefAnno, {format_error,{Fmt,Fas}}, St);
- {warn,Level,Anno,Fmt,Fas} when Level =< Lev ->
- add_warning(Anno, {format_error,{Fmt,Fas}}, St);
- _ -> St
- end;
- _Lev -> St
- end;
+ maybe
+ true ?= is_format_function(M, F),
+ Lev = St#lint.warn_format,
+ true ?= Lev > 0,
+ case check_format_1(As) of
+ {warn,Level,Fmt,Fas} when Level =< Lev ->
+ add_warning(DefAnno, {format_error,{Fmt,Fas}}, St);
+ {warn,Level,Anno,Fmt,Fas} when Level =< Lev ->
+ add_warning(Anno, {format_error,{Fmt,Fas}}, St);
+ _ -> St
+ end
+ else
false -> St
end.
@@ -4383,9 +4347,11 @@ extract_sequences(Fmt, Need0) ->
end
end.
-extract_sequence(1, [$-,C|Fmt], Need) when C >= $0, C =< $9 ->
+extract_sequence(1, [$-,C|Fmt], Need)
+ when is_integer(C), C >= $0, C =< $9 ->
extract_sequence_digits(1, Fmt, Need);
-extract_sequence(1, [C|Fmt], Need) when C >= $0, C =< $9 ->
+extract_sequence(1, [C|Fmt], Need)
+ when is_integer(C), C >= $0, C =< $9 ->
extract_sequence_digits(1, Fmt, Need);
extract_sequence(1, [$-,$*|Fmt], Need) ->
extract_sequence(2, Fmt, [int|Need]);
@@ -4394,7 +4360,8 @@ extract_sequence(1, [$*|Fmt], Need) ->
extract_sequence(1, Fmt, Need) ->
extract_sequence(2, Fmt, Need);
-extract_sequence(2, [$.,C|Fmt], Need) when C >= $0, C =< $9 ->
+extract_sequence(2, [$.,C|Fmt], Need)
+ when is_integer(C), C >= $0, C =< $9 ->
extract_sequence_digits(2, Fmt, Need);
extract_sequence(2, [$.,$*|Fmt], Need) ->
extract_sequence(3, Fmt, [int|Need]);
@@ -4410,36 +4377,23 @@ extract_sequence(3, [$.,_|Fmt], Need) ->
extract_sequence(3, Fmt, Need) ->
extract_sequence(4, Fmt, Need);
-extract_sequence(4, [$t, $l | Fmt], Need) ->
- extract_sequence(4, [$l, $t | Fmt], Need);
-extract_sequence(4, [$t, $c | Fmt], Need) ->
- extract_sequence(5, [$c|Fmt], Need);
-extract_sequence(4, [$t, $s | Fmt], Need) ->
- extract_sequence(5, [$s|Fmt], Need);
-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) ->
- extract_sequence(5, [$p|Fmt], Need);
-extract_sequence(4, [$l, $t, $p | Fmt], Need) ->
- extract_sequence(5, [$p|Fmt], Need);
-extract_sequence(4, [$l, $P | Fmt], Need) ->
- extract_sequence(5, [$P|Fmt], Need);
-extract_sequence(4, [$l, $t, $P | Fmt], Need) ->
- extract_sequence(5, [$P|Fmt], Need);
-extract_sequence(4, [$l, $t, C | _Fmt], _Need) ->
- {error,"invalid control ~lt" ++ [C]};
-extract_sequence(4, [$l, C | _Fmt], _Need) ->
- {error,"invalid control ~l" ++ [C]};
-extract_sequence(4, Fmt, Need) ->
- extract_sequence(5, Fmt, Need);
+extract_sequence(4, Fmt0, Need) ->
+ case extract_modifiers(Fmt0, []) of
+ {error, _} = Error ->
+ Error;
+ {[C|Fmt], Modifiers} ->
+ maybe
+ ok ?= check_modifiers(C, Modifiers),
+ case ordsets:is_element($K, Modifiers) of
+ true ->
+ extract_sequence(5, [C|Fmt], ['fun'|Need]);
+ false ->
+ extract_sequence(5, [C|Fmt], Need)
+ end
+ end;
+ {[], _} ->
+ extract_sequence(5, [], Need)
+ end;
extract_sequence(5, [C|Fmt], Need0) ->
case control_type(C, Need0) of
@@ -4448,11 +4402,56 @@ extract_sequence(5, [C|Fmt], Need0) ->
end;
extract_sequence(_, [], _Need) -> {error,"truncated"}.
-extract_sequence_digits(Fld, [C|Fmt], Need) when C >= $0, C =< $9 ->
+extract_sequence_digits(Fld, [C|Fmt], Need)
+ when is_integer(C), C >= $0, C =< $9 ->
extract_sequence_digits(Fld, Fmt, Need);
extract_sequence_digits(Fld, Fmt, Need) ->
extract_sequence(Fld+1, Fmt, Need).
+extract_modifiers([C|Fmt], Modifiers0) ->
+ case is_modifier(C) of
+ true ->
+ case ordsets:add_element(C, Modifiers0) of
+ Modifiers0 ->
+ {error, "repeated modifier " ++ [C]};
+ Modifiers ->
+ extract_modifiers(Fmt, Modifiers)
+ end;
+ false ->
+ {[C|Fmt], Modifiers0}
+ end;
+extract_modifiers([], Modifiers) ->
+ {[], Modifiers}.
+
+check_modifiers(C, Modifiers) ->
+ maybe
+ ok ?= check_modifiers_1("l", Modifiers, C, "Pp"),
+ ok ?= check_modifiers_1("lt", Modifiers, C, "cPpsWw"),
+ ok ?= check_modifiers_1("Kk", Modifiers, C, "PpWw")
+ end.
+
+check_modifiers_1(M, Modifiers, C, Cs) ->
+ case ordsets:intersection(ordsets:from_list(M), Modifiers) of
+ [_]=Mod ->
+ case lists:member(C, Cs) of
+ true ->
+ ok;
+ false ->
+ {error, "invalid modifier/control combination ~" ++
+ Mod ++ [C]}
+ end;
+ [] ->
+ ok;
+ [_,_]=M ->
+ {error, "conflicting modifiers ~" ++ M ++ [C]}
+ end.
+
+is_modifier($k) -> true;
+is_modifier($K) -> true;
+is_modifier($l) -> true;
+is_modifier($t) -> true;
+is_modifier(_) -> false.
+
control_type($~, Need) -> Need;
control_type($c, Need) -> [int|Need];
control_type($f, Need) -> [float|Need];