defmodule Pathex do
@moduledoc """
This module contains functions and macros to be used with `Pathex` and i
To use Pathex just insert to your context. You can import Pathex in module body or even in function body.
```elixir
require Pathex
import Pathex, only: [path: 1, path: 2, "~>": 2, ...]
```
Or you can use `use`
```elixir
defmodule MyModule do
# `default_mod` option is optional
# when no mod is specified, `:naive` is selected
use Pathex, default_mod: :json
...
end
```
This will import all operatiors and `path` macro
Any macro here belongs to one of three categories:
1. Macro which creates path closure (only `path/2`)
2. Macro which uses path closure as path (`over/3`, `set/3`, `view/2`, ...)
3. Macro which creates path composition (`~>/2`, `|||/2`, ...)
"""
alias Pathex.Builder
alias Pathex.Builder.Viewer
alias Pathex.Combination
alias Pathex.Common
alias Pathex.Operations
alias Pathex.QuotedParser
import Kernel, except: [inspect: 2]
defguardp is_mod(m) when m in ~w[naive map json]a
@typedoc """
Function which is passed to path-closure as second element in args tuple
"""
@type inner_func(output) :: (any() -> result(output))
@type inspect_args :: any()
@type update_args(input, output) :: {input, inner_func(output)}
@type force_update_args(input, output) :: {input, inner_func(output), any()}
@typedoc "This depends on the modifier"
@type pathex_compatible_structure :: map() | list() | Keyword.t() | tuple()
@typedoc "Value returned by non-bang path call"
@type result(inner) :: {:ok, inner} | :error | :delete_me
@typedoc "Also known as [path-closure](path.md)"
@type t :: t(pathex_compatible_structure(), any())
@typedoc "Also known as [path-closure](path.md)"
@type t(input, output) ::
(op_name(),
force_update_args(input, output)
| update_args(input, output)
| inspect_args() ->
result(output | input) | Macro.t())
@typedoc "More about [modifiers](modifiers.md)"
@type mod :: :map | :json | :naive
@typep op_name :: Operations.name()
@doc """
Easy and convenient way to add pathex to your module.
You can specify modifier
```elixir
use Pathex, default_mod: :json
```
Or just use it with default `:naive` modifier
```elixir
use Pathex
```
"""
@doc export: true
defmacro __using__(opts) do
case Keyword.get(opts, :default_mod, :naive) do
mod when is_mod(mod) ->
if module = __CALLER__.module do
Module.put_attribute(module, :pathex_default_mod, mod)
end
quote do
require Pathex
import Pathex, only: [path: 1, path: 2, ~>: 2, &&&: 2, |||: 2, alongside: 1]
end
_wrong_mod ->
raise ArgumentError, "Pathex only works with navie, json and map mods"
end
end
@doc """
Applies `func` to the item under the `path` in `struct`
and returns modified structure. Works like `Map.update!/3` but doesn't raise.
Example:
iex> index = 1
iex> inc = fn x -> x + 1 end
iex> {:ok, [0, %{x: 9}]} = over [0, %{x: 8}], path(index / :x), inc
iex> p = path "hey" / 0
iex> {:ok, %{"hey" => [2, [2]]}} = over %{"hey" => [1, [2]]}, p, inc
> Note:
> Exceptions from passed function left unhandled
iex> over(%{1 => "x"}, path(1), fn x -> x + 1 end)
** (ArithmeticError) bad argument in arithmetic expression
"""
@doc export: true
defmacro over(struct, path, func) do
gen(path, :update, [struct, wrap_ok(func)], __CALLER__)
end
@doc """
Applies the `func` to the item under `path` in `struct` and returns modified structure.
Works like `Map.update!/3`.
Example:
iex> x = 1
iex> inc = fn x -> x + 1 end
iex> [0, %{x: 9}] = over! [0, %{x: 8}], path(x / :x), inc
iex> p = path "hey" / 0
iex> %{"hey" => [2, [2]]} = over! %{"hey" => [1, [2]]}, p, inc
"""
@doc export: true
defmacro over!(struct, path, func) do
path
|> gen(:update, [struct, wrap_ok(func)], __CALLER__)
|> bang(struct, path)
end
@doc """
Sets `value` under `path` in `structure`. Think of it like `Map.put/3`.
Example:
iex> x = 1
iex> {:ok, [0, %{x: 123}]} = set [0, %{x: 8}], path(x / :x), 123
iex> p = path "hey" / 0
iex> {:ok, %{"hey" => [123, [2]]}} = set %{"hey" => [1, [2]]}, p, 123
"""
@doc export: true
defmacro set(struct, path, value) do
gen(path, :update, [struct, quote(do: fn _ -> {:ok, unquote(value)} end)], __CALLER__)
end
@doc """
Sets the `value` under `path` in `struct`. Think of it like `Map.put/3`.
Example:
iex> x = 1
iex> [0, %{x: 123}] = set! [0, %{x: 8}], path(x / :x), 123
iex> p = path "hey" / 0
iex> %{"hey" => [123, [2]]} = set! %{"hey" => [1, [2]]}, p, 123
"""
@doc export: true
defmacro set!(struct, path, value) do
path
|> gen(:update, [struct, quote(do: fn _ -> {:ok, unquote(value)} end)], __CALLER__)
|> bang(struct, path)
end
@doc """
Sets the `value` under `path` in `struct`.
If the path does not exist it creates the path favouring maps
when structure is unknown.
Example:
iex> x = 1
iex> {:ok, [0, %{x: 123}]} = force_set [0, %{x: 8}], path(x / :x), 123
iex> p = path "hey" / 0
iex> {:ok, %{"hey" => %{0 => 1}}} = force_set %{}, p, 1
If the item in path doesn't have the right type, it returns `:error`.
Example:
iex> p = path "hey" / "you"
iex> :error = force_set %{"hey" => {1, 2}}, p, "value"
"""
@doc export: true
defmacro force_set(struct, path, value) do
gen(
path,
:force_update,
[struct, quote(do: fn _ -> {:ok, unquote(value)} end), value],
__CALLER__
)
end
@doc """
Sets the `value` under `path` in `struct`.
If the path does not exist it creates the path favouring maps
when structure is unknown.
Example:
iex> x = 1
iex> [0, %{x: 123}] = force_set! [0, %{x: 8}], path(x / :x), 123
iex> p = path "hey" / 0
iex> %{"hey" => %{0 => 1}} = force_set! %{}, p, 1
If the item in path doesn't have the right type, it raises.
Example:
iex> p = path "hey" / "you"
iex> force_set! %{"hey" => {1, 2}}, p, "value"
** (Pathex.Error) Type mismatch in structure
"""
@doc export: true
defmacro force_set!(struct, path, value) do
path
|> gen(
:force_update,
[struct, quote(do: fn _ -> {:ok, unquote(value)} end), value],
__CALLER__
)
|> bang(struct, path, "Type mismatch in structure")
end
@doc """
Applies `func` under `path` of `struct`.
If the path does not exist it creates the path favouring maps
when structure is unknown and inserts default value.
Example:
iex> x = 1
iex> {:ok, [0, %{x: {:xxx, 8}}]} = force_over([0, %{x: 8}], path(x / :x), & {:xxx, &1}, 123)
iex> p = path "hey" / 0
iex> {:ok, %{"hey" => %{0 => 1}}} = force_over(%{}, p, fn x -> x + 1 end, 1)
If the item in path doesn't have the right type, it returns `:error`.
Example:
iex> p = path "hey" / "you"
iex> :error = force_over %{"hey" => {1, 2}}, p, fn x -> x end, "value"
"""
@doc export: true
defmacro force_over(struct, path, func, value \\ nil) do
gen(path, :force_update, [struct, wrap_ok(func), value], __CALLER__)
end
@doc """
Applies `func` under `path` of `struct`.
If the path does not exist it creates the path favouring maps
when structure is unknown and inserts default value.
Example:
iex> x = 1
iex> [0, %{x: {:xxx, 8}}] = force_over!([0, %{x: 8}], path(x / :x), & {:xxx, &1}, 123)
iex> p = path "hey" / 0
iex> %{"hey" => %{0 => 1}} = force_over!(%{}, p, fn x -> x + 1 end, 1)
If the item in path doesn't have the right type, it raises.
Example:
iex> p = path "hey" / "you"
iex> force_over! %{"hey" => {1, 2}}, p, fn x -> x end, "value"
** (Pathex.Error) Type mismatch in structure
"""
@doc export: true
defmacro force_over!(struct, path, func, value \\ nil) do
path
|> gen(:force_update, [struct, wrap_ok(func), value], __CALLER__)
|> bang(struct, path, "Type mismatch in structure")
end
@doc """
Applies `func` under `path` in `struct` and returns result of this `func`.
Example:
iex> x = 1
iex> {:ok, 9} = at [0, %{x: 8}], path(x / :x), fn x -> x + 1 end
iex> p = path "hey" / 0
iex> {:ok, {:here, 9}} = at(%{"hey" => {9, -9}}, p, & {:here, &1})
"""
@doc export: true
defmacro at(struct, path, func) do
gen(path, :view, [struct, wrap_ok(func)], __CALLER__)
end
@doc """
Applies `func` under `path` in `struct` and returns result of this `func`.
Raises if path is not found.
Example:
iex> x = 1
iex> 9 = at! [0, %{x: 8}], path(x / :x), fn x -> x + 1 end
iex> p = path "hey" / 0
iex> {:here, 9} = at!(%{"hey" => {9, -9}}, p, & {:here, &1})
"""
@doc export: true
defmacro at!(struct, path, func) do
path
|> gen(:view, [struct, wrap_ok(func)], __CALLER__)
|> bang(struct, path)
end
@doc """
Gets the value under `path` in `struct`.
Example:
iex> x = 1
iex> {:ok, 8} = view [0, %{x: 8}], path(x / :x)
iex> p = path "hey" / 0
iex> {:ok, 9} = view %{"hey" => {9, -9}}, p
"""
@doc export: true
defmacro view(struct, path) do
gen(path, :view, [struct, quote(do: fn x -> {:ok, x} end)], __CALLER__)
end
@doc """
Gets the value under `path` in `struct`. Raises if `path` not found.
Example:
iex> x = 1
iex> 8 = view! [0, %{x: 8}], path(x / :x)
iex> p = path "hey" / 0
iex> 9 = view! %{"hey" => {9, -9}}, p
"""
@doc export: true
defmacro view!(struct, path) do
path
|> gen(:view, [struct, quote(do: fn x -> {:ok, x} end)], __CALLER__)
|> bang(struct, path)
end
@doc """
Gets the value under `path` in `struct` or returns `default` when `path` is not present.
Example:
iex> x = 1
iex> 8 = get([0, %{x: 8}], path(x / :x))
iex> p = path "hey" / "you"
iex> nil = get(%{"hey" => [x: 1]}, p)
iex> :default = get(%{"hey" => [x: 1]}, p, :default)
"""
@doc export: true
defmacro get(struct, path, default \\ nil) do
res = gen(path, :view, [struct, quote(do: fn x -> {:ok, x} end)], __CALLER__)
quote do
case unquote(res) do
{:ok, value} -> value
:error -> unquote(default)
end
end
|> Common.set_generated()
end
@doc """
Gets the value under `path` in `struct` or returns default value if not found.
Example:
iex> x = 1
iex> true = exists?([0, %{x: 8}], path(x / :x))
iex> p = path "hey" / "you"
iex> false = exists?(%{"hey" => [x: 1]}, p)
"""
@doc export: true
defmacro exists?(struct, path) do
res = gen(path, :view, [struct, quote(do: fn _ -> true end)], __CALLER__)
quote do
with :error <- unquote(res) do
false
end
end
|> Common.set_generated()
end
@doc """
Deletes value under `path` in `struct`.
Example:
iex> x = 1
iex> {:ok, [0, %{}]} = delete([0, %{x: 8}], path(x / :x))
iex> :error = delete([0, %{x: 8}], path(1 / :y))
"""
@doc export: true
defmacro delete(struct, path) do
path
|> gen(:delete, [struct, quote(do: fn _ -> :delete_me end)], __CALLER__)
|> wrap_delete_me()
end
@doc """
Deletes value under `path` in `struct` or raises if value is not found.
Example:
iex> x = 1
iex> [0, %{}] = delete!([0, %{x: 8}], path(x / :x))
"""
@doc export: true
defmacro delete!(struct, path) do
path
|> gen(:delete, [struct, quote(do: fn _ -> :delete_me end)], __CALLER__)
|> wrap_delete_me()
|> bang(struct, path)
end
defp wrap_delete_me(call) do
quote do
with :delete_me <- unquote(call) do
:error
end
end
end
@doc """
Macro which gets value in the structure and deletes it.
> Note:
>
> Current implementation of this function performs double lookup.
Example:
iex> {:ok, {1, [2, 3]}} = pop([1, 2, 3], path(0))
"""
@doc export: true
defmacro pop(struct, path) do
view = gen(path, :view, [struct, quote(do: fn x -> {:ok, x} end)], __CALLER__)
delete = gen(path, :delete, [struct, quote(do: fn _ -> :delete_me end)], __CALLER__)
quote do
with(
{:ok, value} <- unquote(view),
{:ok, structure} <- unquote(delete)
) do
{:ok, {value, structure}}
end
end
end
@doc """
Gets value under `path` in `struct` and then deletes it.
> Note:
>
> Current implementation of this function performs double lookup.
Example:
iex> {1, [2, 3]} = pop!([1, 2, 3], path(0))
"""
@doc export: true
defmacro pop!(struct, path) do
view =
path
|> gen(:view, [struct, quote(do: fn x -> {:ok, x} end)], __CALLER__)
|> bang(struct, path)
delete =
path
|> gen(:delete, [struct, quote(do: fn _ -> :delete_me end)], __CALLER__)
|> wrap_delete_me()
|> bang(struct, path)
quote do
value = unquote(view)
structure = unquote(delete)
{value, structure}
end
end
@doc """
Creates path from `quoted` ast. Paths look like unix fs path and consist of
elements separated from each other with `/`. See
For example:
iex> x = 1
iex> mypath = path 1 / :atom / "string" / {"tuple?"} / x
iex> structure = [0, [atom: %{"string" => %{{"tuple?"} => %{1 => 2}}}]]
iex> {:ok, 2} = view structure, mypath
Default [modifier](modifiers.md) of this `path/2` is `:naive` which means that
* Every variable is treated as index or key to tuple, list, map and keyword
* Every atom is treated as key to map or keyword
* Every integer is treated as index to tuple, list or key to map
* Every other data type is treated as key to map
> Note:
> `-1` allows data to be prepended to the list
iex> x = -1
iex> p1 = path(-1)
iex> p2 = path(x)
iex> {:ok, [1, 2]} = force_set([2], p1, 1)
iex> {:ok, [1, 2]} = force_set([2], p2, 1)
"""
@doc export: true
defmacro path(quoted, mod \\ nil) do
mod = get_mod(mod, __CALLER__)
{binds, combination} = QuotedParser.parse(quoted, __CALLER__, mod)
combination
|> assert_combination_length(__CALLER__)
|> Builder.build(Operations.builders_for_combination(combination))
|> Common.set_generated()
|> prepend_binds(binds)
end
@doc """
Creates composition of two paths similar to concatenating them together.
This means that `a ~> b` path-closure applies `a` and only if it returns `{:ok, something}`
it applies `b` to `something`
Example:
iex> p1 = path :x / :y
iex> p2 = path :a / :b
iex> composed_path = p1 ~> p2
iex> {:ok, 1} = view %{x: [y: [a: [a: 0, b: 1]]]}, composed_path
"""
@doc export: true
defmacro a ~> b, do: do_concat(a, b, __CALLER__)
@doc """
The same as `Pathex.~>/2` for those who do not like operators
Example:
iex> p1 = path :x / :y
iex> p2 = path :a / :b
iex> composed_path = concat(p1, p2)
iex> {:ok, 1} = view %{x: [y: [a: [a: 0, b: 1]]]}, composed_path
"""
@doc export: true
defmacro concat(a, b), do: do_concat(a, b, __CALLER__)
defp do_concat(a, b, caller) do
{:~>, [], [a, b]}
|> QuotedParser.parse_composition(:~>)
|> Builder.build_composition(:~>, caller)
|> Common.set_generated()
end
@doc """
Creates composition of two paths which has some inspiration from logical `and`.
This means that `a &&& b` path-closure tries to apply `a` and only if it returns `{:ok, something}`, tries
apply `b` and if `b` returns **exactly the same** as `a` does, the `a &&& b` returns `{:ok, something}`
Example:
iex> p1 = path :x / :y
iex> p2 = path :a / :b
iex> ap = p1 &&& p2
iex> {:ok, 1} = view %{x: %{y: 1}, a: [b: 1]}, ap
iex> :error = view %{x: %{y: 1}, a: [b: 2]}, ap
iex> {:ok, %{x: %{y: 2}, a: [b: 2]}} = set %{x: %{y: 1}, a: [b: 1]}, ap, 2
iex> {:ok, %{x: %{y: 2}, a: %{b: 2}}} = force_set %{}, ap, 2
"""
@doc export: true
defmacro a &&& b do
{:&&&, [], [a, b]}
|> QuotedParser.parse_composition(:&&&)
|> Builder.build_composition(:&&&, __CALLER__)
|> Common.set_generated()
end
@doc """
Creates composition of two paths which has some inspiration from logical `or`.
This means that `a ||| b` path-closure tries to apply `a` and only if it returns `:error`, tries
apply `b`
Example:
iex> p1 = path :x / :y
iex> p2 = path :a / :b
iex> op = p1 ||| p2
iex> {:ok, 1} = view %{x: %{y: 1}, a: [b: 2]}, op
iex> {:ok, 2} = view %{x: 1, a: [b: 2]}, op
iex> {:ok, %{x: %{y: 2}, a: [b: 1]}} = set %{x: %{y: 1}, a: [b: 1]}, op, 2
iex> {:ok, %{x: %{y: 2}}} = force_set %{}, op, 2
iex> {:ok, %{x: %{}, a: [b: 1]}} = force_set %{x: %{y: 1}, a: [b: 1]}, op, 2
"""
@doc export: true
defmacro a ||| b do
{:|||, [], [a, b]}
|> QuotedParser.parse_composition(:|||)
|> Builder.build_composition(:|||, __CALLER__)
|> Common.set_generated()
end
@doc """
This macro creates compositions of paths which work along with each other
Think of `alongside([path1, path2, path3])` as `path1 &&& path2 &&& path3`
The only difference is that for viewing alongside returns list of variables
Example:
iex> pa = alongside [path(:x), path(:y)]
iex> {:ok, [1, 2]} = view(%{x: 1, y: 2}, pa)
iex> {:ok, %{x: 3, y: 3}} = set(%{x: 1, y: 2}, pa, 3)
iex> :error = set(%{x: 1}, pa, 3)
iex> {:ok, %{x: 1, y: 1}} = force_set(%{}, pa, 1)
"""
@doc export: true
defmacro alongside(list) do
list
|> Builder.build_composition(:alongside, __CALLER__)
|> Common.set_generated()
end
@doc """
Inspect the given path-closure and returns string which corresponds to given path-closure
Example:
iex> index = 1
iex> p = path(:x) ~> path(:y / index) &&& path(-1)
iex> Pathex.inspect(p)
"path(:x) ~> path(:y / 1) &&& path(-1)"
"""
@spec inspect(Pathex.t()) :: iodata()
@doc export: true
def inspect(path_closure) when is_function(path_closure, 2) do
Macro.to_string path_closure.(:inspect, [])
end
@doc """
This macro converts path (which can be matched upon) into pattern.
These requirements must be satisfied in order for this macro to work correctly:
1. Path must be inlined into this macro. This means that path must be defined
in a argument of this macro
2. Path must consist only of list with constants or map variable or constant items
3. Path must result only in case with one clause
Example
iex> import Pathex
iex> structure = %{users: %{1 => %{fname: "Jose", lname: "Valim"}}}
iex> case structure do
...> pattern(fname, path(:users / 1 / :fname, :map)) ->
...> {:ok, fname}
...> _ ->
...> :error
...> end
{:ok, "Jose"}
"""
@doc export: true
defmacro pattern(variable \\ {:_, [], Elixir}, path) do
{:ok, path, mod} =
with :error <- destruct_inlined(path, __CALLER__) do
raise CompileError, description: "Can't have uninlined paths"
end
mod = get_mod(mod, __CALLER__)
if not Macro.Env.in_match?(__CALLER__) do
raise CompileError, description: "Can't create pattern outside of pattern"
end
with(
{[], combination} <- QuotedParser.parse(path, __CALLER__, mod),
[path] <- Pathex.Combination.to_paths(combination),
{:ok, match} <- Viewer.match_from_path(path, variable)
) do
match
else
{:error, _} ->
raise CompileError, description: "Can't generate matching from this combination"
{_binds, _combination} ->
raise CompileError, description: "You can only use variables and constants in pattern matching"
_other ->
raise CompileError, description: "Unknown error"
end
end
# Helpers
# Helper for generating code for path operation
defp gen(code, op, args, caller) do
case destruct_inlined(code, caller) do
# Special case for inline paths
{:ok, path, mod} ->
path_func = build_only(path, op, caller, get_mod(mod, caller))
quote generated: true do
unquote(path_func).(unquote_splicing(args))
end
|> Common.set_generated()
# Case for not inlined paths
:error ->
# case Macro.expand(path, caller) do
# {:fn, _, clauses} ->
# Enum.find_value(clauses, fn
# {:"->", _, [[^op, args], body]} -> {args, body}
# _ -> false
# end)
# |> IO.inspect()
# IO.puts "YEAH!"
# _ ->
# :error
# end
quote generated: true do
unquote(code).(unquote(op), {unquote_splicing(args)})
end
|> Common.set_generated()
end
end
defp wrap_ok(func) do
quote do
fn x -> {:ok, unquote(func).(x)} end
end
end
# Helper for generating raising functions
@spec bang(Macro.t(), Operations.t(), Macro.t(), binary()) :: Macro.t()
defp bang(quoted, structure, path, err_str \\ "Couldn't find element") do
quote generated: true do
case unquote(quoted) do
{:ok, value} ->
value
:error ->
raise Pathex.Error,
message: unquote(err_str),
path: unquote(path),
structure: unquote(structure)
end
end
end
defp get_mod(nil, %Macro.Env{module: nil}), do: :naive
defp get_mod(nil, %Macro.Env{module: module}) do
Module.get_attribute(module, :pathex_default_mod) || :naive
rescue
e in ArgumentError ->
IO.warn """
You've attemted to compile the path within the enviroment which
is different from the original env. Therefore, Pathex was unable
to get the default modifier (which is bound to the env), so
`:naive` will be used
Original error: #{Kernel.inspect e, pretty: true}
"""
:naive
end
defp get_mod(mod, _) when is_mod(mod), do: mod
defp get_mod(mod, _) do
raise CompileError, description: "You can't set #{Kernel.inspect mod} as a mod"
end
# Builds only one clause of a path
defp build_only(path, opname, caller, mod) do
{binds, combination} =
path
|> fetch_args(caller)
|> QuotedParser.parse(caller, mod)
%{^opname => builder} = Operations.builders_for_combination(combination)
combination
|> Builder.build_only(builder)
|> prepend_binds(binds)
end
defp fetch_args(path, caller) do
case Macro.prewalk(path, &Macro.expand(&1, caller)) do
{{:., _, [__MODULE__, :path]}, _, args} ->
args
{:path, meta, args} = full ->
case Keyword.fetch(meta, :import) do
{:ok, __MODULE__} ->
args
_ ->
full
end
args ->
args
end
end
# This function raises warning if combination will lead to very big closure
@maximum_combination_size 256
defp assert_combination_length(combination, env) do
size = Combination.size(combination)
if size > @maximum_combination_size do
{func, arity} = env.function || {:nofunc, 0}
stacktrace = [{env.module, func, arity, [file: '#{env.file}', line: env.line]}]
"""
This path will generate too many clauses, and therefore will slow down
the compilation and increase amount of generated code. Current
combination has #{size} clauses while suggested amount is #{@maximum_combination_size}
It would be better to split this closure in different paths with `Pathex.~>/2`
Or change the modifier to one which generates less code: `:map` or `:json`
"""
|> IO.warn(stacktrace)
end
combination
end
defp prepend_binds(combination, binds) do
quote do
unquote_splicing(binds)
unquote(combination)
end
end
defp destruct_inlined({:path, meta, [path | mod]}, env) do
case Keyword.fetch(meta, :import) do
{:ok, Pathex} ->
{:ok, path, maybemod mod}
:error ->
case Macro.Env.lookup_import(env, {:path, 2}) do
[{:macro, Pathex} | _] ->
{:ok, path, maybemod mod}
_ ->
:error
end
_ ->
:error
end
end
defp destruct_inlined({{:".", _, [m, :path]}, _, [path | mod]}, env) do
case Macro.expand(m, env) do
Pathex ->
{:ok, path, maybemod mod}
_ ->
:error
end
end
defp destruct_inlined(_, _), do: :error
defp maybemod([]), do: nil
defp maybemod([mod]) when is_mod(mod), do: mod
defp maybemod(_) do
raise CompileError, description: "Incorrect modifier"
end
end