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