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