File 4391-Allow-local-redefinition-of-built-in-types.patch of Package erlang

From 4d08fc95830b650d03fc86c7f29f5a5f043a972b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org>
Date: Fri, 16 Sep 2022 13:45:22 +0200
Subject: [PATCH] Allow local redefinition of built-in types

If a module defines a type and a new built-in type is added with the
same name, Dialyzer will allow the redefinition of that particular
release for one release only. That makes it difficult to write code
that is supposed to work for multiple releases of OTP.

For example, the OTP 24 release introduced the `nonempty_binary`
type. Modules that had their own definition would continue to work in
OTP 24 but not in OTP 25.

Always allow the name of a built-in type to be reused locally, in the
same way that a definition of a function with the same name as a BIF
will take precedence over the BIF. Starting from OTP 25, the built-in
types belong to the erlang module, so it will always be possible to
get the built-in definition if needed by prefixing the name with
`erlang:`.

Closes #6132
---
 lib/compiler/doc/src/compile.xml              |  18 ++-
 lib/dialyzer/src/dialyzer_utils.erl           |  43 +++++-
 .../results/redefine_builtin_type             |   0
 .../results/redefine_builtins                 |   5 +
 .../src/redefine_builtin_type.erl             |  24 ++++
 .../src/redefine_builtins/a.erl               |  14 ++
 .../src/redefine_builtins/b.erl               |   6 +
 lib/stdlib/src/erl_lint.erl                   | 134 +++++++++++-------
 lib/stdlib/test/erl_lint_SUITE.erl            | 105 +++++++++++---
 system/doc/reference_manual/typespec.xml      |  32 ++++-
 10 files changed, 301 insertions(+), 80 deletions(-)
 create mode 100644 lib/dialyzer/test/small_SUITE_data/results/redefine_builtin_type
 create mode 100644 lib/dialyzer/test/small_SUITE_data/results/redefine_builtins
 create mode 100644 lib/dialyzer/test/small_SUITE_data/src/redefine_builtin_type.erl
 create mode 100644 lib/dialyzer/test/small_SUITE_data/src/redefine_builtins/a.erl
 create mode 100644 lib/dialyzer/test/small_SUITE_data/src/redefine_builtins/b.erl

diff --git a/lib/compiler/doc/src/compile.xml b/lib/compiler/doc/src/compile.xml
index 5828fc6a17..06e3d70fa3 100644
--- a/lib/compiler/doc/src/compile.xml
+++ b/lib/compiler/doc/src/compile.xml
@@ -826,7 +826,23 @@ module.beam: module.erl \
                 (or contract) for an exported or unexported function is not
                 given. Use this option to turn on this kind of warning.</p>
           </item>
-        </taglist>
+
+          <tag><c>nowarn_redefined_builtin_type</c></tag>
+          <item>
+            <p>By default, a warning is emitted when a built-in type
+            is locally redefined. Use this option to turn off this
+            kind of warning.</p>
+          </item>
+
+          <tag><c>{nowarn_redefined_builtin_type, Types}</c></tag>
+          <item>
+            <p>By default, a warning is emitted when a built-in type
+            is locally redefined. Use this option to turn off this
+            kind of warning for the types in <c>Types</c>, where
+            <c>Types</c> is a tuple <c>{TypeName,Arity}</c> or a list
+            of such tuples.</p>
+          </item>
+</taglist>
 
 	<p>Other kinds of warnings are <em>opportunistic
 	warnings</em>. They are generated when the compiler happens to
diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl
index a3726b1bc8..c0bbf49625 100644
--- a/lib/dialyzer/src/dialyzer_utils.erl
+++ b/lib/dialyzer/src/dialyzer_utils.erl
@@ -552,14 +552,53 @@ get_spec_info([], SpecMap, CallbackMap,
   {ok, SpecMap, CallbackMap}.
 
 core_to_attr_tuples(Core) ->
-  [{cerl:concrete(Key), get_core_location(cerl:get_ann(Key)), cerl:concrete(Value)} ||
-   {Key, Value} <- cerl:module_attrs(Core)].
+  As = [{cerl:concrete(Key), get_core_location(cerl:get_ann(Key)), cerl:concrete(Value)} ||
+         {Key, Value} <- cerl:module_attrs(Core)],
+  case cerl:concrete(cerl:module_name(Core)) of
+    erlang ->
+      As;
+    _ ->
+      %% Starting from Erlang/OTP 26, locally defining a type having
+      %% the same name as a built-in type is allowed. Change the tag
+      %% from `type` to `user_type` for all such redefinitions.
+      massage_forms(As, sets:new([{version, 2}]))
+  end.
 
 get_core_location([L | _As]) when is_integer(L) -> L;
 get_core_location([{L, C} | _As]) when is_integer(L), is_integer(C) -> {L, C};
 get_core_location([_ | As]) -> get_core_location(As);
 get_core_location([]) -> undefined.
 
+massage_forms([{type, Loc, [{Name, Type0, Args}]} | T], Defs0) ->
+  Type = massage_type(Type0, Defs0),
+  Defs = sets:add_element({Name, length(Args)}, Defs0),
+  [{type, Loc, [{Name, Type, Args}]} | massage_forms(T, Defs)];
+massage_forms([{spec, Loc, [{Name, [Type0]}]} | T], Defs) ->
+  Type = massage_type(Type0, Defs),
+  [{spec, Loc, [{Name, [Type]}]} | massage_forms(T, Defs)];
+massage_forms([H | T], Defs) ->
+  [H | massage_forms(T, Defs)];
+massage_forms([], _Defs) ->
+  [].
+
+massage_type({type, Loc, Name, Args0}, Defs) when is_list(Args0) ->
+  case sets:is_element({Name, length(Args0)}, Defs) of
+    true ->
+      %% This name for a built-in type has been overriden locally
+      %% with a new definition.
+      {user_type, Loc, Name, Args0};
+    false ->
+      Args = massage_type_list(Args0, Defs),
+      {type, Loc, Name, Args}
+  end;
+massage_type(Type, _Defs) ->
+  Type.
+
+massage_type_list([H|T], Defs) ->
+  [massage_type(H, Defs) | massage_type_list(T, Defs)];
+massage_type_list([], _Defs) ->
+  [].
+
 -spec get_fun_meta_info(module(), cerl:c_module(), [dial_warn_tag()]) ->
                 dialyzer_codeserver:fun_meta_info() | {'error', string()}.
 
diff --git a/lib/dialyzer/test/small_SUITE_data/results/redefine_builtin_type b/lib/dialyzer/test/small_SUITE_data/results/redefine_builtin_type
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/lib/dialyzer/test/small_SUITE_data/results/redefine_builtins b/lib/dialyzer/test/small_SUITE_data/results/redefine_builtins
new file mode 100644
index 0000000000..c7da0cf72a
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/results/redefine_builtins
@@ -0,0 +1,5 @@
+
+a.erl:4:2: Invalid type specification for function a:vi/1.
+ The success typing is a:vi(integer()) -> 'ok'
+ But the spec is a:vi(b:integer()) -> 'ok'
+ They do not overlap in the 1st argument
diff --git a/lib/dialyzer/test/small_SUITE_data/src/redefine_builtin_type.erl b/lib/dialyzer/test/small_SUITE_data/src/redefine_builtin_type.erl
new file mode 100644
index 0000000000..9cf80cafb6
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/src/redefine_builtin_type.erl
@@ -0,0 +1,24 @@
+-module(redefine_builtin_type).
+-export([lookup/2, verify_mfa/1, verify_pid/1]).
+
+-type map() :: {atom(), erlang:map()}.
+
+-spec lookup(atom(), map()) -> {'ok', term()} | 'error'.
+
+lookup(Key, {Key, Map}) when is_atom(Key), is_map(Map) ->
+    {ok, Map};
+lookup(Key1, {Key2, Map}) when is_atom(Key1), is_atom(Key2), is_map(Map) ->
+    error.
+
+%% Type `mfa()` depends on `erlang::module()`. Make sure that `mfa()`
+%% does not attempt to use our local definition of `module()`.
+
+-type module() :: pid().
+
+-spec verify_mfa(mfa()) -> 'ok'.
+verify_mfa({M, F, A}) when is_atom(M), is_atom(F), is_integer(A) ->
+    ok.
+
+-spec verify_pid(module()) -> 'ok'.
+verify_pid(Pid) when is_pid(Pid) ->
+    ok.
diff --git a/lib/dialyzer/test/small_SUITE_data/src/redefine_builtins/a.erl b/lib/dialyzer/test/small_SUITE_data/src/redefine_builtins/a.erl
new file mode 100644
index 0000000000..274906d554
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/src/redefine_builtins/a.erl
@@ -0,0 +1,14 @@
+-module(a).
+-export([vi/1, sum/2, vc/1]).
+
+-spec vi(b:integer()) -> 'ok'.
+vi(I) when is_integer(I) ->
+    ok.
+
+-spec sum(b:integer(), integer()) -> integer().
+sum([A], B) ->
+    A + B.
+
+-spec vc(b:collection()) -> 'ok'.
+vc({Int, List}) when length(List) =:= Int ->
+    ok.
diff --git a/lib/dialyzer/test/small_SUITE_data/src/redefine_builtins/b.erl b/lib/dialyzer/test/small_SUITE_data/src/redefine_builtins/b.erl
new file mode 100644
index 0000000000..c11f591036
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/src/redefine_builtins/b.erl
@@ -0,0 +1,6 @@
+-module(b).
+-export_type([integer/0, collection/0]).
+
+-type integer() :: [integer()].
+
+-type collection() :: {erlang:integer(), integer()}.
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index 80b5d7ed09..e879b89e58 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -436,12 +436,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(); "
@@ -674,7 +670,10 @@ 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)}
 	],
     Enabled1 = [Category || {Category,true} <- Enabled0],
     Enabled = ordsets:from_list(Enabled1),
