Expand aliases inside quote
This commit is contained in:
@@ -31,7 +31,9 @@
|
||||
|
||||
-record(elixir_quote, {
|
||||
line=0,
|
||||
marker=nil,
|
||||
var_context=nil,
|
||||
expand_aliases=true,
|
||||
expand_imports=true,
|
||||
unquote=true
|
||||
}).
|
||||
|
||||
|
||||
@@ -282,13 +282,6 @@ defmodule Kernel do
|
||||
"""
|
||||
defmacro left !== right
|
||||
|
||||
@doc """
|
||||
When used inside quoting, marks that the variable should not
|
||||
be hygienezed. Check `Kernel.SpecialForms.quote/1` for more
|
||||
information.
|
||||
"""
|
||||
defmacro var!(var)
|
||||
|
||||
@doc """
|
||||
Returns an integer or float which is the arithmetical absolute value of `number`.
|
||||
|
||||
|
||||
@@ -328,11 +328,11 @@ defmodule Kernel.SpecialForms do
|
||||
quote do: sum(1, 2, 3)
|
||||
#=> { :sum, 0, [1, 2, 3] }
|
||||
|
||||
## Homoiconicity
|
||||
## Explanation
|
||||
|
||||
Elixir is an homoiconic language. Any Elixir program can be
|
||||
represented using its own data structures. The building block
|
||||
of Elixir homoiconicity is a tuple with three elements, for example:
|
||||
Any Elixir code can be represented using Elixir data structures.
|
||||
The building block of Elixir homoiconicity is a tuple with three
|
||||
elements, for example:
|
||||
|
||||
{ :sum, 1, [1, 2, 3] }
|
||||
|
||||
@@ -344,8 +344,18 @@ defmodule Kernel.SpecialForms do
|
||||
* The second element of the tuple is always an integer
|
||||
representing the line number;
|
||||
* The third element of the tuple are the arguments for the
|
||||
function call. The third argument may be an atom, meaning
|
||||
that it may be a variable.
|
||||
function call. The third argument may be an atom, which is
|
||||
usually a variable (or a local call);
|
||||
|
||||
## Options
|
||||
|
||||
* `:hygiene` - When false, disables hygiene for variables, aliases and imports;
|
||||
* `:unquote` - When false, disables unquoting. Useful when you have a quote
|
||||
inside another quote and want to control which quote is
|
||||
able to unquote;
|
||||
* `:location` - When set to `:keep`, keeps the current line and file on quotes.
|
||||
Read the Stacktrace information section below for more information;
|
||||
* `:expand_aliases` - When false, do not expand aliases;
|
||||
|
||||
## Macro literals
|
||||
|
||||
@@ -361,9 +371,17 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Hygiene
|
||||
|
||||
Elixir macros are hygienic regarding to variables. This means
|
||||
a variable defined in a macro cannot affect the scope where
|
||||
the macro is included. Consider the following example:
|
||||
Elixir macros are hygienic. This means aliases and imports
|
||||
defined inside the quoted often refer to the context that
|
||||
defined the macro.
|
||||
|
||||
Furthermore, variables inside quote are also hygienic. That
|
||||
said, a variable defined in a macro cannot affect the scope
|
||||
where the macro is included.
|
||||
|
||||
### Hygiene in variables
|
||||
|
||||
Consider the following example:
|
||||
|
||||
defmodule Hygiene do
|
||||
defmacro no_interference do
|
||||
@@ -395,22 +413,38 @@ defmodule Kernel.SpecialForms do
|
||||
NoHygiene.interference
|
||||
a #=> 1
|
||||
|
||||
Notice that aliases are not hygienic in Elixir, ambiguity
|
||||
must be solved by prepending Elixir:
|
||||
### Hygiene in aliases
|
||||
|
||||
quote do
|
||||
Elixir.Foo #=> Access the root Foo
|
||||
Foo #=> Access the Foo alias in the current module
|
||||
(if any is set), then fallback to Elixir.Foo
|
||||
Aliases inside quote are expanded by default.
|
||||
Consider the following example:
|
||||
|
||||
defmodule Hygiene do
|
||||
alias OrdDict, as: D
|
||||
|
||||
defmacro no_interference do
|
||||
quote do: D.new
|
||||
end
|
||||
end
|
||||
|
||||
## Options
|
||||
require Hygiene
|
||||
Hygiene.no_interference #=> { OrdDict, [] }
|
||||
|
||||
* `:hygiene` - When false, disables hygiene;
|
||||
* `:unquote` - When false, disables unquoting. Useful when you have a quote
|
||||
inside another quote and want to control which quote is able to unquote;
|
||||
* `:location` - When set to `:keep`, keeps the current line and file on quotes.
|
||||
Read the Stacktrace information section below for more information;
|
||||
Notice that, even though the alias `D` is not available
|
||||
in the context the macro is expanded, the code above works
|
||||
because `D` was expanded when the quote was generated.
|
||||
|
||||
There are two ways to disable this behaviour. By giving
|
||||
the `expand_aliases` equals to false to quote or by using
|
||||
the `alias!` macro inside the quote:
|
||||
|
||||
defmodule NoHygiene do
|
||||
defmacro interference do
|
||||
quote do: alias!(D).new
|
||||
end
|
||||
end
|
||||
|
||||
require NoHygiene
|
||||
NoHygiene.interference #=> UndefinedFunctionError
|
||||
|
||||
## Stacktrace information
|
||||
|
||||
@@ -446,6 +480,25 @@ defmodule Kernel.SpecialForms do
|
||||
"""
|
||||
defmacro quote(opts, do: contents)
|
||||
|
||||
@doc """
|
||||
When used inside quoting, marks that the variable should not
|
||||
be hygienezed. Check `quote/2` for more information.
|
||||
"""
|
||||
defmacro var!(var)
|
||||
|
||||
@doc """
|
||||
Defines a variable in the given context.
|
||||
Check `quote/2` for more information.
|
||||
"""
|
||||
defmacro var!(var, context)
|
||||
|
||||
@doc """
|
||||
When used inside quoting, marks that the alias should not
|
||||
be hygienezed. This means the alias will be expanded when
|
||||
the macro is expanded.
|
||||
"""
|
||||
defmacro alias!(alias)
|
||||
|
||||
@doc """
|
||||
Unquotes the given expression from inside a macro.
|
||||
|
||||
|
||||
+9
-26
@@ -442,28 +442,16 @@ defmodule Macro do
|
||||
"""
|
||||
def expand(aliases, env)
|
||||
|
||||
# The first case we handle is __aliases__. In case
|
||||
# aliases just contain one item, we are sure it is
|
||||
# an atom, so we just expand it based on the aliases
|
||||
# dict.
|
||||
def expand({ :__aliases__, _, [h] }, env) when h != Elixir do
|
||||
expand_alias(h, env)
|
||||
end
|
||||
def expand({ :__aliases__, _, _ } = original, env) do
|
||||
case :elixir_aliases.expand(original, env.aliases) do
|
||||
atom when is_atom(atom) -> atom
|
||||
aliases ->
|
||||
aliases = lc alias inlist aliases, do: expand(alias, env)
|
||||
|
||||
# In case aliases contains more than one item, we need
|
||||
# to loop them checking if they are all atoms or not.
|
||||
# Macros and pseudo-variables are then expanded.
|
||||
def expand({ :__aliases__, _, [h|t] } = original, env) do
|
||||
aliases = case h do
|
||||
x when is_atom(x) and x != Elixir -> [expand_alias(x, env)|t]
|
||||
_ -> [h|t]
|
||||
end
|
||||
|
||||
aliases = lc alias inlist aliases, do: expand(alias, env)
|
||||
|
||||
case :lists.all(is_atom(&1), aliases) do
|
||||
true -> :elixir_aliases.concat(aliases)
|
||||
false -> original
|
||||
case :lists.all(is_atom(&1), aliases) do
|
||||
true -> :elixir_aliases.concat(aliases)
|
||||
false -> original
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -524,11 +512,6 @@ defmodule Macro do
|
||||
:lists.any(match?({ :&, _, [_] }, &1), args)
|
||||
end
|
||||
|
||||
defp expand_alias(h, env) do
|
||||
atom = list_to_atom('Elixir-' ++ atom_to_list(h))
|
||||
:elixir_aliases.lookup(atom, env.aliases)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Recurs the quoted expression checking if all sub terms are
|
||||
safe (i.e. they represented data structured and don't actually
|
||||
|
||||
@@ -1,10 +1,29 @@
|
||||
-module(elixir_aliases).
|
||||
-export([first/1, last/1, concat/1, safe_concat/1, lookup/2,
|
||||
format_error/1, ensure_loaded/3]).
|
||||
format_error/1, ensure_loaded/3, expand/2]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Expand an alias returning an atom if possible.
|
||||
%% Otherwise returns the list of aliases args.
|
||||
|
||||
expand({ '__aliases__', _Line, [H] }, Aliases) when H /= 'Elixir' ->
|
||||
expand_alias(H, Aliases);
|
||||
|
||||
expand({ '__aliases__', _Line, [H|T] }, Aliases) when is_atom(H) ->
|
||||
concat(case H of
|
||||
'Elixir' -> [H|T];
|
||||
_ -> [expand_alias(H, Aliases)|T]
|
||||
end);
|
||||
|
||||
expand({ '__aliases__', _Line, List }, _Aliases) ->
|
||||
List.
|
||||
|
||||
expand_alias(H, Aliases) ->
|
||||
lookup(list_to_atom("Elixir-" ++ atom_to_list(H)), Aliases).
|
||||
|
||||
%% Ensure a module is loaded before its usage.
|
||||
|
||||
ensure_loaded(_Line, 'Elixir.Kernel', _S) ->
|
||||
ok;
|
||||
|
||||
|
||||
@@ -372,7 +372,5 @@ in_erlang_macros() ->
|
||||
{'or',2},
|
||||
{'receive',1},
|
||||
{'try',1},
|
||||
{'var!',1},
|
||||
{'var!',2},
|
||||
{'xor',2}
|
||||
].
|
||||
@@ -318,5 +318,8 @@ non_overridable_macros() ->
|
||||
{'super','*'},
|
||||
{'super?',0},
|
||||
{'bc','*'},
|
||||
{'lc','*'}
|
||||
{'lc','*'},
|
||||
{'var!',1},
|
||||
{'var!',2},
|
||||
{'alias!',1}
|
||||
].
|
||||
|
||||
@@ -232,10 +232,10 @@ translate({Kind, Line, [Call, Expr]}, S) when ?FUNS(Kind) ->
|
||||
|
||||
assert_no_aliases_name(Line, Name, Args, S),
|
||||
|
||||
TName = elixir_tree_helpers:abstract_syntax(Name),
|
||||
TArgs = elixir_tree_helpers:abstract_syntax(Args),
|
||||
TGuards = elixir_tree_helpers:abstract_syntax(Guards),
|
||||
TExpr = elixir_tree_helpers:abstract_syntax(Expr),
|
||||
TName = elixir_tree_helpers:abstract_syntax(Name),
|
||||
TArgs = elixir_tree_helpers:abstract_syntax(Args),
|
||||
TGuards = elixir_tree_helpers:abstract_syntax(Guards),
|
||||
TExpr = elixir_tree_helpers:abstract_syntax(Expr),
|
||||
|
||||
{ elixir_def:wrap_definition(Kind, Line, TName, TArgs, TGuards, TExpr, S), S };
|
||||
|
||||
@@ -257,23 +257,7 @@ translate({ apply, Line, [Left, Right, Args] }, S) when is_list(Args) ->
|
||||
|
||||
translate({ apply, Line, Args }, S) ->
|
||||
{ TArgs, NS } = translate_args(Args, S),
|
||||
{ ?ELIXIR_WRAP_CALL(Line, erlang, apply, TArgs), NS };
|
||||
|
||||
%% Handle forced variables
|
||||
|
||||
translate({ 'var!', Line, [Arg] }, S) ->
|
||||
translate({ 'var!', Line, [Arg, nil] }, S);
|
||||
|
||||
translate({ 'var!', Line, [{Name, _, Atom}, Kind] }, S) when is_atom(Name), is_atom(Atom) ->
|
||||
Expanded = elixir_scope:expand_var_context(Line, Kind, "invalid second argument for var!", S),
|
||||
elixir_scope:translate_var(Line, Name, Expanded, S);
|
||||
|
||||
translate({ 'var!', Line, [Name, Kind] }, S) when is_atom(Name) ->
|
||||
Expanded = elixir_scope:expand_var_context(Line, Kind, "invalid second argument for var!", S),
|
||||
elixir_scope:translate_var(Line, Name, Expanded, S);
|
||||
|
||||
translate({ 'var!', Line, [_, _] }, S) ->
|
||||
syntax_error(Line, S#elixir_scope.file, "invalid first argument for var!, expected a var or an atom").
|
||||
{ ?ELIXIR_WRAP_CALL(Line, erlang, apply, TArgs), NS }.
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -344,13 +328,13 @@ rewrite_case_clauses([{do,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,[{'_',_,_}
|
||||
rewrite_case_clauses(Clauses) ->
|
||||
Clauses.
|
||||
|
||||
module_ref(_Raw, Module, nil) ->
|
||||
module_ref(Raw, Module, Nesting) when is_atom(Raw); Nesting == nil ->
|
||||
Module;
|
||||
|
||||
module_ref({ '__aliases__', _, ['Elixir'|_]}, Module, _Nesting) ->
|
||||
module_ref({ '__aliases__', _, ['Elixir'|_] }, Module, _Nesting) ->
|
||||
Module;
|
||||
|
||||
module_ref(_F, Module, Nesting) ->
|
||||
module_ref(_Raw, Module, Nesting) ->
|
||||
elixir_aliases:concat([Nesting, Module]).
|
||||
|
||||
is_reserved_data(moduledoc) -> true;
|
||||
|
||||
@@ -45,6 +45,18 @@ quote(Else, Q, S) ->
|
||||
do_quote({ unquote, _Line, [Expr] }, #elixir_quote{unquote=true}, S) ->
|
||||
elixir_translator:translate_each(Expr, S);
|
||||
|
||||
do_quote({ 'alias!', _Line, [Expr] }, Q, S) ->
|
||||
do_quote(Expr, Q#elixir_quote{expand_aliases=false}, S);
|
||||
|
||||
do_quote({ '__aliases__', Line, [H|_] = Aliases }, #elixir_quote{expand_aliases=true} = Q, S) when is_atom(H) and H /= 'Elixir' ->
|
||||
{ TAliases, SA } = do_quote(['Elixir'|Aliases], Q, S),
|
||||
|
||||
{ { tuple, Line, [
|
||||
{ atom, Line, '__aliases__' },
|
||||
line(Line, Q),
|
||||
TAliases
|
||||
] }, SA };
|
||||
|
||||
do_quote({ { { '.', Line, [Left, unquote] }, _, [Expr] }, _, Args }, #elixir_quote{unquote=true} = Q, S) ->
|
||||
All = [Left, { unquote, Line, [Expr] }, Args, S#elixir_scope.file],
|
||||
{ TAll, TS } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Q, Acc) end, S, All),
|
||||
@@ -59,7 +71,7 @@ do_quote({ Left, Line, nil }, Q, S) when is_atom(Left) ->
|
||||
Tuple = { tuple, Line, [
|
||||
{ atom, Line, Left },
|
||||
line(Line, Q),
|
||||
{ atom, Line, Q#elixir_quote.marker }
|
||||
{ atom, Line, Q#elixir_quote.var_context }
|
||||
] },
|
||||
{ Tuple, S };
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
serialize/1, deserialize/1,
|
||||
serialize_with_vars/2, deserialize_with_vars/2,
|
||||
to_erl_env/1, to_ex_env/1, filename/1,
|
||||
umergev/2, umergec/2, merge_clause_vars/2,
|
||||
expand_var_context/4
|
||||
umergev/2, umergec/2, merge_clause_vars/2
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
@@ -80,18 +79,6 @@ build_ex_var(Line, Key, Name, S) ->
|
||||
Var = { ?ELIXIR_ATOM_CONCAT([Name, "@", Counter]), Line, Context },
|
||||
{ Var, NS }.
|
||||
|
||||
% Handle var contexts
|
||||
|
||||
expand_var_context(_Line, Atom, _Msg, _S) when is_atom(Atom) -> Atom;
|
||||
|
||||
expand_var_context(Line, Else, Msg, S) ->
|
||||
Kind = 'Elixir.Macro':expand(Else, to_ex_env({ Line, S })),
|
||||
case is_atom(Kind) of
|
||||
true -> Kind;
|
||||
false -> elixir_errors:syntax_error(Line, S#elixir_scope.file,
|
||||
"~ts, expected an atom or an alias", [Msg])
|
||||
end.
|
||||
|
||||
% Handle Macro.Env conversion
|
||||
|
||||
to_erl_env(Scope) ->
|
||||
|
||||
@@ -212,28 +212,20 @@ translate_each({ '__CALLER__', Line, Atom }, S) when is_atom(Atom) ->
|
||||
|
||||
%% Aliases
|
||||
|
||||
translate_each({ '__aliases__', Line, [H] }, S) when H /= 'Elixir' ->
|
||||
Atom = list_to_atom("Elixir-" ++ atom_to_list(H)),
|
||||
{ { atom, Line, elixir_aliases:lookup(Atom, S#elixir_scope.aliases) }, S };
|
||||
translate_each({ '__aliases__', Line, _ } = Alias, S) ->
|
||||
case elixir_aliases:expand(Alias, S#elixir_scope.aliases) of
|
||||
Atom when is_atom(Atom) -> { { atom, Line, Atom}, S };
|
||||
Aliases ->
|
||||
{ TAliases, SA } = translate_args(Aliases, S),
|
||||
|
||||
translate_each({ '__aliases__', Line, [H|T] }, S) ->
|
||||
Aliases = if
|
||||
is_atom(H) andalso (H /= 'Elixir') ->
|
||||
Atom = list_to_atom("Elixir-" ++ atom_to_list(H)),
|
||||
[elixir_aliases:lookup(Atom, S#elixir_scope.aliases)|T];
|
||||
true ->
|
||||
[H|T]
|
||||
end,
|
||||
|
||||
{ TAliases, SA } = translate_args(Aliases, S),
|
||||
|
||||
case lists:all(fun is_atom_tuple/1, TAliases) of
|
||||
true ->
|
||||
Atoms = [Atom || { atom, _, Atom } <- TAliases],
|
||||
{ { atom, Line, elixir_aliases:concat(Atoms) }, SA };
|
||||
false ->
|
||||
Args = [elixir_tree_helpers:build_simple_list(Line, TAliases)],
|
||||
{ ?ELIXIR_WRAP_CALL(Line, elixir_aliases, concat, Args), SA }
|
||||
case lists:all(fun is_atom_tuple/1, TAliases) of
|
||||
true ->
|
||||
Atoms = [Atom || { atom, _, Atom } <- TAliases],
|
||||
{ { atom, Line, elixir_aliases:concat(Atoms) }, SA };
|
||||
false ->
|
||||
Args = [elixir_tree_helpers:build_simple_list(Line, TAliases)],
|
||||
{ ?ELIXIR_WRAP_CALL(Line, elixir_aliases, concat, Args), SA }
|
||||
end
|
||||
end;
|
||||
|
||||
%% Quoting
|
||||
@@ -252,11 +244,11 @@ translate_each({ quote, GivenLine, [T] }, S) when is_list(T) ->
|
||||
syntax_error(GivenLine, S#elixir_scope.file, "invalid args for quote")
|
||||
end,
|
||||
|
||||
Marker = case lists:keyfind(var_context, 1, T) of
|
||||
Context = case lists:keyfind(var_context, 1, T) of
|
||||
{ var_context, VarContext } ->
|
||||
elixir_scope:expand_var_context(GivenLine, VarContext,
|
||||
expand_var_context(GivenLine, VarContext,
|
||||
"invalid argument given for var_context in quote", S);
|
||||
_ ->
|
||||
false ->
|
||||
case lists:keyfind(hygiene, 1, T) of
|
||||
{ hygiene, false } -> nil;
|
||||
_ ->
|
||||
@@ -267,14 +259,24 @@ translate_each({ quote, GivenLine, [T] }, S) when is_list(T) ->
|
||||
end
|
||||
end,
|
||||
|
||||
Aliases = case lists:keyfind(expand_aliases, 1, T) of
|
||||
{ expand_aliases, Bool } when is_boolean(Bool) ->
|
||||
Bool;
|
||||
false ->
|
||||
case lists:keyfind(hygiene, 1, T) of
|
||||
{ hygiene, Bool } when is_boolean(Bool) -> Bool;
|
||||
false -> true
|
||||
end
|
||||
end,
|
||||
|
||||
{ DefaultLine, DefaultFile } = case lists:keyfind(location, 1, T) of
|
||||
{ location, keep } -> { keep, keep };
|
||||
_ -> { 0, nil }
|
||||
false -> { 0, nil }
|
||||
end,
|
||||
|
||||
Line = case lists:keyfind(line, 1, T) of
|
||||
{ line, LineValue } -> LineValue;
|
||||
_ -> DefaultLine
|
||||
false -> DefaultLine
|
||||
end,
|
||||
|
||||
{ TLine, SL } = case Line of
|
||||
@@ -284,7 +286,7 @@ translate_each({ quote, GivenLine, [T] }, S) when is_list(T) ->
|
||||
|
||||
File = case lists:keyfind(file, 1, T) of
|
||||
{ file, FileValue } -> FileValue;
|
||||
_ -> DefaultFile
|
||||
false -> DefaultFile
|
||||
end,
|
||||
|
||||
TFile = case File of
|
||||
@@ -306,11 +308,31 @@ translate_each({ quote, GivenLine, [T] }, S) when is_list(T) ->
|
||||
_ -> true
|
||||
end,
|
||||
|
||||
elixir_quote:quote(TExprs, #elixir_quote{marker=Marker, line=TLine, unquote=Unquote}, SL);
|
||||
Q = #elixir_quote{var_context=Context, line=TLine, unquote=Unquote,
|
||||
expand_aliases=Aliases, expand_imports=true},
|
||||
|
||||
elixir_quote:quote(TExprs, Q, SL);
|
||||
|
||||
translate_each({ quote, GivenLine, [_] }, S) ->
|
||||
syntax_error(GivenLine, S#elixir_scope.file, "invalid args for quote");
|
||||
|
||||
translate_each({ 'alias!', Line, [Arg] }, S) ->
|
||||
translate_each(Arg, S);
|
||||
|
||||
translate_each({ 'var!', Line, [Arg] }, S) ->
|
||||
translate_each({ 'var!', Line, [Arg, nil] }, S);
|
||||
|
||||
translate_each({ 'var!', Line, [{Name, _, Atom}, Kind] }, S) when is_atom(Name), is_atom(Atom) ->
|
||||
Expanded = expand_var_context(Line, Kind, "invalid second argument for var!", S),
|
||||
elixir_scope:translate_var(Line, Name, Expanded, S);
|
||||
|
||||
translate_each({ 'var!', Line, [Name, Kind] }, S) when is_atom(Name) ->
|
||||
Expanded = expand_var_context(Line, Kind, "invalid second argument for var!", S),
|
||||
elixir_scope:translate_var(Line, Name, Expanded, S);
|
||||
|
||||
translate_each({ 'var!', Line, [_, _] }, S) ->
|
||||
syntax_error(Line, S#elixir_scope.file, "invalid first argument for var!, expected a var or an atom");
|
||||
|
||||
%% Functions
|
||||
|
||||
translate_each({ fn, Line, [[{do, { '->', _, Pairs }}]] }, S) ->
|
||||
@@ -491,6 +513,19 @@ translate_local(Line, Name, Args, S) ->
|
||||
},
|
||||
{ { call, Line, Remote, TArgs }, NS }.
|
||||
|
||||
expand_var_context(_Line, Atom, _Msg, _S) when is_atom(Atom) -> Atom;
|
||||
|
||||
expand_var_context(Line, { '__aliases__', _, _ } = Alias, Msg, S) ->
|
||||
case translate_each(Alias, S) of
|
||||
{ { atom, _, Atom }, _ } ->
|
||||
Atom;
|
||||
_ ->
|
||||
syntax_error(Line, S#elixir_scope.file, "~ts, expected a compile time available alias", [Msg])
|
||||
end;
|
||||
|
||||
expand_var_context(Line, _, Msg, S) ->
|
||||
syntax_error(Line, S#elixir_scope.file, "~ts, expected an atom or an alias", [Msg]).
|
||||
|
||||
%% Translate args
|
||||
|
||||
%% Variables in arguments are not propagated from one
|
||||
|
||||
@@ -54,8 +54,8 @@ defmodule Kernel.QuoteTest do
|
||||
assert quote(do: foo.unquote(:bar)(1)) == quote(do: foo.bar(1))
|
||||
assert quote(do: foo.unquote(:bar)(1) do 2 + 3 end) == quote(do: foo.bar(1) do 2 + 3 end)
|
||||
|
||||
assert Code.eval_quoted(quote(do: Foo.unquote(Bar))) == { Foo.Bar, [] }
|
||||
assert Code.eval_quoted(quote(do: Foo.unquote(quote do: Bar))) == { Foo.Bar, [] }
|
||||
assert Code.eval_quoted(quote(do: Foo.unquote(Bar))) == { Elixir.Foo.Bar, [] }
|
||||
assert Code.eval_quoted(quote(do: Foo.unquote(quote do: Bar))) == { Elixir.Foo.Bar, [] }
|
||||
|
||||
assert_raise SyntaxError, fn ->
|
||||
quote(do: foo.unquote(1))
|
||||
@@ -142,3 +142,13 @@ defmodule Kernel.QuoteTest.VarHygieneTest do
|
||||
read_interference
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Kernel.QuoteTest.AliasHygieneTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :expand_aliases do
|
||||
assert Code.eval_quoted(quote do: Foo.Bar) == { Elixir.Foo.Bar, [] }
|
||||
assert Code.eval_quoted(quote do: alias!(Foo.Bar)) == { Foo.Bar, [] }
|
||||
assert Code.eval_quoted(quote expand_aliases: false, do: Foo.Bar) == { Foo.Bar, [] }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -57,7 +57,7 @@ defmodule ModuleTest do
|
||||
Module.eval_quoted __MODULE__, contents, [], file: "sample.ex", line: 13
|
||||
|
||||
defmacrop in_module(block) do
|
||||
quote do
|
||||
quote expand_aliases: false do
|
||||
defmodule Temp, unquote(block)
|
||||
:code.purge(Temp)
|
||||
:code.delete(Temp)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
Code.require_file "../test_helper.exs", __FILE__
|
||||
|
||||
defmodule Typespec.Test.Type do
|
||||
defmodule Typespec.TypeTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
# This macro allows us to focus on the result of the
|
||||
# definition and not on the hassles of handling test
|
||||
# module
|
||||
defmacrop test_module([{:do, block}]) do
|
||||
quote do
|
||||
quote expand_aliases: false do
|
||||
{ :module, T, _binary, result } = defmodule T do
|
||||
unquote(block)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user