File 3542-features-Add-support-for-handling-features.patch of Package erlang
From 55b51892b9556c18b2b5aa7607cfe80db9ca4e8d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cons=20T=20=C3=85hs?= <cons@erlang.org>
Date: Mon, 8 Nov 2021 09:47:47 +0100
Subject: [PATCH 2/7] [features] Add support for handling features
* Utility and features specifications in erl_features
* Compiler support
* Add predefined macros for features
* Add support for parsing (long) options to erlc
* Conditionally allow 'else' (also used in preprocessor) as keyword
* This will be needed for EEP49
* Add feature info in new Meta chunk in beam file
* Add warning for atoms that are keywords in features
* Add new feature description options to erlc
* -list-feature to list existing features in short form
* -describe-feature <ftr> to get long description of feature
* Runtime support
* Store enabled features with persistent_term (only set at startup)
* Check features in Meta chunk to determine whether load is allowed
* Features use for test (in erl_features) active when env variable
OTP_TEST_FEATURES is set to true
* init of erl_features is lazy
* Using on_load functions only work after code_server is up
and running.
---
erts/preloaded/src/erlang.erl | 26 +-
erts/preloaded/src/erts.app.src | 2 +-
lib/compiler/src/compile.erl | 93 ++--
lib/kernel/src/code.erl | 3 +-
lib/stdlib/src/Makefile | 1 +
lib/stdlib/src/epp.erl | 220 +++++++--
lib/stdlib/src/erl_compile.erl | 82 +++-
lib/stdlib/src/erl_features.erl | 564 ++++++++++++++++++++++
lib/stdlib/src/erl_lint.erl | 28 +-
lib/stdlib/src/erl_scan.erl | 72 +--
lib/stdlib/src/stdlib.app.src | 1 +
lib/stdlib/test/epp_SUITE.erl | 13 +-
lib/syntax_tools/src/syntax_tools.app.src | 2 +-
13 files changed, 976 insertions(+), 131 deletions(-)
create mode 100644 lib/stdlib/src/erl_features.erl
diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl
index b0430f4182..bbdd46dd80 100644
--- a/erts/preloaded/src/erlang.erl
+++ b/erts/preloaded/src/erlang.erl
@@ -2310,18 +2310,24 @@ is_tuple(_Term) ->
-spec load_module(Module, Binary) -> {module, Module} | {error, Reason} when
Module :: module(),
Binary :: binary(),
- Reason :: badfile | not_purged | on_load.
+ Reason :: badfile | not_purged | on_load | not_allowed.
load_module(Mod, Code) ->
try
- case erlang:prepare_loading(Mod, Code) of
- {error,_}=Error ->
- Error;
- Prep when erlang:is_reference(Prep) ->
- case erlang:finish_loading([Prep]) of
- ok ->
- {module,Mod};
- {Error,[Mod]} ->
- {error,Error}
+ Allowed = (not erlang:module_loaded(erl_features))
+ orelse erl_features:load_allowed(Code),
+ if not Allowed ->
+ {error, not_allowed};
+ true ->
+ case erlang:prepare_loading(Mod, Code) of
+ {error,_}=Error ->
+ Error;
+ Prep when erlang:is_reference(Prep) ->
+ case erlang:finish_loading([Prep]) of
+ ok ->
+ {module,Mod};
+ {Error,[Mod]} ->
+ {error,Error}
+ end
end
end
catch
diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl
index 847ea484ba..d82f53d772 100644
--- a/lib/compiler/src/compile.erl
+++ b/lib/compiler/src/compile.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2022. 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.
@@ -1018,41 +1018,65 @@ do_parse_module(DefEncoding, #compile{ifile=File,options=Opts,dir=Dir}=St) ->
false ->
1
end,
-
- %% FIXME: Rewrite this when the enable feature EEP has been implemented.
- ResWordFun = case proplists:get_value(enable_feature, Opts, []) of
- maybe_expr ->
- fun('maybe') -> true;
- ('else') -> true;
- (Other) -> erl_scan:reserved_word(Other)
- end;
- _ ->
- fun erl_scan:reserved_word/1
- end,
-
- R = epp:parse_file(File,
- [{includes,[".",Dir|inc_paths(Opts)]},
- {source_name, SourceName},
- {macros,pre_defs(Opts)},
- {default_encoding,DefEncoding},
- {location,StartLocation},
- {reserved_word_fun,ResWordFun},
- extra]),
- case R of
- {ok,Forms0,Extra} ->
- Encoding = proplists:get_value(encoding, Extra),
- Forms = case with_columns(Opts ++ compile_options(Forms0)) of
- true ->
- Forms0;
- false ->
- strip_columns(Forms0)
- end,
- {ok,Forms,St#compile{encoding=Encoding}};
- {error,E} ->
- Es = [{St#compile.ifile,[{none,?MODULE,{epp,E}}]}],
- {error,St#compile{errors=St#compile.errors ++ Es}}
+ case erl_features:keyword_fun(Opts, fun erl_scan:f_reserved_word/1) of
+ {ok, {Features, ResWordFun}} ->
+ R = epp:parse_file(File,
+ [{includes,[".",Dir|inc_paths(Opts)]},
+ {source_name, SourceName},
+ {macros,pre_defs(Opts)},
+ {default_encoding,DefEncoding},
+ {location,StartLocation},
+ {reserved_word_fun, ResWordFun},
+ {features, Features},
+ extra]),
+ case R of
+ %% FIXME Extra should include used features as well
+ {ok,Forms0,Extra} ->
+ Encoding = proplists:get_value(encoding, Extra),
+ %% Get features used in the module, indicated by
+ %% enabling features with
+ %% -compile({enable_feature, ..}).
+ UsedFtrs = proplists:get_value(features, Extra),
+ St1 = metadata_add_features(UsedFtrs, St),
+ Forms = case with_columns(Opts ++ compile_options(Forms0)) of
+ true ->
+ Forms0;
+ false ->
+ strip_columns(Forms0)
+ end,
+ {ok,Forms,St1#compile{encoding=Encoding}};
+ {error,E} ->
+ Es = [{St#compile.ifile,[{none,?MODULE,{epp,E}}]}],
+ {error,St#compile{errors=St#compile.errors ++ Es}}
+ end;
+ {error, {Mod, Reason}} ->
+ Es = [{St#compile.ifile,[{none, Mod, Reason}]}],
+ {error, St#compile{errors = St#compile.errors ++ Es}}
end.
+%% The atom to be used in the proplist of the meta chunk indicating
+%% the features used when compiling the module.
+-define(META_USED_FEATURES, enabled_features).
+-define(META_CHUNK_NAME, <<"Meta">>).
+
+metadata_add_features(Ftrs, #compile{extra_chunks = Extra} = St) ->
+ MetaData =
+ case proplists:get_value(?META_CHUNK_NAME, Extra) of
+ undefined ->
+ [];
+ Bin ->
+ erlang:binary_to_term(Bin)
+ end,
+ OldFtrs = proplists:get_value(?META_USED_FEATURES, MetaData, []),
+ NewFtrs = (Ftrs -- OldFtrs) ++ OldFtrs,
+ MetaData1 =
+ proplists:from_map(maps:put(?META_USED_FEATURES, NewFtrs,
+ proplists:to_map(MetaData))),
+ Extra1 = proplists:from_map(maps:put(?META_CHUNK_NAME,
+ erlang:term_to_binary(MetaData1),
+ proplists:to_map(Extra))),
+ St#compile{extra_chunks = Extra1}.
+
with_columns(Opts) ->
case proplists:get_value(error_location, Opts, column) of
column -> true;
@@ -2081,6 +2105,7 @@ pre_load() ->
epp,
erl_bifs,
erl_expand_records,
+ erl_features,
erl_lint,
erl_parse,
erl_scan,
diff --git a/lib/kernel/src/code.erl b/lib/kernel/src/code.erl
index 71a570fa7b..3b426600d3 100644
--- a/lib/kernel/src/code.erl
+++ b/lib/kernel/src/code.erl
@@ -739,7 +739,8 @@ do_start() ->
load_code_server_prerequisites() ->
%% Please keep the alphabetical order.
- Needed = [binary,
+ Needed = [beam_lib,
+ binary,
ets,
filename,
gb_sets,
diff --git a/lib/stdlib/src/Makefile b/lib/stdlib/src/Makefile
index 88f41634fe..b7b5ead962 100644
--- a/lib/stdlib/src/Makefile
+++ b/lib/stdlib/src/Makefile
@@ -65,6 +65,7 @@ MODULES= \
erl_error \
erl_eval \
erl_expand_records \
+ erl_features \
erl_internal \
erl_lint \
erl_parse \
diff --git a/lib/stdlib/src/epp.erl b/lib/stdlib/src/epp.erl
index 1f14133039..1c2dbcb3bb 100644
--- a/lib/stdlib/src/epp.erl
+++ b/lib/stdlib/src/epp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2022. 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.
@@ -72,9 +72,12 @@
uses = #{} %Macro use structure
:: #{name() => [{argspec(), [used()]}]},
default_encoding = ?DEFAULT_ENCODING :: source_encoding(),
- pre_opened = false :: boolean(),
- fname = [] :: function_name_type(),
- erl_scan_opts = [] :: erl_scan:options()
+ pre_opened = false :: boolean(),
+ in_prefix = true :: boolean(),
+ erl_scan_opts = [] :: [_],
+ features = [] :: [atom()],
+ else_reserved = false :: boolean(),
+ fname = [] :: function_name_type()
}).
%% open(Options)
@@ -132,12 +135,14 @@ open(Options) ->
Name ->
Self = self(),
Epp = spawn(fun() -> server(Self, Name, Options) end),
+ Extra = proplists:get_bool(extra, Options),
case epp_request(Epp) of
- {ok, Pid, Encoding} ->
- case proplists:get_bool(extra, Options) of
- true -> {ok, Pid, [{encoding, Encoding}]};
- false -> {ok, Pid}
- end;
+ {ok, Pid, Encoding} when Extra ->
+ {ok, Pid, [{encoding, Encoding}]};
+ {ok, Pid, _} ->
+ {ok, Pid};
+ {ok, Pid} when Extra ->
+ {ok, Pid, []};
Other ->
Other
end
@@ -240,6 +245,8 @@ format_error({error,Term}) ->
io_lib:format("-error(~tp).", [Term]);
format_error({warning,Term}) ->
io_lib:format("-warning(~tp).", [Term]);
+format_error(ftr_after_prefix) ->
+ "feature directive not allowed after anything interesting";
format_error(E) -> file:format_error(E).
-spec scan_file(FileName, Options) ->
@@ -298,6 +305,8 @@ parse_file(Ifile, Path, Predefs) ->
{'macros', PredefMacros :: macros()} |
{'default_encoding', DefEncoding :: source_encoding()} |
{'location',StartLocation :: erl_anno:location()} |
+ {'reserved_word_fun', Fun :: fun((atom()) -> boolean())} |
+ {'features', [Feature :: atom()]} |
'extra'],
Form :: erl_parse:abstract_form()
| {'error', ErrorInfo}
@@ -312,11 +321,13 @@ parse_file(Ifile, Options) ->
{ok,Epp} ->
Forms = parse_file(Epp),
close(Epp),
- {ok,Forms};
+ {ok, Forms};
{ok,Epp,Extra} ->
Forms = parse_file(Epp),
+ Epp ! {get_features, self()},
+ Ftrs = receive X -> X end,
close(Epp),
- {ok,Forms,Extra};
+ {ok, Forms, [{features, Ftrs} | Extra]};
{error,E} ->
{error,E}
end.
@@ -594,7 +605,8 @@ server(Pid, Name, Options) ->
init_server(Pid, FileName, Options, St0) ->
SourceName = proplists:get_value(source_name, Options, FileName),
Pdm = proplists:get_value(macros, Options, []),
- Ms0 = predef_macros(SourceName),
+ Features = proplists:get_value(features, Options, []),
+ Ms0 = predef_macros(SourceName, Features),
case user_predef(Pdm, Ms0) of
{ok,Ms1} ->
DefEncoding = proplists:get_value(default_encoding, Options,
@@ -605,16 +617,19 @@ init_server(Pid, FileName, Options, St0) ->
%% first in path
Path = [filename:dirname(FileName) |
proplists:get_value(includes, Options, [])],
-
- ResWordFun = proplists:get_value(reserved_word_fun, Options,
- fun erl_scan:reserved_word/1),
-
+ ResWordFun =
+ proplists:get_value(reserved_word_fun, Options,
+ fun erl_scan:f_reserved_word/1),
%% the default location is 1 for backwards compatibility, not {1,1}
AtLocation = proplists:get_value(location, Options, 1),
+
St = St0#epp{delta=0, name=SourceName, name2=SourceName,
path=Path, location=AtLocation, macs=Ms1,
default_encoding=DefEncoding,
- erl_scan_opts=[{reserved_word_fun,ResWordFun}]},
+ erl_scan_opts =
+ [{reserved_word_fun, ResWordFun}],
+ features = Features,
+ else_reserved = ResWordFun('else')},
From = wait_request(St),
Anno = erl_anno:new(AtLocation),
enter_file_reply(From, file_name(SourceName), Anno,
@@ -628,10 +643,11 @@ init_server(Pid, FileName, Options, St0) ->
%% Initialise the macro dictionary with the default predefined macros,
%% FILE, LINE, MODULE as undefined, MACHINE and MACHINE value.
-predef_macros(File) ->
+predef_macros(File, EnabledFeatures) ->
Machine = list_to_atom(erlang:system_info(machine)),
Anno = line1(),
OtpVersion = list_to_integer(erlang:system_info(otp_release)),
+ AvailableFeatures = erl_features:features(),
Defs = [{'FILE', {none,[{string,Anno,File}]}},
{'FUNCTION_NAME', undefined},
{'FUNCTION_ARITY', undefined},
@@ -642,10 +658,38 @@ predef_macros(File) ->
{'BASE_MODULE_STRING', undefined},
{'MACHINE', {none,[{atom,Anno,Machine}]}},
{Machine, {none,[{atom,Anno,true}]}},
- {'OTP_RELEASE', {none,[{integer,Anno,OtpVersion}]}}
+ {'OTP_RELEASE', {none,[{integer,Anno,OtpVersion}]}},
+ %% FIXME Understand this has to be a list. Is it because
+ %% it takes an argument?
+ {'FEATURE_AVAILABLE', [ftr_macro(AvailableFeatures)]},
+ {'FEATURE_ENABLED', [ftr_macro(EnabledFeatures)]}
],
maps:from_list(Defs).
+%% Make macro definition from a list of features. The macro takes one
+%% argument and returns true when argument is available as a feature.
+ftr_macro(Features) ->
+ Anno = line1(),
+ Arg = 'X',
+ Fexp = fun(Ftr) -> [{'(', Anno},
+ {var, Anno, Arg},
+ {')', Anno},
+ {'==', Anno},
+ {atom, Anno, Ftr}]
+ end,
+ Body =
+ case Features of
+ [] -> [{atom, Anno, false}];
+ [Ftr| Ftrs] ->
+ [{'(', Anno}|
+ lists:foldl(fun(F, Expr) ->
+ Fexp(F) ++ [{'orelse', Anno} | Expr]
+ end,
+ Fexp(Ftr) ++ [{')', Anno}],
+ Ftrs)]
+ end,
+ {1, {[Arg], Body}}.
+
%% user_predef(PreDefMacros, Macros) ->
%% {ok,MacroDict} | {error,E}
%% Add the predefined macros to the macros dictionary. A macro without a
@@ -680,6 +724,9 @@ user_predef([], Ms) -> {ok,Ms}.
wait_request(St) ->
receive
{epp_request,From,scan_erl_form} -> From;
+ {get_features, From} ->
+ From ! St#epp.features,
+ wait_request(St);
{epp_request,From,macro_defs} ->
%% Return the old format to avoid any incompability issues.
Defs = [{{atom,K},V} || {K,V} <- maps:to_list(St#epp.macs)],
@@ -736,7 +783,11 @@ enter_file2(NewF, Pname, From, St0, AtLocation) ->
Anno = erl_anno:new(AtLocation),
enter_file_reply(From, Pname, Anno, AtLocation, code),
#epp{macs = Ms0,
- default_encoding = DefEncoding} = St0,
+ default_encoding = DefEncoding,
+ in_prefix = InPrefix,
+ erl_scan_opts = ScanOpts,
+ else_reserved = ElseReserved,
+ features = Ftrs} = St0,
Ms = Ms0#{'FILE':={none,[{string,Anno,Pname}]}},
%% update the head of the include path to be the directory of the new
%% source file, so that an included file can always include other files
@@ -748,6 +799,10 @@ enter_file2(NewF, Pname, From, St0, AtLocation) ->
_ = set_encoding(NewF, DefEncoding),
#epp{file=NewF,location=AtLocation,name=Pname,name2=Pname,delta=0,
sstk=[St0|St0#epp.sstk],path=Path,macs=Ms,
+ in_prefix = InPrefix,
+ features = Ftrs,
+ erl_scan_opts = ScanOpts,
+ else_reserved = ElseReserved,
default_encoding=DefEncoding}.
enter_file_reply(From, Name, LocationAnno, AtLocation, Where) ->
@@ -789,8 +844,16 @@ leave_file(From, St) ->
CurrLoc = add_line(OldLoc, Delta),
Anno = erl_anno:new(CurrLoc),
Ms0 = St#epp.macs,
+ InPrefix = St#epp.in_prefix,
+ Ftrs = St#epp.features,
+ ElseReserved = St#epp.else_reserved,
+ ScanOpts = St#epp.erl_scan_opts,
Ms = Ms0#{'FILE':={none,[{string,Anno,OldName2}]}},
- NextSt = OldSt#epp{sstk=Sts,macs=Ms,uses=St#epp.uses},
+ NextSt = OldSt#epp{sstk=Sts,macs=Ms,uses=St#epp.uses,
+ in_prefix = InPrefix,
+ features = Ftrs,
+ else_reserved = ElseReserved,
+ erl_scan_opts = ScanOpts},
enter_file_reply(From, OldName, Anno, CurrLoc, code),
case OldName2 =:= OldName of
true ->
@@ -812,27 +875,30 @@ leave_file(From, St) ->
%% scan_toks(Tokens, From, EppState)
scan_toks(From, St) ->
- case io:scan_erl_form(St#epp.file, '', St#epp.location, St#epp.erl_scan_opts) of
- {ok,Toks,Cl} ->
- scan_toks(Toks, From, St#epp{location=Cl});
- {error,E,Cl} ->
- epp_reply(From, {error,E}),
- wait_req_scan(St#epp{location=Cl});
- {eof,Cl} ->
- leave_file(From, St#epp{location=Cl});
- {error,_E} ->
+ #epp{file = File, location = Loc, erl_scan_opts = ScanOpts} = St,
+ case io:scan_erl_form(File, '', Loc, ScanOpts) of
+ {ok,Toks,Cl} ->
+ scan_toks(Toks, From, St#epp{location=Cl});
+ {error,E,Cl} ->
+ epp_reply(From, {error,E}),
+ wait_req_scan(St#epp{location=Cl});
+ {eof,Cl} ->
+ leave_file(From, St#epp{location=Cl});
+ {error,_E} ->
epp_reply(From, {error,{St#epp.location,epp,cannot_parse}}),
- leave_file(wait_request(St), St) %This serious, just exit!
+ leave_file(wait_request(St), St) %This serious, just exit!
end.
+scan_toks([{'-',_Lh},{atom,_Ld,feature}=Feature|Toks], From, St) ->
+ scan_feature(Toks, Feature, From, St);
scan_toks([{'-',_Lh},{atom,_Ld,define}=Define|Toks], From, St) ->
scan_define(Toks, Define, From, St);
scan_toks([{'-',_Lh},{atom,_Ld,undef}=Undef|Toks], From, St) ->
- scan_undef(Toks, Undef, From, St);
+ scan_undef(Toks, Undef, From, leave_prefix(St));
scan_toks([{'-',_Lh},{atom,_Ld,error}=Error|Toks], From, St) ->
- scan_err_warn(Toks, Error, From, St);
+ scan_err_warn(Toks, Error, From, leave_prefix(St));
scan_toks([{'-',_Lh},{atom,_Ld,warning}=Warn|Toks], From, St) ->
- scan_err_warn(Toks, Warn, From, St);
+ scan_err_warn(Toks, Warn, From, leave_prefix(St));
scan_toks([{'-',_Lh},{atom,_Li,include}=Inc|Toks], From, St) ->
scan_include(Toks, Inc, From, St);
scan_toks([{'-',_Lh},{atom,_Li,include_lib}=IncLib|Toks], From, St) ->
@@ -843,7 +909,9 @@ scan_toks([{'-',_Lh},{atom,_Li,ifndef}=IfnDef|Toks], From, St) ->
scan_ifndef(Toks, IfnDef, From, St);
scan_toks([{'-',_Lh},{atom,_Le,'else'}=Else|Toks], From, St) ->
scan_else(Toks, Else, From, St);
-scan_toks([{'-',_Lh},{'else',_Le}=Else|Toks], From, St) ->
+%% conditionally allow else as a keyword
+scan_toks([{'-',_Lh},{'else',_Le}=Else|Toks], From, St)
+ when St#epp.else_reserved ->
scan_else(Toks, Else, From, St);
scan_toks([{'-',_Lh},{'if',_Le}=If|Toks], From, St) ->
scan_if(Toks, If, From, St);
@@ -862,13 +930,37 @@ scan_toks([{'-',_Lh},{atom,_Lf,file}=FileToken|Toks0], From, St) ->
scan_toks(Toks0, From, St) ->
case catch expand_macros(Toks0, St#epp{fname=Toks0}) of
Toks1 when is_list(Toks1) ->
+ InPrefix =
+ St#epp.in_prefix
+ andalso case Toks1 of
+ [] -> true;
+ [{'-', _Loc}, Tok | _] ->
+ in_prefix(Tok);
+ _ ->
+ false
+ end,
epp_reply(From, {ok,Toks1}),
- wait_req_scan(St#epp{macs=scan_module(Toks1, St#epp.macs)});
+ wait_req_scan(St#epp{in_prefix = InPrefix,
+ macs=scan_module(Toks1, St#epp.macs)});
{error,ErrL,What} ->
epp_reply(From, {error,{ErrL,epp,What}}),
wait_req_scan(St)
end.
+%% Determine whether we have passed the prefix where a -feature
+%% directive is allowed.
+in_prefix({atom, _, Atom}) ->
+ %% These directives are allowed inside the prefix
+ lists:member(Atom, ['module', 'feature',
+ 'if', 'else', 'elif', 'endif', 'ifdef', 'ifndef',
+ 'define', 'undef',
+ 'include', 'include_lib']);
+in_prefix(_T) ->
+ false.
+
+leave_prefix(#epp{} = St) ->
+ St#epp{in_prefix = false}.
+
scan_module([{'-',_Ah},{atom,_Am,module},{'(',_Al}|Ts], Ms) ->
scan_module_1(Ts, Ms);
scan_module([{'-',_Ah},{atom,_Am,extends},{'(',_Al}|Ts], Ms) ->
@@ -909,6 +1001,58 @@ scan_err_warn(Toks, {atom,_,Tag}=Token, From, St) ->
epp_reply(From, {error,{loc(T),epp,{bad,Tag}}}),
wait_req_scan(St).
+%% scan a feature directive
+scan_feature([{'(', _Ap}, {atom, _Am, Ind},
+ {',', _}, {atom, _, Ftr}, {')', _}, {dot, _}],
+ Feature, From, St)
+ when St#epp.in_prefix,
+ (Ind =:= enable
+ orelse Ind =:= disable) ->
+ case update_features(St, Ind, Ftr, loc(Feature)) of
+ {ok, St1} ->
+ scan_toks(From, St1);
+ {error, {{Mod, Reason}, ErrLoc}} ->
+ epp_reply(From, {error, {ErrLoc, Mod, Reason}}),
+ wait_req_scan(St)
+ end;
+scan_feature([{'(', _Ap}, {atom, _Am, _Ind},
+ {',', _}, {atom, _, _Ftr}, {')', _}, {dot, _}| _Toks],
+ Feature, From, St) when not St#epp.in_prefix ->
+ epp_reply(From, {error, {loc(Feature), epp,
+ ftr_after_prefix}}),
+ wait_req_scan(St);
+scan_feature(Toks, {atom, _, Tag} = Token, From, St) ->
+ T = no_match(Toks, Token),
+ epp_reply(From, {error,{loc(T),epp,{bad,Tag}}}),
+ wait_req_scan(St).
+
+%% FIXME Rewrite this
+update_features(St0, Ind, Ftr, Loc) ->
+ Ftrs0 = St0#epp.features,
+ ScanOpts = St0#epp.erl_scan_opts,
+ KeywordFun =
+ case proplists:get_value(reserved_word_fun, ScanOpts) of
+ undefined -> fun erl_scan:f_reserved_word/1;
+ Fun -> Fun
+ end,
+ case erl_features:keyword_fun(Ind, Ftr, Ftrs0, KeywordFun) of
+ {error, Reason} ->
+ {error, {Reason, Loc}};
+ {ok, ResWordFun1, Ftrs1} ->
+ Macs0 = St0#epp.macs,
+ Macs1 = Macs0#{'FEATURE_ENABLED' => [ftr_macro(Ftrs1)]},
+ %% ?liof("ok!\n", []),
+ %% FIXME WE need to keep any other scan_opts
+ %% present. Right now, there are no other, but
+ %% that might change.
+ StX = St0#epp{erl_scan_opts =
+ [{reserved_word_fun, ResWordFun1}],
+ features = Ftrs1,
+ else_reserved = ResWordFun1('else'),
+ macs = Macs1},
+ {ok, StX}
+ end.
+
%% scan_define(Tokens, DefineToken, From, EppState)
scan_define([{'(',_Ap},{Type,_Am,_}=Mac|Toks], Def, From, St)
@@ -1328,6 +1472,7 @@ new_location(Ln, {Le,_}, {Lf,_}) ->
%% nested conditionals and repeated 'else's.
skip_toks(From, St, [I|Sis]) ->
+ ElseReserved = St#epp.else_reserved,
case io:scan_erl_form(St#epp.file, '', St#epp.location, St#epp.erl_scan_opts) of
{ok,[{'-',_Ah},{atom,_Ai,ifdef}|_Toks],Cl} ->
skip_toks(From, St#epp{location=Cl}, [ifdef,I|Sis]);
@@ -1337,7 +1482,8 @@ skip_toks(From, St, [I|Sis]) ->
skip_toks(From, St#epp{location=Cl}, ['if',I|Sis]);
{ok,[{'-',_Ah},{atom,_Ae,'else'}=Else|_Toks],Cl}->
skip_else(Else, From, St#epp{location=Cl}, [I|Sis]);
- {ok,[{'-',_Ah},{'else',_Ae}=Else|_Toks],Cl}->
+ %% conditionally allow else as reserved word
+ {ok,[{'-',_Ah},{'else',_Ae}=Else|_Toks],Cl} when ElseReserved ->
skip_else(Else, From, St#epp{location=Cl}, [I|Sis]);
{ok,[{'-',_Ah},{atom,_Ae,'elif'}=Elif|Toks],Cl}->
skip_elif(Toks, Elif, From, St#epp{location=Cl}, [I|Sis]);
diff --git a/lib/stdlib/src/erl_compile.erl b/lib/stdlib/src/erl_compile.erl
index 063d3e700d..87af230c67 100644
--- a/lib/stdlib/src/erl_compile.erl
+++ b/lib/stdlib/src/erl_compile.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2022. 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.
@@ -109,6 +109,9 @@ compile1(Files, Opts) ->
parse_generic_option("b"++Opt, T0, Opts) ->
{OutputType,T} = get_option("b", Opt, T0),
compile1(T, Opts#options{output_type=list_to_atom(OutputType)});
+%% parse_generic_option("c"++Opt, T0, Opts) ->
+%% {InputType,T} = get_option("c", Opt, T0),
+%% compile1(T, Opts#options{input_type=[$.| InputType]});
parse_generic_option("D"++Opt, T0, #options{defines=Defs}=Opts) ->
{Val0,T} = get_option("D", Opt, T0),
{Key0,Val1} = split_at_equals(Val0, []),
@@ -171,6 +174,25 @@ parse_generic_option("P", T, #options{specific=Spec}=Opts) ->
compile1(T, Opts#options{specific=['P'|Spec]});
parse_generic_option("S", T, #options{specific=Spec}=Opts) ->
compile1(T, Opts#options{specific=['S'|Spec]});
+parse_generic_option("enable-feature" ++ Str, T0,
+ #options{specific = Spec} = Opts) ->
+ {FtrStr, T} = get_option("enable-feature", Str, T0),
+ Feature = list_to_atom(FtrStr),
+ compile1(T, Opts#options{
+ specific = Spec ++ [{enable_feature, Feature}]});
+parse_generic_option("disable-feature" ++ Str, T0,
+ #options{specific = Spec} = Opts) ->
+ {FtrStr, T} = get_option("disable-feature", Str, T0),
+ Feature = list_to_atom(FtrStr),
+ compile1(T, Opts#options{specific = Spec ++ [{disable_feature, Feature}]});
+parse_generic_option("describe-feature" ++ Str, T0,
+ #options{specific = Spec} = Opts) ->
+ {FtrStr, T} = get_option("disable-feature", Str, T0),
+ Feature = list_to_atom(FtrStr),
+ compile1(T, Opts#options{specific =[{describe_feature, Feature}| Spec]});
+parse_generic_option("list-features", T,
+ #options{specific = Spec} = Opts) ->
+ compile1(T, Opts#options{specific =[{list_features, true}| Spec]});
parse_generic_option(Option, _T, _Opts) ->
usage(io_lib:format("Unknown option: -~ts\n", [Option])).
@@ -230,11 +252,24 @@ usage(Error) ->
{"-E","generate listing of expanded code (Erlang compiler)"},
{"-S","generate assembly listing (Erlang compiler)"},
{"-P","generate listing of preprocessed code (Erlang compiler)"},
+ {"-enable-feature <feature>",
+ "enable <feature> when compiling (Erlang compiler)"},
+ {"-disable-feature <feature>",
+ "disable <feature> when compiling (Erlang compiler)"},
+ {"-list-features",
+ "list short descriptions of available feature (Erlang compiler)"},
+ {"-describe-feature <feature>",
+ "show long description of <feature>"},
{"+term","pass the Erlang term unchanged to the compiler"}],
+ Fmt = fun(K, D) when length(K) < 15 ->
+ io_lib:format("~-14s ~s\n", [K, D]);
+ (K, D) ->
+ io_lib:format("~s\n~-14s ~s\n", [K, "", D])
+ end,
Msg = [Error,
"Usage: erlc [Options] file.ext ...\n",
"Options:\n",
- [io_lib:format("~-14s ~s\n", [K,D]) || {K,D} <- H]],
+ [Fmt(K, D) || {K,D} <- H]],
throw({error, Msg}).
get_option(_Name, [], [[C|_]=Option|T]) when C =/= $- ->
@@ -252,14 +287,19 @@ split_at_equals([], Acc) ->
{lists:reverse(Acc),[]}.
compile2(Files, #options{cwd=Cwd,includes=Incl,outfile=Outfile}=Opts0) ->
- Opts = Opts0#options{includes=lists:reverse(Incl)},
- case {Outfile,length(Files)} of
- {"", _} ->
- compile3(Files, Cwd, Opts);
- {[_|_], 1} ->
- compile3(Files, Cwd, Opts);
- {[_|_], _N} ->
- throw({error, "Output file name given, but more than one input file.\n"})
+ case show_info(Opts0) of
+ {ok, Msg} ->
+ throw({error, Msg});
+ false ->
+ Opts = Opts0#options{includes=lists:reverse(Incl)},
+ case {Outfile,length(Files)} of
+ {"", _} ->
+ compile3(Files, Cwd, Opts);
+ {[_|_], 1} ->
+ compile3(Files, Cwd, Opts);
+ {[_|_], _N} ->
+ throw({error, "Output file name given, but more than one input file.\n"})
+ end
end.
%% Compile the list of files, until done or compilation fails.
@@ -278,6 +318,28 @@ compile3([File|Rest], Cwd, Options) ->
compile3(Rest, Cwd, Options);
compile3([], _Cwd, _Options) -> ok.
+show_info(#options{specific = Spec}) ->
+ G = fun G0([]) -> undefined;
+ G0([E|Es]) ->
+ case proplists:get_value(E, Spec) of
+ undefined -> G0(Es);
+ V -> {E, V}
+ end
+ end,
+
+ case G([list_features, describe_feature]) of
+ {list_features, true} ->
+ Features = erl_features:features(),
+ Msg = ["Available features:\n",
+ [io_lib:format(" ~-13s ~s\n", [Ftr, erl_features:short(Ftr)])
+ || Ftr <- Features]],
+ {ok, Msg};
+ {describe_feature, Ftr} ->
+ {ok, erl_features:long(Ftr)};
+ _ ->
+ false
+ end.
+
%% Invoke the appropriate compiler, depending on the file extension.
compile_file("", Input, _Output, _Options) ->
throw({error, io_lib:format("File has no extension: ~ts~n", [Input])});
diff --git a/lib/stdlib/src/erl_features.erl b/lib/stdlib/src/erl_features.erl
new file mode 100644
index 0000000000..d8618f9c3f
--- /dev/null
+++ b/lib/stdlib/src/erl_features.erl
@@ -0,0 +1,564 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2021-2022. 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.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(erl_features).
+
+%% FIXME divide the exported functions in public and internal for the
+%% sake of documentation.
+-export([features/0,
+ feature_info/1,
+ collect_features/1,
+ short/1,
+ long/1,
+ enabled_features/0,
+ is_valid_feature/1,
+ load_allowed/1,
+ keywords/0,
+ keywords/1,
+ keyword_fun/2,
+ keyword_fun/4,
+ enable_feature/1,
+ disable_feature/1,
+ format_error/1,
+ format_error/2]).
+
+-export([features_used/1]).
+
+-type type() :: 'extension' | 'backwards_incompatible_change'.
+-type status() :: 'experimental'
+ | 'approved'
+ | 'permanent'
+ | 'rejected'.
+-type release() :: non_neg_integer().
+-type error() :: {?MODULE, {'invalid_features', [atom()]}}.
+
+-define(VALID_FEATURE(Feature),
+ (case is_valid_feature(Feature) of
+ false ->
+ error(invalid_feature, [Feature],
+ [{error_info,
+ #{module => ?MODULE,
+ cause => #{1 => "unknown feature"}}}]);
+ true -> ok
+ end)).
+
+%% Specification about currently known features.
+feature_specs() ->
+ #{}.
+
+%% Currently known features
+-spec features() -> [atom()].
+features() ->
+ Map = case persistent_term:get({?MODULE, feature_specs}, none) of
+ none -> init_specs();
+ M -> M
+ end,
+ maps:keys(Map).
+
+is_valid_feature(Ftr) ->
+ lists:member(Ftr, features()).
+
+-spec short(atom()) -> iolist().
+short(Feature) ->
+ #{short := Short,
+ status := Status} = Info = feature_info(Feature),
+ #{Status := Release} = Info,
+ io_lib:format("~-40s ~-12s (~p)", [Short, Status, Release]).
+
+long(Feature) ->
+ #{short := Short,
+ description := Description,
+ status := Status,
+ keywords := Keywords,
+ type := Type} = Info = feature_info(Feature),
+ StatusFmt = " ~-10s ~-12s (~p)\n",
+ History = [io_lib:format(StatusFmt, [T, S, R])
+ || {T, S, R} <- history(Status, Info)],
+ KeywordsStrs =
+ if Keywords == [] -> "";
+ true ->
+ io_lib:format(" ~-10s ~p\n", ["Keywords", Keywords])
+ end,
+ Lines = [{"~s - ~s\n", [Feature, Short]},
+ {" ~-10s ~s\n", ["Type", Type]},
+ {"~s", [History]},
+ {"~s", [KeywordsStrs]},
+ {"\n~s\n", [nqTeX(Description)]}],
+ [io_lib:format(FStr, Args) || {FStr, Args} <- Lines].
+
+history(Current, Info) ->
+ G = fun(Key, S) ->
+ case maps:find(Key, Info) of
+ error -> [];
+ {ok, R} -> [{S, Key, R}]
+ end
+ end,
+ F = fun(Key) -> G(Key, "") end,
+ History =
+ case Current of
+ experimental -> [];
+ rejected -> F(experimental);
+ approved -> F(experimental);
+ permanent -> F(approved) ++ F(experimental)
+ end,
+ G(Current, "Status") ++ History.
+
+%% Dead simple line breaking for better presentation.
+nqTeX(String) ->
+ Words = string:tokens(String, " "),
+ WithLens = lists:map(fun(W) -> {W, length(W)} end, Words),
+ adjust(WithLens).
+
+adjust(WLs) ->
+ adjust(0, WLs, []).
+
+adjust(_, [], Ws) ->
+ lists:reverse(tl(Ws));
+adjust(Col, [{W, L}| WLs], Ws) ->
+ case Col + L > 72 of
+ true ->
+ lists:reverse(["\n"| tl(Ws)])
+ ++ adjust(L+1, WLs, [" ", W]);
+ false ->
+ adjust(Col + L + 1, WLs, [" ", W| Ws])
+ end.
+
+
+-spec feature_info(atom()) -> FeatureInfoMap | no_return()
+ when
+ Description :: string(),
+ FeatureInfoMap ::
+ #{description := Description,
+ short := Description,
+ type := type(),
+ keywords := [atom()],
+ status := status(),
+ experimental => release(),
+ approved => release(),
+ permanent => release(),
+ rejected => release()
+ }.
+feature_info(Feature) ->
+ ?VALID_FEATURE(Feature),
+
+ Map = persistent_term:get({?MODULE, feature_specs}),
+ maps:get(Feature, Map).
+
+%% New keywords for a feature. The current set is just for
+%% tests and development.
+-spec keywords(atom()) -> [atom()].
+keywords(Ftr) ->
+ ?VALID_FEATURE(Ftr),
+
+ #{keywords := Keywords} = feature_info(Ftr),
+ Keywords.
+
+%% Internal - Ftr is valid
+keywords(Ftr, Map) ->
+ maps:get(keywords, maps:get(Ftr, Map)).
+
+%% Utilities
+%% Returns list of enabled features and a new keywords function
+%% -spec keyword_fun_add_feature(atom(), fun((atom()) -> boolean())) ->
+%% {'ok', fun((atom()) -> boolean())}
+%% | {'error', error()}.
+keyword_fun(Opts, KeywordFun) ->
+ %% Get items enabling or disabling features, preserving order.
+ IsFtr = fun({enable_feature, _}) -> true;
+ ({disable_feature, _}) -> true;
+ (_) -> false
+ end,
+ FeatureOps = lists:filter(IsFtr, Opts),
+ {AddFeatures, DelFeatures} = collect_features(FeatureOps),
+ %% FIXME check that all features are known at this stage so we
+ %% don't miss out on reporting any unknown features.
+
+ case keyword_fun_add_features(AddFeatures, KeywordFun) of
+ {ok, Fun} ->
+ case keyword_fun_remove_features(DelFeatures, Fun) of
+ {ok, FunX} ->
+ {ok, {AddFeatures -- DelFeatures, FunX}};
+ {error, _} = Error ->
+ %% FIXME We are missing potential incorrect
+ %% features being disabled
+ Error
+ end;
+ {error, _} = Error ->
+ Error
+ end.
+
+%% -spec keyword_fun_add_feature(atom(), fun((atom()) -> boolean())) ->
+%% {'ok', fun((atom()) -> boolean())}
+%% | {'error', error()}.
+keyword_fun(Ind, Feature, Ftrs, KeywordFun) ->
+ case is_valid_feature(Feature) of
+ true ->
+ case Ind of
+ enable ->
+ {ok,
+ add_feature(Feature, KeywordFun),
+ [Feature | Ftrs]};
+ disable ->
+ {ok,
+ remove_feature(Feature, KeywordFun),
+ Ftrs -- [Feature]}
+ end;
+ false ->
+ {error, {?MODULE, {invalid_features, [Feature]}}}
+ end.
+
+%% FIXME Rename this to reflect that it returns a function!
+add_feature(Feature, F) ->
+ Words = keywords(Feature),
+ fun(Word) ->
+ lists:member(Word, Words)
+ orelse F(Word)
+ end.
+
+%% FIXME Rename this to reflect that it returns a function!
+remove_feature(Feature, F) ->
+ Words = keywords(Feature),
+ fun(Word) ->
+ case lists:member(Word, Words) of
+ true -> false;
+ false -> F(Word)
+ end
+ end.
+
+-spec keyword_fun_add_features([atom()], fun((atom()) -> boolean())) ->
+ {'ok', fun((atom()) -> boolean())}
+ | {'error', error()}.
+keyword_fun_add_features(Features, F) ->
+ case lists:all(fun is_valid_feature/1, Features) of
+ true ->
+ {ok, lists:foldl(fun add_feature/2, F, Features)};
+ false ->
+ IsInvalid = fun(Ftr) -> not is_valid_feature(Ftr) end,
+ Invalid = lists:filter(IsInvalid, Features),
+ {error, {?MODULE, {invalid_features, Invalid}}}
+ end.
+
+-spec keyword_fun_remove_features([atom()], fun((atom()) -> boolean())) ->
+ {'ok', fun((atom()) -> boolean())}
+ | {'error', error()}.
+keyword_fun_remove_features(Features, F) ->
+ case lists:all(fun is_valid_feature/1, Features) of
+ true ->
+ {ok, lists:foldl(fun remove_feature/2, F, Features)};
+ false ->
+ IsInvalid = fun(Ftr) -> not is_valid_feature(Ftr) end,
+ Invalid = lists:filter(IsInvalid, Features),
+ {error, {?MODULE, {invalid_features, Invalid}}}
+ end.
+
+format_error(Reason, [{_M, _F, _Args, Info}| _St]) ->
+ ErrorInfo = proplists:get_value(error_info, Info, #{}),
+ ErrorMap = maps:get(cause, ErrorInfo),
+ ErrorMap#{reason => io_lib:format("~p: ~p", [?MODULE, Reason])}.
+
+format_error({invalid_features, Features}) ->
+ Fmt = fun F([Ftr]) -> io_lib:fwrite("'~p'", [Ftr]);
+ F([Ftr1, Ftr2]) ->
+ io_lib:fwrite("'~p' and '~p'", [Ftr1, Ftr2]);
+ F([Ftr| Ftrs]) ->
+ io_lib:fwrite("'~p', ~s", [Ftr, F(Ftrs)])
+ end,
+ case Features of
+ [Ftr] ->
+ io_lib:fwrite("the feature ~s does not exist.", [Fmt([Ftr])]);
+ Ftrs ->
+ io_lib:fwrite("the features ~s do not exist.", [Fmt(Ftrs)])
+ end.
+
+%% Hold the state of which features are currently enabled.
+%% This is almost static, so we go for an almost permanent state,
+%% i.e., use persistent_term.
+init_features() ->
+ Map = init_specs(),
+
+ persistent_term:put({?MODULE, enabled_features}, []),
+ persistent_term:put({?MODULE, keywords}, []),
+
+ RawOps = lists:filter(fun({Tag, _}) ->
+ Tag == 'enable-feature'
+ orelse Tag == 'disable-feature';
+ (_) -> false
+ end,
+ init:get_arguments()),
+
+ Cnv = fun('enable-feature') -> enable_feature;
+ ('disable-feature') -> disable_feature
+ end,
+
+ FeatureOps = lists:append(lists:map(fun({Tag, Strings}) ->
+ lists:map(fun(S) ->
+ {Tag, S} end,
+ Strings)
+ end,
+ RawOps)),
+
+ %% Convert failure, e.g., too long string for atom, to not
+ %% being a valid feature.
+ F = fun({Tag, String}) ->
+ try
+ Atom = list_to_atom(String),
+ case is_valid_feature(Atom) of
+ true -> {true, {Cnv(Tag), Atom}};
+ false when Atom == all ->
+ {true, {Cnv(Tag), Atom}};
+ false -> false
+ end
+ catch
+ _ -> false
+ end
+ end,
+ FOps = lists:filtermap(F, FeatureOps),
+ {Features, _} = collect_features(FOps),
+ {Enabled, Keywords} =
+ lists:foldl(fun(Ftr, {Ftrs, Keys}) ->
+ case lists:member(Ftr, Ftrs) of
+ true ->
+ {Ftrs, Keys};
+ false ->
+ {[Ftr| Ftrs],
+ keywords(Ftr, Map) ++ Keys}
+ end
+ end,
+ {[], []},
+ Features),
+
+ %% Save state
+ enabled_features(Enabled),
+ set_keywords(Keywords),
+ persistent_term:put({?MODULE, init_done}, true),
+ ok.
+
+init_specs() ->
+ Specs = case os:getenv("OTP_TEST_FEATURES") of
+ "true" -> test_features();
+ _ -> feature_specs()
+ end,
+ persistent_term:put({?MODULE, feature_specs}, Specs),
+ Specs.
+
+ensure_init() ->
+ case persistent_term:get({?MODULE, init_done}, false) of
+ true -> ok;
+ false ->
+ init_features()
+ end.
+
+%% FIXME - remove this. It should not be available at runtime. This
+%% is all done by the init code.
+enable_feature(Feature) ->
+ ?VALID_FEATURE(Feature),
+
+ Features = enabled_features(),
+ case lists:member(Feature, Features) of
+ true ->
+ %% already there, maybe raise an error
+ Features;
+ false ->
+ NewFeatures = [Feature| Features],
+ enabled_features(NewFeatures),
+ Keywords = keywords(),
+ New = keywords(Feature),
+ set_keywords(New ++ Keywords),
+ NewFeatures
+ end.
+
+disable_feature(Feature) ->
+ ?VALID_FEATURE(Feature),
+
+ Features = enabled_features(),
+ case lists:member(Feature, Features) of
+ true ->
+ NewFeatures = Features -- [Feature],
+ enabled_features(NewFeatures),
+ Keywords = keywords(),
+ Rem = keywords(Feature),
+ set_keywords(Keywords -- Rem),
+ NewFeatures;
+ false ->
+ %% Not there, possibly raise an error
+ Features
+ end.
+
+enabled_features() ->
+ ensure_init(),
+ persistent_term:get({?MODULE, enabled_features}).
+
+enabled_features(Ftrs) ->
+ persistent_term:put({?MODULE, enabled_features}, Ftrs).
+
+keywords() ->
+ ensure_init(),
+ persistent_term:get({?MODULE, keywords}).
+
+set_keywords(Words) ->
+ persistent_term:put({?MODULE, keywords}, Words).
+
+
+-spec load_allowed(binary()) -> boolean().
+load_allowed(Binary) ->
+ case erts_internal:beamfile_chunk(Binary, "Meta") of
+ undefined ->
+ true;
+ Meta ->
+ MetaData = erlang:binary_to_term(Meta),
+ case proplists:get_value(enabled_features, MetaData) of
+ undefined ->
+ true;
+ Used ->
+ Enabled = enabled_features(),
+ lists:all(fun(UFtr) ->
+ lists:member(UFtr, Enabled)
+ end,
+ Used)
+ end
+ end.
+
+
+%% Return features used by module or beam file
+features_used(Module) when is_atom(Module) ->
+ case code:get_object_code(Module) of
+ error ->
+ not_found;
+ {_Mod, Bin, _Fname} ->
+ features_in(Bin)
+ end;
+features_used(FName) when is_list(FName) ->
+ features_in(FName).
+
+features_in(NameOrBin) ->
+ case beam_lib:chunks(NameOrBin, ["Meta"], [allow_missing_chunks]) of
+ {ok, {_, [{_, missing_chunk}]}} ->
+ [];
+ {ok, {_, [{_, Meta}]}} ->
+ MetaData = erlang:binary_to_term(Meta),
+ proplists:get_value(enabled_features, MetaData, []);
+ _ ->
+ not_found
+ end.
+
+approved_features() ->
+ [Ftr || Ftr <- features(),
+ maps:get(status, feature_info(Ftr)) == approved].
+
+permanent_features() ->
+ [Ftr || Ftr <- features(),
+ maps:get(status, feature_info(Ftr)) == permanent].
+
+%% Interpret feature ops (enable or disable) to build the full set of
+%% features. The meta feature 'all' is expanded to all known
+%% features.
+collect_features(FOps) ->
+ %% Features enabled by default
+ Enabled = approved_features() ++ permanent_features(),
+ collect_features(FOps, Enabled, []).
+
+collect_features([], Add, Del) ->
+ {Add, Del};
+collect_features([{enable_feature, all}| FOps], Add, _Del) ->
+ All = features(),
+ Add1 = lists:foldl(fun add_ftr/2, Add, All),
+ collect_features(FOps, Add1, []);
+collect_features([{enable_feature, Feature}| FOps], Add, Del) ->
+ collect_features(FOps, add_ftr(Feature, Add), Del -- [Feature]);
+collect_features([{disable_feature, all}| FOps], _Add, Del) ->
+ %% Start over
+ All = features(),
+ collect_features(FOps, [], Del -- All);
+collect_features([{disable_feature, Feature}| FOps], Add, Del) ->
+ collect_features(FOps, Add -- [Feature],
+ add_ftr(Feature, Del)).
+
+add_ftr(F, []) ->
+ [F];
+add_ftr(F, [F| _] = Fs) ->
+ Fs;
+add_ftr(F, [F0| Fs]) ->
+ [F0| add_ftr(F, Fs)].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Test features - not present in a release
+test_features() ->
+ #{ifn_expr =>
+ #{short => "New expression `ifn cond -> body end`",
+ description =>
+ "Inclusion of expression `ifn cond -> body end`, which "
+ "evaluates `body` when cond is false. This is a truly "
+ "experimental feature, present only to show and use the "
+ "support for experimental features. Not extensively tested. "
+ "Implementated by a transformation in the parser.",
+ status => experimental,
+ experimental => 24,
+ keywords => ['ifn'],
+ type => extension},
+ ifnot_expr =>
+ #{short => "New expression `ifnot cond -> body end`",
+ description =>
+ "Inclusion of expression `ifnot cond -> body end`, which "
+ "evaluates `body` when cond is false. This is a truly "
+ "experimental feature, present only to show and use the "
+ "support for experimental features. Not extensively tested. "
+ "Similar to ifn_expr, but with a deeper implementation.",
+ status => experimental,
+ experimental => 25,
+ keywords => ['ifnot'],
+ type => extension},
+ unless_expr =>
+ #{short => "`unless <cond> -> <bodby> end",
+ description =>
+ "Introduction of new expression `unless <cond> -> <body> end."
+ " Truly experimental.",
+ status => experimental,
+ experimental => 25,
+ keywords => ['unless'],
+ type => extension},
+ maps =>
+ #{short => "Add maps as new data type",
+ description => "Add new low data type maps with syntactic "
+ "support in Erlang as well native support in the beam. "
+ "Insert, lookup and delete are asymptotically constant.",
+ status => permanent,
+ experimental => 17,
+ approved => 18,
+ permanent => 19,
+ keywords => [],
+ type => extension},
+ cond_expr =>
+ #{short => "Introduce general Lisp style conditional",
+ description =>
+ "Finally complement the painfully broken `if` "
+ "with a general conditional as in Lisp from the days of old.",
+ status => approved,
+ experimental => 24,
+ approved => 25,
+ keywords => [],
+ type => extension},
+ while_expr =>
+ #{short => "Introduce strange iterative expressions",
+ description =>
+ "Introduce looping constructs, with seemingly "
+ "destructive assignment and vague semantics.",
+ status => experimental,
+ experimental => 25,
+ keywords => ['while', 'until'],
+ type => extension}}.
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index 84ae2f6bf9..3ae2bc41c4 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -199,6 +199,9 @@ format_error(invalid_call) ->
format_error(invalid_record) ->
"invalid record expression";
+format_error({future_feature, Ftr, Atom}) ->
+ io_lib:format("atom '~p' is reserved in the experimental feature '~p'",
+ [Atom, Ftr]);
format_error({attribute,A}) ->
io_lib:format("attribute ~tw after function definitions", [A]);
format_error({missing_qlc_hrl,A}) ->
@@ -588,6 +591,7 @@ module(Forms, FileName) ->
ErrorInfo :: error_info()).
module(Forms, FileName, Opts0) ->
+ %% FIXME Hmm, this is not coherent with the semantics of features
%% We want the options given on the command line to take
%% precedence over options in the module.
Opts = compiler_options(Forms) ++ Opts0,
@@ -658,7 +662,10 @@ start(File, Opts) ->
true, Opts)},
{nif_inline,
bool_option(warn_nif_inline, nowarn_nif_inline,
- true, Opts)}
+ true, Opts)},
+ {keyword_warning,
+ bool_option(warn_keywords, nowarn_keywords,
+ false, Opts)}
],
Enabled1 = [Category || {Category,true} <- Enabled0],
Enabled = ordsets:from_list(Enabled1),
@@ -4164,7 +4171,22 @@ test_overriden_by_local(Anno, OldTest, Arity, St) ->
%% keyword_warning(Anno, Atom, State) -> State.
%% Add warning for atoms that will be reserved keywords in the future.
%% (Currently, no such keywords to warn for.)
-keyword_warning(_Anno, _A, St) -> St.
+keyword_warning(Anno, Atom, St) ->
+ case is_warn_enabled(keyword_warning, St) of
+ true ->
+ Ftrs = erl_features:features(),
+ Reserved =
+ fun(Ftr) ->
+ lists:member(Atom, erl_features:keywords(Ftr))
+ end,
+ case lists:filter(Reserved, Ftrs) of
+ [] -> St;
+ [Ftr] ->
+ add_warning(Anno, {future_feature, Ftr, Atom}, St)
+ end;
+ false ->
+ St
+ end.
%% format_function(Anno, ModName, FuncName, [Arg], State) -> State.
%% Add warning for bad calls to io:fwrite/format functions.
diff --git a/lib/stdlib/src/erl_scan.erl b/lib/stdlib/src/erl_scan.erl
index d1c3b94cf3..a30747b5e5 100644
--- a/lib/stdlib/src/erl_scan.erl
+++ b/lib/stdlib/src/erl_scan.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2022. 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.
@@ -52,7 +52,8 @@
%%% External exports
-export([string/1,string/2,string/3,tokens/3,tokens/4,
- format_error/1,reserved_word/1]).
+ format_error/1,reserved_word/1,
+ f_reserved_word/1]).
-export([column/1,end_location/1,line/1,location/1,text/1,
category/1,symbol/1]).
@@ -1227,32 +1228,43 @@ tabs(8) -> "\t\t\t\t\t\t\t\t";
tabs(9) -> "\t\t\t\t\t\t\t\t\t";
tabs(10) -> "\t\t\t\t\t\t\t\t\t\t".
+%% Dynamic version of reserved_word that knows about the possibility
+%% that enabled features might change the set of reserved words.
-spec reserved_word(Atom :: atom()) -> boolean().
-reserved_word('after') -> true;
-reserved_word('begin') -> true;
-reserved_word('case') -> true;
-reserved_word('try') -> true;
-reserved_word('cond') -> true;
-reserved_word('catch') -> true;
-reserved_word('andalso') -> true;
-reserved_word('orelse') -> true;
-reserved_word('end') -> true;
-reserved_word('fun') -> true;
-reserved_word('if') -> true;
-reserved_word('let') -> true;
-reserved_word('of') -> true;
-reserved_word('receive') -> true;
-reserved_word('when') -> true;
-reserved_word('bnot') -> true;
-reserved_word('not') -> true;
-reserved_word('div') -> true;
-reserved_word('rem') -> true;
-reserved_word('band') -> true;
-reserved_word('and') -> true;
-reserved_word('bor') -> true;
-reserved_word('bxor') -> true;
-reserved_word('bsl') -> true;
-reserved_word('bsr') -> true;
-reserved_word('or') -> true;
-reserved_word('xor') -> true;
-reserved_word(_) -> false.
+reserved_word(Atom) ->
+ case f_reserved_word(Atom) of
+ true -> true;
+ false ->
+ lists:member(Atom, erl_features:keywords())
+ end.
+
+%% Static version of reserved_words. These represent the fixed set of
+%% reserved words.
+f_reserved_word('after') -> true;
+f_reserved_word('begin') -> true;
+f_reserved_word('case') -> true;
+f_reserved_word('try') -> true;
+f_reserved_word('cond') -> true;
+f_reserved_word('catch') -> true;
+f_reserved_word('andalso') -> true;
+f_reserved_word('orelse') -> true;
+f_reserved_word('end') -> true;
+f_reserved_word('fun') -> true;
+f_reserved_word('if') -> true;
+f_reserved_word('let') -> true;
+f_reserved_word('of') -> true;
+f_reserved_word('receive') -> true;
+f_reserved_word('when') -> true;
+f_reserved_word('bnot') -> true;
+f_reserved_word('not') -> true;
+f_reserved_word('div') -> true;
+f_reserved_word('rem') -> true;
+f_reserved_word('band') -> true;
+f_reserved_word('and') -> true;
+f_reserved_word('bor') -> true;
+f_reserved_word('bxor') -> true;
+f_reserved_word('bsl') -> true;
+f_reserved_word('bsr') -> true;
+f_reserved_word('or') -> true;
+f_reserved_word('xor') -> true;
+f_reserved_word(_) -> false.
diff --git a/lib/stdlib/src/stdlib.app.src b/lib/stdlib/src/stdlib.app.src
index 9962fef931..fbf0e73149 100644
--- a/lib/stdlib/src/stdlib.app.src
+++ b/lib/stdlib/src/stdlib.app.src
@@ -46,6 +46,7 @@
erl_error,
erl_eval,
erl_expand_records,
+ erl_features,
erl_internal,
erl_lint,
erl_parse,
diff --git a/lib/stdlib/test/epp_SUITE.erl b/lib/stdlib/test/epp_SUITE.erl
index 30437de302..8e800f8112 100644
--- a/lib/stdlib/test/epp_SUITE.erl
+++ b/lib/stdlib/test/epp_SUITE.erl
@@ -1609,8 +1609,9 @@ encoding(Config) when is_list(Config) ->
epp_parse_file(ErlFile, [{default_encoding,latin1}]),
{ok,[{attribute,1,file,_},
{attribute,1,module,encoding},
- {eof,3}],[{encoding,none}]} =
+ {eof,3}],Extra0} =
epp_parse_file(ErlFile, [{default_encoding,latin1},extra]),
+ none = proplists:get_value(encoding, Extra0),
%% Try a latin-1 file with encoding given in a comment.
C2 = <<"-module(encoding).
@@ -1632,16 +1633,20 @@ encoding(Config) when is_list(Config) ->
epp_parse_file(ErlFile, [{default_encoding,utf8}]),
{ok,[{attribute,1,file,_},
{attribute,1,module,encoding},
- {eof,4}],[{encoding,latin1}]} =
+ {eof,4}],Extra1} =
epp_parse_file(ErlFile, [extra]),
+ latin1 = proplists:get_value(encoding, Extra1),
+
{ok,[{attribute,1,file,_},
{attribute,1,module,encoding},
- {eof,4}],[{encoding,latin1}]} =
+ {eof,4}],Extra2} =
epp_parse_file(ErlFile, [{default_encoding,latin1},extra]),
+ latin1 = proplists:get_value(encoding, Extra2),
{ok,[{attribute,1,file,_},
{attribute,1,module,encoding},
- {eof,4}],[{encoding,latin1}]} =
+ {eof,4}],Extra3} =
epp_parse_file(ErlFile, [{default_encoding,utf8},extra]),
+ latin1 = proplists:get_value(encoding, Extra3),
ok.
extends(Config) ->
--
2.34.1