@@ -2975,17 +2974,13 @@ 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
@@ -3005,12 +3000,29 @@ type_def(Attr, Anno, TypeName, ProtoType, Args, St0) ->
 	    end
     end.
 
+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.
+
 is_underspecified({type,_,term,[]}, 0) -> true;
 is_underspecified({type,_,any,[]}, 0) -> true;
 is_underspecified(_ProtType, _Arity) -> false.
 
 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;
@@ -3020,24 +3032,39 @@ check_type(Types, St) ->
 		      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);
@@ -3045,34 +3072,34 @@ check_type({var, A, Name}, SeenVars, St) ->
 	    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},
@@ -3080,20 +3107,20 @@ 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;
+check_type_2({type, _A, Tag, Args}, SeenVars, St) when Tag =:= product;
                                                      Tag =:= union;
                                                      Tag =:= tuple ->
     lists:foldl(fun(T, {AccSeenVars, AccSt}) ->
-			check_type(T, AccSeenVars, AccSt)
+			check_type_1(T, AccSeenVars, AccSt)
 		end, {SeenVars, St}, Args);
-check_type({type, Anno, TypeName, Args}, SeenVars, St) ->
+check_type_2({type, Anno, TypeName, Args}, SeenVars, St) ->
     #lint{module = Module, types=Types} = St,
     Arity = length(Args),
     TypePair = {TypeName, Arity},
@@ -3109,20 +3136,21 @@ 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;
+              _ ->
+                  St
+          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 ->
@@ -3157,7 +3185,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) ->
@@ -3173,8 +3201,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.
 
diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl
index b37bcd43da..2f2f0fc23e 100644
--- a/lib/stdlib/test/erl_lint_SUITE.erl
+++ b/lib/stdlib/test/erl_lint_SUITE.erl
@@ -78,7 +78,8 @@
          underscore_match/1,
          unused_record/1,
          unused_type2/1,
-         eep49/1]).
+         eep49/1,
+         redefined_builtin_type/1]).
 
 suite() ->
     [{ct_hooks,[ts_install_cth]},
@@ -106,7 +107,8 @@ all() ->
      otp_15456, otp_15563, unused_type, binary_types, removed, otp_16516,
      inline_nifs, warn_missing_spec, otp_16824,
      underscore_match, unused_record, unused_type2,
-     eep49].
+     eep49,
+     redefined_builtin_type].
 
 groups() -> 
     [{unused_vars_warn, [],
@@ -966,13 +968,13 @@ binary_types(Config) when is_list(Config) ->
     Ts = [{binary1,
            <<"-type nonempty_binary() :: term().">>,
            [nowarn_unused_type],
-           {errors,[{{1,22},erl_lint,
-                     {builtin_type,{nonempty_binary,0}}}],[]}},
+           {warnings,[{{1,22},erl_lint,
+                       {redefine_builtin_type,{nonempty_binary,0}}}]}},
           {binary2,
            <<"-type nonempty_bitstring() :: term().">>,
            [nowarn_unused_type],
-           {errors,[{{1,22},erl_lint,
-                     {builtin_type,{nonempty_bitstring,0}}}],[]}}],
+           {warnings,[{{1,22},erl_lint,
+                       {redefine_builtin_type,{nonempty_bitstring,0}}}]}}],
     [] = run(Config, Ts),
     ok.
 
@@ -2810,9 +2812,9 @@ otp_11772(Config) when is_list(Config) ->
             t() ->
                 1.
          ">>,
-    {errors,[{{7,14},erl_lint,{builtin_type,{node,0}}},
-             {{8,14},erl_lint,{builtin_type,{mfa,0}}}],
-     []} = run_test2(Config, Ts, []),
+    {warnings,[{{7,14},erl_lint,{redefine_builtin_type,{node,0}}},
+               {{8,14},erl_lint,{redefine_builtin_type,{mfa,0}}}]} =
+        run_test2(Config, Ts, []),
     ok.
 
 %% OTP-11771. Do not allow redefinition of the types arity(_) &c..
@@ -2835,11 +2837,11 @@ otp_11771(Config) when is_list(Config) ->
             t() ->
                 1.
          ">>,
-    {errors,[{{7,14},erl_lint,{builtin_type,{arity,0}}},
-             {{8,14},erl_lint,{builtin_type,{bitstring,0}}},
-             {{9,14},erl_lint,{builtin_type,{iodata,0}}},
-             {{10,14},erl_lint,{builtin_type,{boolean,0}}}],
-     []} = run_test2(Config, Ts, []),
+    {warnings,[{{7,14},erl_lint,{redefine_builtin_type,{arity,0}}},
+               {{8,14},erl_lint,{redefine_builtin_type,{bitstring,0}}},
+               {{9,14},erl_lint,{redefine_builtin_type,{iodata,0}}},
+               {{10,14},erl_lint,{redefine_builtin_type,{boolean,0}}}]} =
+        run_test2(Config, Ts, []),
     ok.
 
 %% OTP-11872. The type map() undefined when exported.
@@ -2851,15 +2853,16 @@ otp_11872(Config) when is_list(Config) ->
 
             -export_type([map/0, product/0]).
 
-            -opaque map() :: dict().
+            -opaque map() :: unknown_type().
 
             -spec t() -> map().
 
             t() ->
                 1.
          ">>,
-    {errors,[{{6,14},erl_lint,{undefined_type,{product,0}}},
-             {{8,14},erl_lint,{builtin_type,{map,0}}}], []} =
+    {error,[{{6,14},erl_lint,{undefined_type,{product,0}}},
+            {{8,30},erl_lint,{undefined_type,{unknown_type,0}}}],
+     [{{8,14},erl_lint,{redefine_builtin_type,{map,0}}}]} =
         run_test2(Config, Ts, []),
     ok.
 
@@ -3869,7 +3872,7 @@ maps_type(Config) when is_list(Config) ->
 	    t(M) -> M.
 	 ">>,
 	 [],
-	 {errors,[{{3,7},erl_lint,{builtin_type,{map,0}}}],[]}}],
+         {warnings,[{{3,7},erl_lint,{redefine_builtin_type,{map,0}}}]}}],
     [] = run(Config, Ts),
     ok.
 
@@ -4837,6 +4840,72 @@ eep49(Config) when is_list(Config) ->
     [] = run(Config, Ts),
     ok.
 
