summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjörn Gustavsson <bjorn@erlang.org>2023-05-03 07:08:07 +0200
committerBjörn Gustavsson <bjorn@erlang.org>2023-05-03 09:33:50 +0200
commit5de101cd72b401fd4563e31fa54bcc8e7501de12 (patch)
treee49424313f5c4866259662eb58415d66236cbf04
parente1c12fe2746c319cb9899f620569fb914a54cb8b (diff)
downloaderlang-5de101cd72b401fd4563e31fa54bcc8e7501de12.tar.gz
Merely warn for unbound variables in union types
In #6864, it became an error to have unbound variables in a union. Before that, unbound variables in unions were silently ignored. For compatibility with code that used to compile before Erlang/OTP 26, change the error to a warning. The warning can be disabled with the `nowarn_singleton_typevar` option. It is expected that unbound variables in unions will again become an error in Erlang/OTP 27. Closes #7116
-rw-r--r--lib/stdlib/src/erl_lint.erl48
-rw-r--r--lib/stdlib/test/erl_lint_SUITE.erl168
2 files changed, 130 insertions, 86 deletions
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index a4833f940b..55e53cad3d 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -669,6 +669,9 @@ start(File, 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],
@@ -2931,6 +2934,16 @@ check_type(Types, St) ->
"_"++_ -> 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).
@@ -2971,6 +2984,7 @@ 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,
@@ -3022,16 +3036,30 @@ check_type_2({type, _A, Tag, Args}=_F, SeenVars, St) when Tag =:= product;
lists:foldl(fun(T, {AccSeenVars, AccSt}) ->
check_type_1(T, AccSeenVars, AccSt)
end, {SeenVars, St}, Args);
-check_type_2({type, _A, union, Args}=_F, SeenVars, St) ->
- lists:foldl(fun(T, {AccSeenVars, AccSt}) ->
- {SeenVars0, St0} = check_type_1(T, SeenVars, AccSt),
- UpdatedSeenVars = maps:merge_with(fun (_K, {seen_once, _}, {seen_once, _}=R) -> R;
- (_K, {seen_once, _}, Else) -> Else;
- (_K, Else, {seen_once, _}) -> Else;
- (_K, Else1, _Else2) -> Else1
- end, SeenVars0, AccSeenVars),
- {UpdatedSeenVars, St0}
- 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),
diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl
index 834eb23335..81bc3e9a0d 100644
--- a/lib/stdlib/test/erl_lint_SUITE.erl
+++ b/lib/stdlib/test/erl_lint_SUITE.erl
@@ -907,105 +907,121 @@ unused_import(Config) when is_list(Config) ->
%% Test singleton type variables
singleton_type_var_errors(Config) when is_list(Config) ->
- Ts = [ {singleton_error1
- , <<"-spec test_singleton_typevars_in_union(Opts) -> term() when
+ Ts = [{singleton_error1,
+ <<"-spec test_singleton_typevars_in_union(Opts) -> term() when
Opts :: {ok, Unknown} | {error, Unknown}.
test_singleton_typevars_in_union(_) ->
error.
- ">>
- , []
- , { errors
- , [{{2,36},erl_lint,{singleton_typevar,'Unknown'}}]
- , []
- }
- }
- , { singleton_error2
- , <<"-spec test_singleton_list_typevars_in_union([Opts]) -> term() when
+ ">>,
+ [],
+ {warnings,[{{2,36},erl_lint,{singleton_typevar,'Unknown'}}]}},
+
+ {singleton_error2,
+ <<"-spec test_singleton_list_typevars_in_union([Opts]) -> term() when
Opts :: {ok, Unknown} | {error, Unknown}.
test_singleton_list_typevars_in_union(_) ->
- error.">>
- , []
- , { errors
- , [{{2,36},erl_lint,{singleton_typevar,'Unknown'}}]
- , []
- }
- }
- , { singleton_error3
- , <<"-spec test_singleton_list_typevars_in_list([Opts]) -> term() when
+ error.">>,
+ [],
+ {warnings,[{{2,36},erl_lint,{singleton_typevar,'Unknown'}}]}},
+
+ {singleton_error3,
+ <<"-spec test_singleton_list_typevars_in_list([Opts]) -> term() when
Opts :: {ok, Unknown}.
test_singleton_list_typevars_in_list(_) ->
- error.">>
- , []
- , { errors
- , [{{2,36},erl_lint,{singleton_typevar,'Unknown'}}]
- , []
- }
- }
- , { singleton_error4
- , <<"-spec test_singleton_list_typevars_in_list_with_type_subst([{ok, Unknown}]) -> term().
+ error.">>,
+ [],
+ {errors,
+ [{{2,36},erl_lint,{singleton_typevar,'Unknown'}}],[]}},
+
+ {singleton_error4,
+ <<"-spec test_singleton_list_typevars_in_list_with_type_subst([{ok, Unknown}]) -> term().
test_singleton_list_typevars_in_list_with_type_subst(_) ->
- error.">>
- , []
- , { errors
- , [{{1,86},erl_lint,{singleton_typevar,'Unknown'}}]
- , []
- }
- }
- , { singleton_error5
- , <<"-spec test_singleton_buried_typevars_in_union(Opts) -> term() when
+ error.">>,
+ [],
+ {errors,[{{1,86},erl_lint,{singleton_typevar,'Unknown'}}],[]}},
+
+ {singleton_error5,
+ <<"-spec test_singleton_buried_typevars_in_union(Opts) -> term() when
Opts :: {ok, Foo} | {error, Foo},
Foo :: {true, X} | {false, X}.
test_singleton_buried_typevars_in_union(_) ->
- error.">>
- , []
- , { errors
- , [{{3,38},erl_lint,{singleton_typevar,'X'}}]
- , []
- }
- }
- , { singleton_error6
- , <<"-spec test_multiple_subtypes_to_same_typevar(Opts) -> term() when
+ error.">>,
+ [],
+ {warnings,[{{3,38},erl_lint,{singleton_typevar,'X'}}]}},
+
+ {singleton_error6,
+ <<"-spec test_multiple_subtypes_to_same_typevar(Opts) -> term() when
Opts :: {Foo, Bar} | Y,
Foo :: X,
Bar :: X,
Y :: Z.
test_multiple_subtypes_to_same_typevar(_) ->
- error.">>
- , []
- , { errors
- , [{{5,31},erl_lint,{singleton_typevar,'Z'}}]
- , []
- }
- }
- , { singleton_error7
- , <<"-spec test_duplicate_non_terminal_var_in_union(Opts) -> term() when
+ error.">>,
+ [],
+ {errors,[{{5,31},erl_lint,{singleton_typevar,'Z'}}],[]}},
+
+ {singleton_error7,
+ <<"-spec test_duplicate_non_terminal_var_in_union(Opts) -> term() when
Opts :: {ok, U, U} | {error, U, U},
U :: Foo.
test_duplicate_non_terminal_var_in_union(_) ->
- error.">>
- , []
- , { errors
- , [{{3,31},erl_lint,{singleton_typevar,'Foo'}}]
- , []
- }
- }
- , { singleton_ok1
- , <<"-spec test_multiple_occurrences_singleton(Opts) -> term() when
+ error.">>,
+ [],
+ {errors,[{{3,31},erl_lint,{singleton_typevar,'Foo'}}],[]}},
+
+ {singleton_error8,
+ <<"-spec test_unused_outside_union(Opts) -> term() when
+ Unused :: Unknown,
+ A :: Unknown,
+ Opts :: {Unknown | A}.
+ test_unused_outside_union(_) ->
+ error.">>,
+ [],
+ {errors,[{{2,21},erl_lint,{singleton_typevar,'Unused'}}],[]}},
+
+ {singleton_disabled_warning,
+ <<"-spec test_singleton_typevars_in_union(Opts) -> term() when
+ Opts :: {ok, Unknown} | {error, Unknown}.
+ test_singleton_typevars_in_union(_) ->
+ error.
+ ">>,
+ [nowarn_singleton_typevar],
+ []},
+
+ {singleton_ok1,
+ <<"-spec test_multiple_occurrences_singleton(Opts) -> term() when
Opts :: {Foo, Foo}.
test_multiple_occurrences_singleton(_) ->
- ok.">>
- , []
- , []
- }
- , { singleton_ok2
- , <<"-spec id(X) -> X.
+ ok.">>,
+ [],
+ []},
+
+ {singleton_ok2,
+ <<"-spec id(X) -> X.
id(X) ->
- X.">>
- , []
- , []
- }
+ X.">>,
+ [],
+ []},
+
+ {singleton_ok3,
+ <<"-spec ok(Opts) -> term() when
+ Opts :: {Unknown, {ok, Unknown} | {error, Unknown}}.
+ ok(_) ->
+ error.">>,
+ [],
+ []},
+
+ {singleton_ok4,
+ <<"-spec ok(Opts) -> term() when
+ Union :: {ok, Unknown} | {error, Unknown},
+ Opts :: {{tag, Unknown} | Union}.
+ ok(_) ->
+ error.">>,
+ [],
+ []}
+
+ ],
- ],
[] = run(Config, Ts),
ok.