1defmodule Macro.Env do
2  @moduledoc """
3  A struct that holds compile time environment information.
4
5  The current environment can be accessed at any time as
6  `__ENV__/0`. Inside macros, the caller environment can be
7  accessed as `__CALLER__/0`.
8
9  An instance of `Macro.Env` must not be modified by hand. If you need to
10  create a custom environment to pass to `Code.eval_quoted/3`, use the
11  following trick:
12
13      def make_custom_env do
14        import SomeModule, only: [some_function: 2]
15        alias A.B.C
16        __ENV__
17      end
18
19  You may then call `make_custom_env()` to get a struct with the desired
20  imports and aliases included.
21
22  It contains the following fields:
23
24    * `aliases` -  a list of two-element tuples, where the first
25      element is the aliased name and the second one the actual name
26    * `context` - the context of the environment; it can be `nil`
27      (default context), `:guard` (inside a guard) or `:match` (inside a match)
28    * `context_modules` - a list of modules defined in the current context
29    * `file` - the current file name as a binary
30    * `function` - a tuple as `{atom, integer}`, where the first
31      element is the function name and the second its arity; returns
32      `nil` if not inside a function
33    * `functions` - a list of functions imported from each module
34    * `line` - the current line as an integer
35    * `macro_aliases` - a list of aliases defined inside the current macro
36    * `macros` - a list of macros imported from each module
37    * `module` - the current module name
38    * `requires` - the list of required modules
39
40  The following fields are private to Elixir's macro expansion mechanism and
41  must not be accessed directly:
42
43    * `contextual_vars`
44    * `current_vars`
45    * `lexical_tracker`
46    * `prematch_vars`
47    * `tracers`
48    * `unused_vars`
49
50  The following fields are deprecated and must not be accessed or relied on:
51
52    * `vars` - a list keeping all defined variables as `{var, context}`
53
54  """
55
56  @type aliases :: [{module, module}]
57  @type context :: :match | :guard | nil
58  @type context_modules :: [module]
59  @type file :: binary
60  @type functions :: [{module, [name_arity]}]
61  @type lexical_tracker :: pid | nil
62  @type line :: non_neg_integer
63  @type macro_aliases :: [{module, {term, module}}]
64  @type macros :: [{module, [name_arity]}]
65  @type name_arity :: {atom, arity}
66  @type requires :: [module]
67  @type variable :: {atom, atom | term}
68
69  @typep contextual_vars :: [atom]
70  @typep current_vars ::
71           {%{optional(variable) => {var_version, var_type}},
72            %{optional(variable) => {var_version, var_type}} | false}
73  @typep unused_vars ::
74           {%{optional({atom, var_version}) => non_neg_integer | false}, non_neg_integer}
75  @typep prematch_vars ::
76           {%{optional(variable) => {var_version, var_type}}, non_neg_integer}
77           | :warn
78           | :raise
79           | :pin
80           | :apply
81  @typep tracers :: [module]
82  @typep var_type :: :term
83  @typep var_version :: non_neg_integer
84  @typep vars :: [variable]
85
86  @type t :: %{
87          __struct__: __MODULE__,
88          aliases: aliases,
89          context: context,
90          context_modules: context_modules,
91          contextual_vars: contextual_vars,
92          current_vars: current_vars,
93          file: file,
94          function: name_arity | nil,
95          functions: functions,
96          lexical_tracker: lexical_tracker,
97          line: line,
98          macro_aliases: macro_aliases,
99          macros: macros,
100          module: module,
101          prematch_vars: prematch_vars,
102          unused_vars: unused_vars,
103          requires: requires,
104          tracers: tracers,
105          vars: vars
106        }
107
108  # Define the __struct__ callbacks by hand for bootstrap reasons.
109  # TODO: Remove :vars field on v2.0
110  @doc false
111  def __struct__ do
112    %{
113      __struct__: __MODULE__,
114      aliases: [],
115      context: nil,
116      context_modules: [],
117      contextual_vars: [],
118      current_vars: {%{}, %{}},
119      file: "nofile",
120      function: nil,
121      functions: [],
122      lexical_tracker: nil,
123      line: 0,
124      macro_aliases: [],
125      macros: [],
126      module: nil,
127      prematch_vars: :warn,
128      requires: [],
129      tracers: [],
130      unused_vars: {%{}, 0},
131      vars: []
132    }
133  end
134
135  @doc false
136  def __struct__(kv) do
137    Enum.reduce(kv, __struct__(), fn {k, v}, acc -> :maps.update(k, v, acc) end)
138  end
139
140  @doc """
141  Returns a list of variables in the current environment.
142
143  Each variable is identified by a tuple of two elements,
144  where the first element is the variable name as an atom
145  and the second element is its context, which may be an
146  atom or an integer.
147  """
148  @doc since: "1.7.0"
149  @spec vars(t) :: [variable]
150  def vars(env)
151
152  def vars(%{__struct__: Macro.Env, current_vars: {read, _}}) do
153    Map.keys(read)
154  end
155
156  @doc """
157  Checks if a variable belongs to the environment.
158  """
159  @doc since: "1.7.0"
160  @spec has_var?(t, variable) :: boolean()
161  def has_var?(env, var)
162
163  def has_var?(%{__struct__: Macro.Env, current_vars: {read, _}}, var) do
164    Map.has_key?(read, var)
165  end
166
167  @doc """
168  Returns a keyword list containing the file and line
169  information as keys.
170  """
171  @spec location(t) :: keyword
172  def location(env)
173
174  def location(%{__struct__: Macro.Env, file: file, line: line}) do
175    [file: file, line: line]
176  end
177
178  @doc """
179  Returns a `Macro.Env` in the match context.
180  """
181  @spec to_match(t) :: t
182  def to_match(%{__struct__: Macro.Env, context: :match} = env) do
183    env
184  end
185
186  def to_match(%{__struct__: Macro.Env, current_vars: {read, _}, unused_vars: {_, counter}} = env) do
187    %{env | context: :match, prematch_vars: {read, counter}}
188  end
189
190  @doc """
191  Returns whether the compilation environment is currently
192  inside a guard.
193  """
194  @spec in_guard?(t) :: boolean
195  def in_guard?(env)
196  def in_guard?(%{__struct__: Macro.Env, context: context}), do: context == :guard
197
198  @doc """
199  Returns whether the compilation environment is currently
200  inside a match clause.
201  """
202  @spec in_match?(t) :: boolean
203  def in_match?(env)
204  def in_match?(%{__struct__: Macro.Env, context: context}), do: context == :match
205
206  @doc """
207  Returns the environment stacktrace.
208  """
209  @spec stacktrace(t) :: list
210  def stacktrace(%{__struct__: Macro.Env} = env) do
211    cond do
212      is_nil(env.module) ->
213        [{:elixir_compiler, :__FILE__, 1, relative_location(env)}]
214
215      is_nil(env.function) ->
216        [{env.module, :__MODULE__, 0, relative_location(env)}]
217
218      true ->
219        {name, arity} = env.function
220        [{env.module, name, arity, relative_location(env)}]
221    end
222  end
223
224  defp relative_location(env) do
225    [file: String.to_charlist(Path.relative_to_cwd(env.file)), line: env.line]
226  end
227end
228