+%% GH-6132: Allow local redefinition of types.
+redefined_builtin_type(Config) ->
+    Ts = [{redef1,
+           <<"-type nonempty_binary() :: term().
+              -type map() :: {_,_}.">>,
+           [nowarn_unused_type,
+            nowarn_redefined_builtin_type],
+           []},
+          {redef2,
+           <<"-type nonempty_bitstring() :: term().
+              -type map() :: {_,_}.">>,
+           [nowarn_unused_type,
+            {nowarn_redefined_builtin_type,{map,0}}],
+           {warnings,[{{1,22},erl_lint,
+                       {redefine_builtin_type,{nonempty_bitstring,0}}}]}},
+          {redef3,
+           <<"-compile({nowarn_redefined_builtin_type,{map,0}}).
+              -compile({nowarn_redefined_builtin_type,[{nonempty_bitstring,0}]}).
+              -type nonempty_bitstring() :: term().
+              -type map() :: {_,_}.
+              -type list() :: erlang:map().">>,
+           [nowarn_unused_type,
+            {nowarn_redefined_builtin_type,{map,0}}],
+           {warnings,[{{5,16},erl_lint,
+                       {redefine_builtin_type,{list,0}}}]}},
+          {redef4,
+           <<"-type tuple() :: 'tuple'.
+              -type map() :: 'map'.
+              -type list() :: 'list'.
+              -spec t(tuple() | map()) -> list().
+              t(_) -> ok.
+             ">>,
+           [],
+           {warnings,[{{1,22},erl_lint,{redefine_builtin_type,{tuple,0}}},
+                      {{2,16},erl_lint,{redefine_builtin_type,{map,0}}},
+                      {{3,16},erl_lint,{redefine_builtin_type,{list,0}}}
+                     ]}},
+          {redef5,
+           <<"-type atom() :: 'atom'.
+              -type integer() :: 'integer'.
+              -type reference() :: 'ref'.
+              -type pid() :: 'pid'.
+              -type port() :: 'port'.
+              -type float() :: 'float'.
+              -type iodata() :: 'iodata'.
+              -type ref_set() :: gb_sets:set(reference()).
+              -type pid_map() :: #{pid() => port()}.
+              -type atom_int_fun() :: fun((atom()) -> integer()).
+              -type collection(Type) :: {'collection', Type}.
+              -callback b1(I :: iodata()) -> atom().
+              -spec t(collection(float())) -> {pid_map(), ref_set(), atom_int_fun()}.
+              t(_) -> ok.
+             ">>,
+           [],
+           {warnings,[{{1,22},erl_lint,{redefine_builtin_type,{atom,0}}},
+                      {{2,16},erl_lint,{redefine_builtin_type,{integer,0}}},
+                      {{3,16},erl_lint,{redefine_builtin_type,{reference,0}}},
+                      {{4,16},erl_lint,{redefine_builtin_type,{pid,0}}},
+                      {{5,16},erl_lint,{redefine_builtin_type,{port,0}}},
+                      {{6,16},erl_lint,{redefine_builtin_type,{float,0}}},
+                      {{7,16},erl_lint,{redefine_builtin_type,{iodata,0}}}
+                     ]}}
+         ],
+    [] = run(Config, Ts),
+    ok.
+
 format_error(E) ->
     lists:flatten(erl_lint:format_error(E)).
 
diff --git a/system/doc/reference_manual/typespec.xml b/system/doc/reference_manual/typespec.xml
index 03a83680a8..dbe97237ec 100644
--- a/system/doc/reference_manual/typespec.xml
+++ b/system/doc/reference_manual/typespec.xml
@@ -314,11 +314,6 @@
     <tcaption>Additional built-in types</tcaption>
   </table>
 
-  <p>
-    Users are not allowed to define types with the same names as the
-    predefined or built-in ones. This is checked by the compiler and
-    its violation results in a compilation error.
-  </p>
   <note>
     <p>
       The following built-in list types also exist,
@@ -345,7 +340,34 @@
     This is described in <seeguide marker="#typeinrecords">
     Type Information in Record Declarations</seeguide>.
   </p>
+
+  <section>
+    <title>Redefining built-in types</title>
+    <p>
+      Starting from Erlang/OTP 26, is is permitted to define a type
+      having the same name as a built-in type. It is recommended to
+      avoid deliberately reusing built-in names because it can be
+      confusing. However, when an Erlang/OTP release introduces a new
+      type, code that happened to define its own type having the same
+      name will continue to work.
+    </p>
+
+    <p>As an example, imagine that the Erlang/OTP 42 release introduces
+    a new type <c>gadget()</c> defined like this:</p>
+
+    <pre>
+  -type gadget() :: {'gadget', reference()}.</pre>
+
+    <p>Further imagine that some code has its own (different)
+    definition of <c>gadget()</c>, for example:</p>
+
+    <pre>
+  -type gadget() :: #{}.</pre>
+
+    <p>Since redefinitions are allowed, the code will still compile (but
+    with a warning), and Dialyzer will not emit any additional warnings.</p>
   </section>
+</section>
 
   <section>
     <title>Type Declarations of User-Defined Types</title>
-- 
2.35.3

openSUSE Build Service is sponsored by