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 * `context` - the context of the environment; it can be `nil` 25 (default context), `:guard` (inside a guard) or `:match` (inside a match) 26 * `context_modules` - a list of modules defined in the current context 27 * `file` - the current file name as a binary 28 * `function` - a tuple as `{atom, integer}`, where the first 29 element is the function name and the second its arity; returns 30 `nil` if not inside a function 31 * `line` - the current line as an integer 32 * `module` - the current module name 33 34 The following fields are private to Elixir's macro expansion mechanism and 35 must not be accessed directly: 36 37 * `aliases` 38 * `functions` 39 * `macro_aliases` 40 * `macros` 41 * `lexical_tracker` 42 * `requires` 43 * `tracers` 44 * `versioned_vars` 45 46 """ 47 48 @type context :: :match | :guard | nil 49 @type context_modules :: [module] 50 @type file :: binary 51 @type line :: non_neg_integer 52 @type name_arity :: {atom, arity} 53 @type variable :: {atom, atom | term} 54 55 @typep aliases :: [{module, module}] 56 @typep functions :: [{module, [name_arity]}] 57 @typep lexical_tracker :: pid | nil 58 @typep macro_aliases :: [{module, {term, module}}] 59 @typep macros :: [{module, [name_arity]}] 60 @typep requires :: [module] 61 @typep tracers :: [module] 62 @typep versioned_vars :: %{optional(variable) => var_version :: non_neg_integer} 63 64 @type t :: %{ 65 __struct__: __MODULE__, 66 aliases: aliases, 67 context: context, 68 context_modules: context_modules, 69 file: file, 70 function: name_arity | nil, 71 functions: functions, 72 lexical_tracker: lexical_tracker, 73 line: line, 74 macro_aliases: macro_aliases, 75 macros: macros, 76 module: module, 77 requires: requires, 78 tracers: tracers, 79 versioned_vars: versioned_vars 80 } 81 82 # Define the __struct__ callbacks by hand for bootstrap reasons. 83 @doc false 84 def __struct__ do 85 %{ 86 __struct__: __MODULE__, 87 aliases: [], 88 context: nil, 89 context_modules: [], 90 file: "nofile", 91 function: nil, 92 functions: [], 93 lexical_tracker: nil, 94 line: 0, 95 macro_aliases: [], 96 macros: [], 97 module: nil, 98 requires: [], 99 tracers: [], 100 versioned_vars: %{} 101 } 102 end 103 104 @doc false 105 def __struct__(kv) do 106 Enum.reduce(kv, __struct__(), fn {k, v}, acc -> :maps.update(k, v, acc) end) 107 end 108 109 @doc """ 110 Returns a list of variables in the current environment. 111 112 Each variable is identified by a tuple of two elements, 113 where the first element is the variable name as an atom 114 and the second element is its context, which may be an 115 atom or an integer. 116 """ 117 @doc since: "1.7.0" 118 @spec vars(t) :: [variable] 119 def vars(env) 120 121 def vars(%{__struct__: Macro.Env, versioned_vars: vars}) do 122 Map.keys(vars) 123 end 124 125 @doc """ 126 Checks if a variable belongs to the environment. 127 128 ## Examples 129 130 iex> x = 13 131 iex> x 132 13 133 iex> Macro.Env.has_var?(__ENV__, {:x, nil}) 134 true 135 iex> Macro.Env.has_var?(__ENV__, {:unknown, nil}) 136 false 137 138 """ 139 @doc since: "1.7.0" 140 @spec has_var?(t, variable) :: boolean() 141 def has_var?(env, var) 142 143 def has_var?(%{__struct__: Macro.Env, versioned_vars: vars}, var) do 144 Map.has_key?(vars, var) 145 end 146 147 @doc """ 148 Returns a keyword list containing the file and line 149 information as keys. 150 """ 151 @spec location(t) :: keyword 152 def location(env) 153 154 def location(%{__struct__: Macro.Env, file: file, line: line}) do 155 [file: file, line: line] 156 end 157 158 @doc """ 159 Fetches the alias for the given atom. 160 161 Returns `{:ok, alias}` if the alias exists, `:error` 162 otherwise. 163 164 ## Examples 165 166 iex> alias Foo.Bar, as: Baz 167 iex> Baz 168 Foo.Bar 169 iex> Macro.Env.fetch_alias(__ENV__, :Baz) 170 {:ok, Foo.Bar} 171 iex> Macro.Env.fetch_alias(__ENV__, :Unknown) 172 :error 173 174 """ 175 @doc since: "1.13.0" 176 @spec fetch_alias(t, atom) :: {:ok, atom} | :error 177 def fetch_alias(%{__struct__: Macro.Env, aliases: aliases}, atom) when is_atom(atom), 178 do: Keyword.fetch(aliases, :"Elixir.#{atom}") 179 180 @doc """ 181 Fetches the macro alias for the given atom. 182 183 Returns `{:ok, macro_alias}` if the alias exists, `:error` 184 otherwise. 185 186 A macro alias is only used inside quoted expansion. See 187 `fetch_alias/2` for a more general example. 188 """ 189 @doc since: "1.13.0" 190 @spec fetch_macro_alias(t, atom) :: {:ok, atom} | :error 191 def fetch_macro_alias(%{__struct__: Macro.Env, macro_aliases: aliases}, atom) 192 when is_atom(atom), 193 do: Keyword.fetch(aliases, :"Elixir.#{atom}") 194 195 @doc """ 196 Returns the modules from which the given `{name, arity}` was 197 imported. 198 199 It returns a list of two element tuples in the shape of 200 `{:function | :macro, module}`. The elements in the list 201 are in no particular order and the order is not guaranteed. 202 203 ## Examples 204 205 iex> Macro.Env.lookup_import(__ENV__, {:duplicate, 2}) 206 [] 207 iex> import Tuple, only: [duplicate: 2], warn: false 208 iex> Macro.Env.lookup_import(__ENV__, {:duplicate, 2}) 209 [{:function, Tuple}] 210 iex> import List, only: [duplicate: 2], warn: false 211 iex> Macro.Env.lookup_import(__ENV__, {:duplicate, 2}) 212 [{:function, List}, {:function, Tuple}] 213 214 iex> Macro.Env.lookup_import(__ENV__, {:def, 1}) 215 [{:macro, Kernel}] 216 217 """ 218 @doc since: "1.13.0" 219 @spec lookup_import(t, name_arity) :: [{:function | :macro, module}] 220 def lookup_import( 221 %{__struct__: Macro.Env, functions: functions, macros: macros}, 222 {name, arity} = pair 223 ) 224 when is_atom(name) and is_integer(arity) do 225 f = for {mod, pairs} <- functions, :ordsets.is_element(pair, pairs), do: {:function, mod} 226 m = for {mod, pairs} <- macros, :ordsets.is_element(pair, pairs), do: {:macro, mod} 227 f ++ m 228 end 229 230 @doc """ 231 Returns true if the given module has been required. 232 233 ## Examples 234 235 iex> Macro.Env.required?(__ENV__, Integer) 236 false 237 iex> require Integer 238 iex> Macro.Env.required?(__ENV__, Integer) 239 true 240 241 iex> Macro.Env.required?(__ENV__, Kernel) 242 true 243 """ 244 @doc since: "1.13.0" 245 @spec required?(t, module) :: boolean 246 def required?(%{__struct__: Macro.Env, requires: requires}, mod) when is_atom(mod), 247 do: mod in requires 248 249 @doc """ 250 Prepend a tracer to the list of tracers in the environment. 251 252 ## Examples 253 254 Macro.Env.prepend_tracer(__ENV__, MyCustomTracer) 255 256 """ 257 @doc since: "1.13.0" 258 @spec prepend_tracer(t, module) :: t 259 def prepend_tracer(%{__struct__: Macro.Env, tracers: tracers} = env, tracer) do 260 %{env | tracers: [tracer | tracers]} 261 end 262 263 @doc """ 264 Returns a `Macro.Env` in the match context. 265 """ 266 @spec to_match(t) :: t 267 def to_match(%{__struct__: Macro.Env} = env) do 268 %{env | context: :match} 269 end 270 271 @doc """ 272 Returns whether the compilation environment is currently 273 inside a guard. 274 """ 275 @spec in_guard?(t) :: boolean 276 def in_guard?(env) 277 def in_guard?(%{__struct__: Macro.Env, context: context}), do: context == :guard 278 279 @doc """ 280 Returns whether the compilation environment is currently 281 inside a match clause. 282 """ 283 @spec in_match?(t) :: boolean 284 def in_match?(env) 285 def in_match?(%{__struct__: Macro.Env, context: context}), do: context == :match 286 287 @doc """ 288 Returns the environment stacktrace. 289 """ 290 @spec stacktrace(t) :: list 291 def stacktrace(%{__struct__: Macro.Env} = env) do 292 cond do 293 is_nil(env.module) -> 294 [{:elixir_compiler, :__FILE__, 1, relative_location(env)}] 295 296 is_nil(env.function) -> 297 [{env.module, :__MODULE__, 0, relative_location(env)}] 298 299 true -> 300 {name, arity} = env.function 301 [{env.module, name, arity, relative_location(env)}] 302 end 303 end 304 305 defp relative_location(env) do 306 [file: String.to_charlist(Path.relative_to_cwd(env.file)), line: env.line] 307 end 308end 309