Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang
erlang
1091-Allow-local-redefinition-of-built-in-types...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 1091-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() -> no_load_nif, 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
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor