diff --git a/lib/elixir/lib/inspect.ex b/lib/elixir/lib/inspect.ex index 0f779d1bf6..1a4336240d 100644 --- a/lib/elixir/lib/inspect.ex +++ b/lib/elixir/lib/inspect.ex @@ -409,11 +409,7 @@ end defimpl Inspect, for: Any do defmacro __deriving__(module, struct, options) do - fields = - struct - |> Map.drop([:__exception__, :__struct__]) - |> Map.keys() - + fields = Map.keys(struct) -- [:__exception__, :__struct__] only = Keyword.get(options, :only, fields) except = Keyword.get(options, :except, []) @@ -469,3 +465,24 @@ defimpl Inspect, for: Any do container_doc(open, map, close, opts, &Inspect.List.keyword/2, separator: sep, break: :strict) end end + +require Protocol + +Protocol.derive( + Inspect, + Macro.Env, + only: [ + :module, + :file, + :line, + :function, + :context, + :aliases, + :requires, + :functions, + :macros, + :macro_aliases, + :context_modules, + :lexical_tracker + ] +) diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex index 3977023495..9edd84504f 100644 --- a/lib/elixir/lib/kernel.ex +++ b/lib/elixir/lib/kernel.ex @@ -3905,7 +3905,7 @@ defmodule Kernel do end # We reimplement Macro.Env.vars/1 due to bootstrap concerns. - module_vars = module_vars(:maps.keys(env.current_vars), 0) + module_vars = module_vars(:maps.keys(elem(env.current_vars, 0)), 0) quote do unquote(with_alias) diff --git a/lib/elixir/lib/macro/env.ex b/lib/elixir/lib/macro/env.ex index ed25892d6f..e5a2770ada 100644 --- a/lib/elixir/lib/macro/env.ex +++ b/lib/elixir/lib/macro/env.ex @@ -43,7 +43,6 @@ defmodule Macro.Env do relied on. To get a list of all variables, see `vars/1`: * `current_vars` - * `unused_vars` * `prematch_vars` * `contextual_vars` @@ -69,9 +68,11 @@ defmodule Macro.Env do @typep vars :: [variable] @typep var_type :: :term @typep var_version :: non_neg_integer - @typep unused_vars :: %{optional({variable, var_version}) => non_neg_integer | false} - @typep current_vars :: %{optional(variable) => {var_version, var_type}} - @typep prematch_vars :: current_vars | :warn | :raise | :pin | :apply + @typep current_vars :: + {%{optional(variable) => {var_version, var_type}}, + %{optional({variable, var_version}) => non_neg_integer | false}} + @typep prematch_vars :: + %{optional(variable) => {var_version, var_type}} | :warn | :raise | :pin | :apply @typep contextual_vars :: [atom] @type t :: %{ @@ -88,7 +89,6 @@ defmodule Macro.Env do macro_aliases: macro_aliases, context_modules: context_modules, vars: vars, - unused_vars: unused_vars, current_vars: current_vars, prematch_vars: prematch_vars, lexical_tracker: lexical_tracker, @@ -111,8 +111,7 @@ defmodule Macro.Env do macro_aliases: [], context_modules: [], vars: [], - unused_vars: %{}, - current_vars: %{}, + current_vars: {%{}, %{}}, prematch_vars: :warn, lexical_tracker: nil, contextual_vars: [] @@ -135,7 +134,7 @@ defmodule Macro.Env do @spec vars(t) :: [variable] def vars(env) - def vars(%{__struct__: Macro.Env, current_vars: current_vars}) do + def vars(%{__struct__: Macro.Env, current_vars: {current_vars, _}}) do Map.keys(current_vars) end @@ -146,7 +145,7 @@ defmodule Macro.Env do @spec has_var?(t, variable) :: boolean() def has_var?(env, var) - def has_var?(%{__struct__: Macro.Env, current_vars: current_vars}, var) do + def has_var?(%{__struct__: Macro.Env, current_vars: {current_vars, _}}, var) do Map.has_key?(current_vars, var) end @@ -169,7 +168,7 @@ defmodule Macro.Env do env end - def to_match(%{__struct__: Macro.Env, current_vars: vars} = env) do + def to_match(%{__struct__: Macro.Env, current_vars: {vars, _}} = env) do %{env | context: :match, prematch_vars: vars} end diff --git a/lib/elixir/src/elixir_bitstring.erl b/lib/elixir/src/elixir_bitstring.erl index 662f8f922c..dc4e15844e 100644 --- a/lib/elixir/src/elixir_bitstring.erl +++ b/lib/elixir/src/elixir_bitstring.erl @@ -276,14 +276,15 @@ validate_spec_arg(_Meta, _Key, _Value, _E, _OriginalE) -> ok. is_valid_spec_arg_var(Var, E, #{context := match} = OriginalE) -> - case ?key(OriginalE, prematch_vars) of - #{Var := _} -> - true; - _ -> - maps:is_key(Var, ?key(E, current_vars)) andalso - not maps:is_key(Var, ?key(OriginalE, current_vars)) + case OriginalE of + #{prematch_vars := #{Var := _}} -> true; + _ -> is_var(Var, E) andalso not is_var(Var, OriginalE) end; -is_valid_spec_arg_var(_Var, _E, _OriginalE) -> true. +is_valid_spec_arg_var(_Var, _E, _OriginalE) -> + true. + +is_var(Var, #{current_vars := {Current, _}}) -> + maps:is_key(Var, Current). validate_size_required(Meta, true, default, Type, default, E) when Type == binary; Type == bitstring -> form_error(Meta, E, ?MODULE, unsized_binary); diff --git a/lib/elixir/src/elixir_clauses.erl b/lib/elixir/src/elixir_clauses.erl index c76dd81c32..e197ebb2ab 100644 --- a/lib/elixir/src/elixir_clauses.erl +++ b/lib/elixir/src/elixir_clauses.erl @@ -9,7 +9,7 @@ match(Fun, Expr, #{context := match} = E) -> Fun(Expr, E); -match(Fun, Expr, #{context := Context, prematch_vars := Prematch, current_vars := Current} = E) -> +match(Fun, Expr, #{context := Context, prematch_vars := Prematch, current_vars := {Current, _}} = E) -> {EExpr, EE} = Fun(Expr, E#{context := match, prematch_vars := Current}), {EExpr, EE#{context := Context, prematch_vars := Prematch}}. diff --git a/lib/elixir/src/elixir_env.erl b/lib/elixir/src/elixir_env.erl index 283ff4ec6a..d5649510fb 100644 --- a/lib/elixir/src/elixir_env.erl +++ b/lib/elixir/src/elixir_env.erl @@ -21,8 +21,7 @@ new() -> macro_aliases => [], %% keep aliases defined inside a macro context_modules => [], %% modules defined in the current context vars => [], %% a set of defined variables - unused_vars => #{}, %% a map with unused variables - current_vars => #{}, %% a map with current variables + current_vars => {#{}, #{}}, %% a tuple with maps of current and unused variables prematch_vars => warn, %% behaviour outside and inside matches lexical_tracker => nil, %% holds the lexical tracker PID contextual_vars => []}. %% holds available contextual variables @@ -34,7 +33,7 @@ linify(#{} = Env) -> with_vars(Env, Vars) -> CurrentVars = maps:from_list([{Var, 0} || Var <- Vars]), - Env#{vars := Vars, current_vars := CurrentVars}. + Env#{vars := Vars, current_vars := {CurrentVars, #{}}}. env_to_scope(#{context := Context}) -> #elixir_erl{context=Context}. @@ -46,28 +45,27 @@ env_to_scope_with_vars(Env, Vars) -> }. reset_vars(Env) -> - Env#{vars := [], current_vars := #{}, unused_vars := #{}}. + Env#{vars := [], current_vars := {#{}, #{}}}. %% SCOPE MERGING %% Receives two scopes and return a new scope based on the second %% with their variables merged. %% Unrolled for performance reasons. -mergev(#{unused_vars := U1, current_vars := C1}, - #{unused_vars := U2, current_vars := C2} = E2) -> +mergev(#{current_vars := {C1, U1}}, #{current_vars := {C2, U2}} = E2) -> if C1 =/= C2 -> if U1 =/= U2 -> C = merge_vars(C1, C2), - E2#{vars := maps:keys(C), unused_vars := merge_vars(U1, U2), current_vars := C}; + E2#{vars := maps:keys(C), current_vars := {C, merge_vars(U1, U2)}}; true -> C = merge_vars(C1, C2), - E2#{vars := maps:keys(C), current_vars := C} + E2#{vars := maps:keys(C), current_vars := {C, U2}} end; U1 =/= U2 -> - E2#{unused_vars := merge_vars(U1, U2)}; + E2#{current_vars := {C2, merge_vars(U1, U2)}}; true -> E2 @@ -77,18 +75,18 @@ mergev(#{unused_vars := U1, current_vars := C1}, %% keeping the variables from the first (imports %% and everything else are passed forward). %% Unrolled for performance reasons. -mergea(#{unused_vars := U1, current_vars := C1, vars := V1}, - #{unused_vars := U2, current_vars := C2} = E2) -> +mergea(#{current_vars := {C1, U1}, vars := V1}, + #{current_vars := {C2, U2}} = E2) -> if C1 =/= C2 -> if U1 =/= U2 -> - E2#{vars := V1, unused_vars := U1, current_vars := C1}; + E2#{vars := V1, current_vars := {C1, U1}}; true -> - E2#{vars := V1, current_vars := C1} + E2#{vars := V1, current_vars := {C1, U2}} end; U1 =/= U2 -> - E2#{unused_vars := U1}; + E2#{current_vars := {C2, U1}}; true -> E2 end. @@ -104,13 +102,13 @@ merge_vars(V1, V2) -> %% UNUSED VARS -check_unused_vars(#{unused_vars := Unused} = E) -> +check_unused_vars(#{current_vars := {_, Unused}} = E) -> [elixir_errors:form_warn([{line, Line}], E, ?MODULE, {unused_var, Name}) || {{{Name, _}, _}, Line} <- maps:to_list(Unused), Line /= false, not_underscored(Name)], E. -merge_and_check_unused_vars(#{unused_vars := Unused} = E, #{unused_vars := ClauseUnused}) -> - E#{unused_vars := merge_and_check_unused_vars(Unused, ClauseUnused, E)}. +merge_and_check_unused_vars(#{current_vars := {C, Unused}} = E, #{current_vars := {_, ClauseUnused}}) -> + E#{current_vars := {C, merge_and_check_unused_vars(Unused, ClauseUnused, E)}}. merge_and_check_unused_vars(Unused, ClauseUnused, E) -> maps:fold(fun(Key, ClauseValue, Acc) -> diff --git a/lib/elixir/src/elixir_expand.erl b/lib/elixir/src/elixir_expand.erl index 5470603fca..16cabd6f68 100644 --- a/lib/elixir/src/elixir_expand.erl +++ b/lib/elixir/src/elixir_expand.erl @@ -310,17 +310,19 @@ expand({super, Meta, Args}, E) when is_list(Args) -> %% Vars expand({'^', Meta, [Arg]}, #{context := match} = E) -> - #{current_vars := Current, prematch_vars := Prematch} = E, + #{current_vars := {Current, Unused}, prematch_vars := Prematch} = E, %% We need to rollback to a no match context. - NoMatchE = E#{context := nil, current_vars := Prematch, prematch_vars := pin}, + NoMatchE = E#{context := nil, current_vars := {Prematch, Unused}, prematch_vars := pin}, case expand(Arg, NoMatchE) of {{Name, _, Kind} = Var, ExpandedE} when is_atom(Name), is_atom(Kind) -> + #{current_vars := {NewPrematch, NewUnused}} = ExpandedE, + EA = ExpandedE#{ context := match, - current_vars := Current, - prematch_vars := ?key(ExpandedE, current_vars) + current_vars := {Current, NewUnused}, + prematch_vars := NewPrematch }, {{'^', Meta, [Var]}, EA}; @@ -336,7 +338,7 @@ expand({'_', Meta, Kind}, E) when is_atom(Kind) -> form_error(Meta, E, ?MODULE, unbound_underscore); expand({Name, Meta, Kind} = Var, #{context := match} = E) when is_atom(Name), is_atom(Kind) -> - #{unused_vars := Unused, current_vars := Current, prematch_vars := Prematch} = E, + #{current_vars := {Current, Unused}, prematch_vars := Prematch} = E, Pair = {Name, elixir_utils:var_context(Meta, Kind)}, PrematchVersion = var_version(Prematch, Pair), @@ -346,25 +348,25 @@ expand({Name, Meta, Kind} = Var, #{context := match} = E) when is_atom(Name), is #{Pair := PrematchVersion} -> NewUnused = var_unused(Pair, Meta, PrematchVersion + 1, Unused), NewCurrent = Current#{Pair => PrematchVersion + 1}, - E#{unused_vars := NewUnused, current_vars := NewCurrent}; + E#{current_vars := {NewCurrent, NewUnused}}; %% Variable was already overriden #{Pair := CurrentVersion} -> maybe_warn_underscored_var_repeat(Meta, Name, Kind, E), NewUnused = Unused#{{Pair, CurrentVersion} => false}, - E#{unused_vars := NewUnused}; + E#{current_vars := {Current, NewUnused}}; %% Variable defined for the first time _ -> NewVars = ordsets:add_element(Pair, ?key(E, vars)), NewUnused = var_unused(Pair, Meta, 0, Unused), NewCurrent = Current#{Pair => 0}, - E#{vars := NewVars, unused_vars := NewUnused, current_vars := NewCurrent} + E#{vars := NewVars, current_vars := {NewCurrent, NewUnused}} end, {Var, EE}; expand({Name, Meta, Kind} = Var, E) when is_atom(Name), is_atom(Kind) -> - #{unused_vars := Unused, current_vars := Current} = E, + #{current_vars := {Current, Unused}} = E, Pair = {Name, elixir_utils:var_context(Meta, Kind)}, case Current of @@ -374,7 +376,7 @@ expand({Name, Meta, Kind} = Var, E) when is_atom(Name), is_atom(Kind) -> case Unused of #{UnusedKey := Entry} when Entry /= false -> - {Var, E#{unused_vars := Unused#{UnusedKey := false}}}; + {Var, E#{current_vars := {Current, Unused#{UnusedKey := false}}}}; _ -> {Var, E} end; @@ -484,12 +486,12 @@ expand(Other, E) -> %% Helpers -escape_env_entries(Meta, #{unused_vars := Unused, current_vars := Current} = Env0) -> +escape_env_entries(Meta, #{current_vars := {Current, Unused}} = Env0) -> Env1 = case Env0 of #{function := nil} -> Env0; _ -> Env0#{lexical_tracker := nil} end, - Env2 = Env1#{unused_vars := escape_map(Unused), current_vars := escape_map(Current)}, + Env2 = Env1#{current_vars := {escape_map(Current), escape_map(Unused)}}, Env3 = elixir_env:linify({?line(Meta), Env2}), Env3. @@ -754,7 +756,7 @@ assert_no_ambiguous_op(Name, Meta, [Arg], E) -> {ambiguous_op, Kind} -> Pair = {Name, Kind}, case ?key(E, current_vars) of - #{Pair := _} -> + {#{Pair := _}, _} -> form_error(Meta, E, ?MODULE, {op_ambiguity, Name, Arg}); _ -> ok diff --git a/lib/elixir/src/elixir_module.erl b/lib/elixir/src/elixir_module.erl index 1afd8faa42..e67a5ec1b4 100644 --- a/lib/elixir/src/elixir_module.erl +++ b/lib/elixir/src/elixir_module.erl @@ -56,13 +56,13 @@ next_counter(Module) -> compile(Module, _Block, _Vars, #{line := Line, file := File}) when Module == nil; is_boolean(Module) -> elixir_errors:form_error([{line, Line}], File, ?MODULE, {invalid_module, Module}); -compile(Module, Block, Vars, #{line := Line} = Env) when is_atom(Module) -> +compile(Module, Block, Vars, #{line := Line, current_vars := {Current, _}} = Env) when is_atom(Module) -> %% In case we are generating a module from inside a function, %% we get rid of the lexical tracker information as, at this %% point, the lexical tracker process is long gone. LexEnv = case ?key(Env, function) of - nil -> Env#{module := Module, unused_vars := #{}}; - _ -> Env#{lexical_tracker := nil, function := nil, module := Module, unused_vars := #{}} + nil -> Env#{module := Module, current_vars := {Current, #{}}}; + _ -> Env#{lexical_tracker := nil, function := nil, module := Module, current_vars := {Current, #{}}} end, case ?key(LexEnv, lexical_tracker) of