From 5de101cd72b401fd4563e31fa54bcc8e7501de12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Wed, 3 May 2023 07:08:07 +0200 Subject: 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 --- lib/stdlib/src/erl_lint.erl | 48 ++++++++--- lib/stdlib/test/erl_lint_SUITE.erl | 168 ++++++++++++++++++++----------------- 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. -- cgit v1.2.1