Compare commits

...
14 Commits
Author SHA1 Message Date
José Valim 16a14c1f6d Release v1.4.0-rc.1 2016-12-05 21:51:41 +01:00
Andrea Leopardi cfa6cf220e Use :extra_applications instead of :applications in escript.build (#5534) 2016-12-05 13:47:54 +01:00
José Valim 5e1b7d9470 Fix expansion_test
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-12-03 22:56:12 +01:00
José Valim 38baa729da Annotate the context for variables as zero-arity funs in quotes
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-12-03 22:42:35 +01:00
Andrea Leopardi 5fe53f7b7a Hard-deprecate the Set module (#5513)
It will be removed in Elixir 2.0.
2016-12-01 10:30:50 +01:00
José Valim 276b0d3c72 Ensure comprehensions with guards and filters keep proper ordering, closes #5509
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-11-30 20:00:38 +01:00
Henrik Nyh abb8817a0a inflect -> infer (#5503) 2016-11-30 09:36:07 +01:00
José Valim 5309843d2b Unify testing of evaluator related functionality
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-11-29 17:52:33 +01:00
Myron Marston 1a2c2dcda6 Improve IEx autocomplete to support navigating map atom keys (#5488)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-11-29 17:52:27 +01:00
José Valim 03b6c07215 Include proper line numbers on unused module attribute warning, closes #5490
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-11-29 11:15:51 +01:00
José Valim d10b9d5b29 Improve docs for archives
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-11-29 09:59:50 +01:00
José Valim e38be075c3 Add DateTime.from_iso8601/1
Thanks to Lau Taarnskov for suggestions and code review.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-11-29 00:15:27 +01:00
José Valim 28824e8171 Ensure child task cannot link after parent unlinks
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-11-28 15:07:29 +01:00
José Valim 3edc407713 Update CHANGELOG 2016-11-28 12:28:57 +01:00
29 changed files with 503 additions and 191 deletions
+11 -6
View File
@@ -1,6 +1,6 @@
# Changelog for Elixir v1.4
Elixir v1.4 brings new features, enhancements and bug fixes into Elixir. The most notable changes are the addition of the `Registry` module and the `Task.async_stream/3` and `Task.async_stream/5` which aids developers in writing concurrent software. Those two features and a couple other improvements are described in detail below followed by the complete list of changes.
Elixir v1.4 brings new features, enhancements and bug fixes into Elixir. The most notable changes are the addition of the `Registry` module and the `Task.async_stream/3` and `Task.async_stream/5` which aid developers in writing concurrent software. Those two features and a couple other improvements are described in detail below followed by the complete list of changes.
## Registry
@@ -55,9 +55,9 @@ While the snippet above works fine in many occasions, for large collections it w
The `Task.async_stream` functions are also lazy, allowing developers to partially consume the stream until a condition is reached. Furthermore, `Task.Supervisor.async_stream/4` and `Task.Supervisor.async_stream/6` can be used to ensure the concurrent tasks are spawned under a given supervisor.
## Application inflection
## Application inference
Mix v1.4 now automatically inflects the list of applications that are required on runtime from your dependencies list.
Mix v1.4 now automatically infers the list of applications that are required on runtime from your dependencies list.
In previous Mix versions, most of your dependencies had to be added both to your dependencies list and applications list. Here is how a `mix.exs` would look like:
@@ -72,7 +72,7 @@ In previous Mix versions, most of your dependencies had to be added both to your
This was error prone as many developers would not list their dependencies in their applications list.
Mix v1.4 now automatically inflects your applications list as long as you leave the `:applications` key empty. The `mix.exs` above can be rewritten to:
Mix v1.4 now automatically infers your applications list as long as you leave the `:applications` key empty. The `mix.exs` above can be rewritten to:
def application do
[extra_applications: [:logger]]
@@ -91,7 +91,7 @@ Finally, if there is a dependency you don't want to include in the application r
We hope this feature provides a more streamlined workflow for developers who are building releases for their Elixir projects.
## v1.4.0-rc.0 (2016-11-28)
## v1.4.0-rc.1 (2016-12-05)
### 1. Enhancements
@@ -135,6 +135,7 @@ We hope this feature provides a more streamlined workflow for developers who are
* [IEx.Autocomplete] Stop appending a trailing dot when autocompleting modules in IEx
* [IEx.Autocomplete] Support autocompletion for structs
* [IEx.Autocomplete] Improve IEx autocomplete to support navigating map atom keys
* [IEx.Helpers] `c/1` now compiles in memory by default to avoid common issue where `.beam` files remain at projects root directory
* [IEx.Helpers] Add info about protocols in `i/1`
* [IEx.Server] Support interrupting IEx evaluation through the Ctrl+G prompt
@@ -142,7 +143,7 @@ We hope this feature provides a more streamlined workflow for developers who are
#### Mix
* [mix archive] Compress archive files built by `mix archive` as they are now unzipped during installation
* [mix compile] Automatically inflect the list of applications for Mix projects
* [mix compile] Automatically infer the list of applications for Mix projects
* [mix cmd] Add the ability to specify one or more apps in `mix cmd`
* [mix deps] Warn if there are non-applications in the `apps` directory for umbrella projects
* [mix deps] Add warning for invalid paths on `mix deps.clean`
@@ -160,6 +161,8 @@ We hope this feature provides a more streamlined workflow for developers who are
* [Float] Avoid multiple roundings in `Float.ceil/2`, `Float.floor/2` and `Float.round/2`
* [Kernel] Don't crash in `macro_exported?/3` when dealing with Erlang modules
* [Kernel] Ensure locals calls are rewritten when calling a local function or macro from inside a module
* [Kernel] Annotate the context for variables as zero-arity funs in quotes
* [Kernel.SpecialForms] Ensure comprehensions with guards and filters keep proper ordering,
* [Kernel.SpecialForms] Produce meaningful warning when with's else clauses have no effect
* [Macro] Wrap fn calls in parens in `Macro.to_string/2`
* [Macro] Do not print aliases as keys inside keyword lists in `Macro.to_string/2`
@@ -167,6 +170,7 @@ We hope this feature provides a more streamlined workflow for developers who are
* [Stream] Ensure `Stream.take/2` does not consume next element on `:suspend`
* [String] Fix infinite recursion in `String.replace_leading/3` and `String.replace_trailing/3` when given an empty string
* [Task] Fix `Task.shutdown/1,2` infinite block when task has no monitor
* [Task] Ensure task cannot link after parents unlinks
#### ExUnit
@@ -203,6 +207,7 @@ We hope this feature provides a more streamlined workflow for developers who are
* [Kernel] Deprecate support for making private functions overridable. Overridable functions must always be public as they must be contracts
* [Kernel] Warn if variable is used as a function call
* [OptionParser] Deprecate aliases with multiple letters, such as `-abc`
* [Set] Deprecate the `Set` module
* [Stream] Deprecate `Stream.uniq/2` in favor of `Stream.uniq_by/2`
#### IEx
+1 -1
View File
@@ -1,7 +1,7 @@
REBAR ?= "$(CURDIR)/rebar"
PREFIX ?= /usr/local
SHARE_PREFIX ?= $(PREFIX)/share
CANONICAL := master/
CANONICAL :=
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
+1 -1
View File
@@ -1 +1 @@
1.4.0-rc.0
1.4.0-rc.1
+81 -3
View File
@@ -1598,9 +1598,7 @@ defmodule DateTime do
WARNING: the ISO 8601 datetime format does not contain the time zone nor
its abbreviation, which means information is lost when converting to such
format. This is also why this module does not provide a `from_iso8601/1`
function, as it is impossible to build a proper `DateTime` from only the
information in the ISO 8601 string.
format.
### Examples
@@ -1630,6 +1628,86 @@ defmodule DateTime do
time_zone, zone_abbr, utc_offset, std_offset)
end
@doc """
Parses the extended "Date and time of day" format described by
[ISO 8601:2004](https://en.wikipedia.org/wiki/ISO_8601).
Since ISO8601 does not include the proper time zone, the given
string will be converted to UTC and its offset in seconds will be
returned as part of this function. Therefore offset information
must be present in the string.
As specified in the standard, the separator "T" may be omitted if
desired as there is no ambiguity within this function.
Time representations with reduced accuracy are not supported.
## Examples
iex> DateTime.from_iso8601("2015-01-23T23:50:07Z")
{:ok, %DateTime{calendar: Calendar.ISO, day: 23, hour: 23, microsecond: {0, 0}, minute: 50, month: 1, second: 7, std_offset: 0,
time_zone: "Etc/UTC", utc_offset: 0, year: 2015, zone_abbr: "UTC"}, 0}
iex> DateTime.from_iso8601("2015-01-23T23:50:07.123+02:30")
{:ok, %DateTime{calendar: Calendar.ISO, day: 23, hour: 21, microsecond: {123000, 3}, minute: 20, month: 1, second: 7, std_offset: 0,
time_zone: "Etc/UTC", utc_offset: 0, year: 2015, zone_abbr: "UTC"}, 9000}
iex> DateTime.from_iso8601("2015-01-23P23:50:07")
{:error, :invalid_format}
iex> DateTime.from_iso8601("2015-01-23 23:50:07A")
{:error, :invalid_format}
iex> DateTime.from_iso8601("2015-01-23T23:50:07")
{:error, :missing_offset}
iex> DateTime.from_iso8601("2015-01-23 23:50:61")
{:error, :invalid_time}
iex> DateTime.from_iso8601("2015-01-32 23:50:07")
{:error, :invalid_date}
iex> DateTime.from_iso8601("2015-01-23T23:50:07.123-00:00")
{:error, :invalid_format}
iex> DateTime.from_iso8601("2015-01-23T23:50:07.123-00:60")
{:error, :invalid_format}
"""
@spec from_iso8601(String.t) :: {:ok, t, Calendar.utc_offset} | {:error, atom}
def from_iso8601(<<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes, sep,
hour::2-bytes, ?:, min::2-bytes, ?:, sec::2-bytes, rest::binary>>) when sep in [?\s, ?T] do
with {year, ""} <- Integer.parse(year),
{month, ""} <- Integer.parse(month),
{day, ""} <- Integer.parse(day),
{hour, ""} <- Integer.parse(hour),
{min, ""} <- Integer.parse(min),
{sec, ""} <- Integer.parse(sec),
{microsec, rest} <- Calendar.ISO.parse_microsecond(rest),
{:ok, date} <- Calendar.ISO.date(year, month, day),
{:ok, time} <- Time.new(hour, min, sec, microsec),
{:ok, offset} <- parse_offset(rest) do
%{year: year, month: month, day: day} = date
%{hour: hour, minute: minute, second: second, microsecond: microsecond} = time
erl = {{year, month, day}, {hour, minute, second}}
seconds = :calendar.datetime_to_gregorian_seconds(erl)
{{year, month, day}, {hour, minute, second}} =
:calendar.gregorian_seconds_to_datetime(seconds - offset)
{:ok, %DateTime{year: year, month: month, day: day,
hour: hour, minute: minute, second: second, microsecond: microsecond,
std_offset: 0, utc_offset: 0, zone_abbr: "UTC", time_zone: "Etc/UTC"}, offset}
else
{:error, reason} -> {:error, reason}
_ -> {:error, :invalid_format}
end
end
def from_iso8601(_) do
{:error, :invalid_format}
end
defp parse_offset(rest) do
case Calendar.ISO.parse_offset(rest) do
{offset, ""} when is_integer(offset) -> {:ok, offset}
{nil, ""} -> {:error, :missing_offset}
_ -> {:error, :invalid_format}
end
end
@doc """
Converts the given datetime to a string according to its calendar.
+7 -3
View File
@@ -2399,7 +2399,11 @@ defmodule Kernel do
# @attribute(value)
defp do_at([arg], meta, name, function?, env) do
read? = :lists.keymember(:context, 1, meta)
line =
case :lists.keymember(:context, 1, meta) do
true -> nil
false -> env.line
end
cond do
function? ->
@@ -2413,11 +2417,11 @@ defmodule Kernel do
{stack, _} = :elixir_quote.escape(env_stacktrace(env), false)
arg = {env.line, arg}
quote do: Module.put_attribute(__MODULE__, unquote(name), unquote(arg),
unquote(stack), unquote(read?))
unquote(stack), unquote(line))
true ->
quote do: Module.put_attribute(__MODULE__, unquote(name), unquote(arg),
nil, unquote(read?))
nil, unquote(line))
end
end
+10 -10
View File
@@ -854,7 +854,7 @@ defmodule Module do
"""
@spec put_attribute(module, key :: atom, value :: term) :: term
def put_attribute(module, key, value) do
put_attribute(module, key, value, nil, false)
put_attribute(module, key, value, nil, nil)
end
@doc """
@@ -910,7 +910,7 @@ defmodule Module do
table = data_table_for(module)
case :ets.take(table, key) do
[{_, value, _accumulated? = true, _}] ->
:ets.insert(table, {key, [], true, true})
:ets.insert(table, {key, [], true, nil})
value
[{_, value, _, _}] ->
value
@@ -960,7 +960,7 @@ defmodule Module do
end
if Keyword.get(opts, :accumulate) do
:ets.insert_new(table, {new, [], _accumulated? = true, _read? = true}) ||
:ets.insert_new(table, {new, [], _accumulated? = true, _unread_line = nil}) ||
:ets.update_element(table, new, {3, true})
end
@@ -1031,7 +1031,7 @@ defmodule Module do
[] -> [value]
end
:ets.insert(table, {key, new, true, true})
:ets.insert(table, {key, new, true, nil})
end
@doc false
@@ -1043,7 +1043,7 @@ defmodule Module do
case :ets.lookup(table, key) do
[{^key, val, _, _}] ->
:ets.update_element(table, key, {4, true})
:ets.update_element(table, key, {4, nil})
val
[] when is_list(stack) ->
# TODO: Consider raising instead of warning on v2.0 as it usually cascades
@@ -1058,22 +1058,22 @@ defmodule Module do
@doc false
# Used internally by Kernel's @.
# This function is private and must be used only internally.
def put_attribute(module, key, value, stack, read?) when is_atom(key) do
def put_attribute(module, key, value, stack, unread_line) when is_atom(key) do
assert_not_compiled!(:put_attribute, module)
table = data_table_for(module)
value = preprocess_attribute(key, value)
case :ets.lookup(table, key) do
[{^key, {line, <<_::binary>>}, accumulated?, _read?}]
[{^key, {line, <<_::binary>>}, accumulated?, _unread_line}]
when key in [:doc, :typedoc, :moduledoc] and is_list(stack) ->
IO.warn "redefining @#{key} attribute previously set at line #{line}", stack
:ets.insert(table, {key, value, accumulated?, read?})
:ets.insert(table, {key, value, accumulated?, unread_line})
[{^key, current, _accumulated? = true, _read?}] ->
:ets.insert(table, {key, [value | current], true, read?})
:ets.insert(table, {key, [value | current], true, unread_line})
_ ->
:ets.insert(table, {key, value, false, read?})
:ets.insert(table, {key, value, false, unread_line})
end
value
+3 -1
View File
@@ -9,7 +9,9 @@ defmodule Set do
@type values :: [ value ]
@type t :: map
# TODO: Deprecate every function by 1.4
# TODO: Remove by 2.0
# (hard-deprecated in elixir_dispatch)
defmacrop target(set) do
quote do
case unquote(set) do
+7 -15
View File
@@ -10,29 +10,21 @@ defmodule Task.Supervised do
{:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])}
end
def start_link(caller, link, info, fun) do
{:ok, spawn_link(caller, link, info, fun)}
def start_link(caller, monitor, info, fun) do
{:ok, spawn_link(caller, monitor, info, fun)}
end
def spawn_link(caller, link \\ :nolink, info, fun) do
:proc_lib.spawn_link(__MODULE__, :reply, [caller, link, info, fun])
def spawn_link(caller, monitor \\ :nomonitor, info, fun) do
:proc_lib.spawn_link(__MODULE__, :reply, [caller, monitor, info, fun])
end
def reply(caller, link, info, mfa) do
def reply(caller, monitor, info, mfa) do
initial_call(mfa)
case link do
:link ->
try do
Process.link(caller)
catch
:error, :noproc ->
exit({:shutdown, :noproc})
end
reply(caller, nil, @ref_timeout, info, mfa)
case monitor do
:monitor ->
mref = Process.monitor(caller)
reply(caller, mref, @ref_timeout, info, mfa)
:nolink ->
:nomonitor ->
reply(caller, nil, :infinity, info, mfa)
end
end
+8 -8
View File
@@ -106,7 +106,7 @@ defmodule Task.Supervisor do
"""
@spec async_nolink(Supervisor.supervisor, module, atom, [term]) :: Task.t
def async_nolink(supervisor, module, fun, args) do
do_async(supervisor, :monitor, module, fun, args)
do_async(supervisor, :nolink, module, fun, args)
end
@doc """
@@ -181,7 +181,7 @@ defmodule Task.Supervisor do
Enumerable.t
def async_stream_nolink(supervisor, enumerable, module, function, args, options \\ [])
when is_atom(module) and is_atom(function) and is_list(args) do
build_stream(supervisor, :monitor, enumerable, {module, function, args}, options)
build_stream(supervisor, :nolink, enumerable, {module, function, args}, options)
end
@doc """
@@ -197,7 +197,7 @@ defmodule Task.Supervisor do
@spec async_stream_nolink(Supervisor.supervisor, Enumerable.t, (term -> term), Keyword.t) ::
Enumerable.t
def async_stream_nolink(supervisor, enumerable, fun, options \\ []) when is_function(fun, 1) do
build_stream(supervisor, :monitor, enumerable, fun, options)
build_stream(supervisor, :nolink, enumerable, fun, options)
end
@doc """
@@ -250,7 +250,7 @@ defmodule Task.Supervisor do
defp do_async(supervisor, link_type, module, fun, args) do
owner = self()
args = [owner, link_type, get_info(owner), {module, fun, args}]
args = [owner, :monitor, get_info(owner), {module, fun, args}]
{:ok, pid} = Supervisor.start_child(supervisor, args)
if link_type == :link, do: Process.link(pid)
ref = Process.monitor(pid)
@@ -258,12 +258,12 @@ defmodule Task.Supervisor do
%Task{pid: pid, ref: ref, owner: owner}
end
defp build_stream(supervisor, type, enumerable, fun, options) do
defp build_stream(supervisor, link_type, enumerable, fun, options) do
&Task.Supervised.stream(enumerable, &1, &2, fun, options, fn owner, mfa ->
args = [owner, type, get_info(owner), mfa]
args = [owner, :monitor, get_info(owner), mfa]
{:ok, pid} = Supervisor.start_child(supervisor, args)
if type == :link, do: Process.link(pid)
{type, pid}
if link_type == :link, do: Process.link(pid)
{link_type, pid}
end)
end
end
+3
View File
@@ -353,5 +353,8 @@ deprecation('Elixir.HashSet', _, _) ->
"use the MapSet module instead";
deprecation('Elixir.Dict', _, _) ->
"use the Map module for working with maps or the Keyword module for working with keyword lists";
deprecation('Elixir.Set', _, _) ->
"use the MapSet module for working with sets";
deprecation(_, _, _) ->
false.
+3 -1
View File
@@ -114,7 +114,9 @@ translate_gen(_Meta, Left, Right, T, S) ->
SF = SL#elixir_scope{extra=S#elixir_scope.extra, extra_guards=nil},
{TT, {TFilters, TS}} = translate_filters(T, SF),
Guards = ExtraGuards ++ translate_guards(TLeftGuards) ++ TFilters,
%% The list of guards is kept in reverse order
Guards = TFilters ++ translate_guards(TLeftGuards) ++ ExtraGuards,
{TLeft, TRight, Guards, TT, TS}.
translate_guards([]) ->
+22 -22
View File
@@ -85,7 +85,7 @@ do_compile(Line, Module, Block, Vars, E) ->
Forms3 = attributes_form(Line, File, Data, PersistedAttrs, Forms2),
elixir_locals:ensure_no_import_conflict(Line, File, Module, All),
warn_unused_attributes(Line, File, Data, PersistedAttrs),
warn_unused_attributes(File, Data, PersistedAttrs),
Location = {elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line},
@@ -152,26 +152,26 @@ build(Line, File, Module, Docs, Lexical) ->
end,
ets:insert(Data, [
% {Key, Value, Accumulate?, Read?}
{after_compile, [], true, true},
{before_compile, [], true, true},
{behaviour, [], true, true},
{compile, [], true, true},
{derive, [], true, true},
{dialyzer, [], true, true},
{external_resource, [], true, true},
{moduledoc, nil, false, true},
{on_definition, OnDefinition, true, true},
{on_load, [], true, true},
% {Key, Value, Accumulate?, UnreadLine}
{after_compile, [], true, nil},
{before_compile, [], true, nil},
{behaviour, [], true, nil},
{compile, [], true, nil},
{derive, [], true, nil},
{dialyzer, [], true, nil},
{external_resource, [], true, nil},
{moduledoc, nil, false, nil},
{on_definition, OnDefinition, true, nil},
{on_load, [], true, nil},
% Types
{callback, [], true, true},
{opaque, [], true, true},
{optional_callbacks, [], true, true},
{macrocallback, [], true, true},
{spec, [], true, true},
{type, [], true, true},
{typep, [], true, true}
{callback, [], true, nil},
{opaque, [], true, nil},
{optional_callbacks, [], true, nil},
{macrocallback, [], true, nil},
{spec, [], true, nil},
{type, [], true, nil},
{typep, [], true, nil}
]),
Persisted = [behaviour, on_load, compile, external_resource, dialyzer, vsn],
@@ -425,11 +425,11 @@ check_module_availability(Line, File, Module) ->
ok
end.
warn_unused_attributes(Line, File, Data, PersistedAttrs) ->
warn_unused_attributes(File, Data, PersistedAttrs) ->
ReservedAttrs = [after_compile, before_compile, moduledoc, on_definition | PersistedAttrs],
Keys = ets:select(Data, [{{'$1', '_', '_', false}, [{is_atom, '$1'}], ['$1']}]),
Keys = ets:select(Data, [{{'$1', '_', '_', '$2'}, [{is_atom, '$1'}, {is_integer, '$2'}], [['$1', '$2']]}]),
[elixir_errors:form_warn([{line, Line}], File, ?MODULE, {unused_attribute, Key}) ||
Key <- Keys, not lists:member(Key, ReservedAttrs)].
[Key, Line] <- Keys, not lists:member(Key, ReservedAttrs)].
% __INFO__
+33 -24
View File
@@ -189,12 +189,15 @@ do_quote({'__aliases__', Meta, [H | T]} = Alias, #elixir_quote{aliases_hygiene=t
Aliases when is_list(Aliases) -> false
end,
AliasMeta = keystore(alias, keydelete(counter, Meta), Annotation),
do_quote_tuple({'__aliases__', AliasMeta, [H | T]}, Q, E);
do_quote_tuple('__aliases__', AliasMeta, [H | T], Q, E);
%% Vars
do_quote({Left, Meta, nil}, #elixir_quote{vars_hygiene=true, imports_hygiene=true} = Q, E) when is_atom(Left) ->
do_quote_import(Left, Meta, Q#elixir_quote.context, Q, E);
do_quote({Left, Meta, nil}, #elixir_quote{vars_hygiene=true} = Q, E) when is_atom(Left) ->
do_quote_tuple({Left, Meta, Q#elixir_quote.context}, Q, E);
do_quote_tuple(Left, Meta, Q#elixir_quote.context, Q, E);
%% Unquote
@@ -207,28 +210,11 @@ do_quote({{'.', Meta, [Left, unquote]}, _, [Expr]}, #elixir_quote{unquote=true}
%% Imports
do_quote({'&', Meta, [{'/', _, [{F, _, C}, A]}] = Args},
#elixir_quote{imports_hygiene=true} = Q, E) when is_atom(F), is_integer(A), is_atom(C) ->
#elixir_quote{imports_hygiene=true} = Q, E) when is_atom(F), is_integer(A), is_atom(C) ->
do_quote_fa('&', Meta, Args, F, A, Q, E);
do_quote({Name, Meta, ArgsOrAtom}, #elixir_quote{imports_hygiene=true} = Q, E) when is_atom(Name) ->
Arity = case is_atom(ArgsOrAtom) of
true -> 0;
false -> length(ArgsOrAtom)
end,
NewMeta = case (keyfind(import, Meta) == false) andalso
elixir_dispatch:find_import(Meta, Name, Arity, E) of
false ->
case (Arity == 1) andalso keyfind(ambiguous_op, Meta) of
{ambiguous_op, nil} -> keystore(ambiguous_op, Meta, Q#elixir_quote.context);
_ -> Meta
end;
Receiver ->
keystore(import, keystore(context, Meta, Q#elixir_quote.context), Receiver)
end,
Annotated = annotate({Name, NewMeta, ArgsOrAtom}, Q#elixir_quote.context),
do_quote_tuple(Annotated, Q, E);
do_quote_import(Name, Meta, ArgsOrAtom, Q, E);
do_quote({_, _, _} = Tuple, #elixir_quote{escape=false} = Q, E) ->
Annotated = annotate(Tuple, Q#elixir_quote.context),
@@ -300,6 +286,26 @@ bad_escape(Arg) ->
"The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, ",
"PIDs and remote functions in the format &Mod.fun/arity">>).
do_quote_import(Name, Meta, ArgsOrAtom, #elixir_quote{imports_hygiene=true} = Q, E) ->
Arity = case is_atom(ArgsOrAtom) of
true -> 0;
false -> length(ArgsOrAtom)
end,
NewMeta = case (keyfind(import, Meta) == false) andalso
elixir_dispatch:find_import(Meta, Name, Arity, E) of
false ->
case (Arity == 1) andalso keyfind(ambiguous_op, Meta) of
{ambiguous_op, nil} -> keystore(ambiguous_op, Meta, Q#elixir_quote.context);
_ -> Meta
end;
Receiver ->
keystore(import, keystore(context, Meta, Q#elixir_quote.context), Receiver)
end,
Annotated = annotate({Name, NewMeta, ArgsOrAtom}, Q#elixir_quote.context),
do_quote_tuple(Annotated, Q, E).
do_quote_call(Left, Meta, Expr, Args, Q, E) ->
All = [meta(Meta, Q), Left, {unquote, Meta, [Expr]}, Args,
Q#elixir_quote.context],
@@ -313,13 +319,16 @@ do_quote_fa(Target, Meta, Args, F, A, Q, E) ->
false -> Meta;
Receiver -> keystore(import_fa, Meta, {Receiver, Q#elixir_quote.context})
end,
do_quote_tuple({Target, NewMeta, Args}, Q, E).
do_quote_tuple(Target, NewMeta, Args, Q, E).
do_quote_tuple({Left, Meta, Right}, Q, E) ->
do_quote_tuple(Left, Meta, Right, Q, E).
% In a def unquote(name)(args) expression name will be an atom literal,
% thus location: :keep will not have enough information to generate the proper file/line annotation.
% This alters metadata to force Elixir to show the file to which the definition is added
% instead of the file where definition is quoted (i.e. we behave the opposite to location: :keep).
do_quote_tuple({Left, Meta, [{{unquote, _, _}, _, _}, _] = Right}, Q, E) when ?defs(Left) ->
do_quote_tuple(Left, Meta, [{{unquote, _, _}, _, _}, _] = Right, Q, E) when ?defs(Left) ->
{TLeft, LQ} = do_quote(Left, Q, E),
{[Head, Body], RQ} = do_quote(Right, LQ, E),
{'{}', [], [HLeft, HMeta, HRight]} = Head,
@@ -327,7 +336,7 @@ do_quote_tuple({Left, Meta, [{{unquote, _, _}, _, _}, _] = Right}, Q, E) when ?d
NewHead = {'{}', [], [HLeft, NewMeta, HRight]},
{{'{}', [], [TLeft, meta(Meta, Q), [NewHead, Body]]}, RQ};
do_quote_tuple({Left, Meta, Right}, Q, E) ->
do_quote_tuple(Left, Meta, Right, Q, E) ->
{TLeft, LQ} = do_quote(Left, Q, E),
{TRight, RQ} = do_quote(Right, LQ, E),
{{'{}', [], [TLeft, meta(Meta, Q), TRight]}, RQ}.
@@ -51,6 +51,12 @@ defmodule Kernel.ComprehensionTest do
assert for(x when x == 3 when x == 7 <- 1..10, do: x) == [3, 7]
end
test "for comprehensions with guards and filters" do
assert for({var, _} when is_atom(var) <- [{:foo, 1}, {2, :bar}],
var = Atom.to_string(var),
do: var) == ["foo"]
end
test "for comprehensions with map key matching" do
maps = [%{x: 1}, %{y: 2}, %{x: 3}]
assert for(%{x: v} <- maps, do: v * 2) == [2, 6]
@@ -153,7 +153,7 @@ defmodule Kernel.ExpansionTest do
end
test "locals: in guards" do
assert expand(quote(do: fn pid when :erlang.==(pid, self) -> pid end)) ==
assert expand_and_clean(quote(do: fn pid when :erlang.==(pid, self) -> pid end), [:import, :context]) ==
quote(do: fn pid when :erlang.==(pid, :erlang.self()) -> pid end)
end
@@ -337,22 +337,22 @@ defmodule Kernel.ExpansionTest do
## Cond
test "cond: expands each clause" do
assert expand_and_clean(quote do: (cond do x = 1 -> x; _ -> x end)) ==
assert expand(quote do: (cond do x = 1 -> x; _ -> x end)) ==
quote do: (cond do x = 1 -> x; _ -> x() end)
end
test "cond: does not share lexical scope between clauses" do
assert expand_and_clean(quote do: (cond do 1 -> import List; 2 -> flatten([1, 2, 3]) end)) ==
assert expand(quote do: (cond do 1 -> import List; 2 -> flatten([1, 2, 3]) end)) ==
quote do: (cond do 1 -> import :"Elixir.List", []; 2 -> flatten([1, 2, 3]) end)
end
test "cond: does not leaks vars on head" do
assert expand_and_clean(quote do: (cond do x = 1 -> x; y = 2 -> y end; :erlang.+(x, y))) ==
assert expand(quote do: (cond do x = 1 -> x; y = 2 -> y end; :erlang.+(x, y))) ==
quote do: (cond do x = 1 -> x; y = 2 -> y end; :erlang.+(x(), y()))
end
test "cond: leaks vars" do
assert expand_and_clean(quote do: (cond do 1 -> x = 1; 2 -> y = 2 end; :erlang.+(x, y))) ==
assert expand(quote do: (cond do 1 -> x = 1; 2 -> y = 2 end; :erlang.+(x, y))) ==
quote do: (cond do 1 -> x = 1; 2 -> y = 2 end; :erlang.+(x, y))
end
@@ -365,27 +365,27 @@ defmodule Kernel.ExpansionTest do
## Case
test "case: expands each clause" do
assert expand_and_clean(quote do: (case w do x -> x; _ -> x end)) ==
assert expand(quote do: (case w do x -> x; _ -> x end)) ==
quote do: (case w() do x -> x; _ -> x() end)
end
test "case: does not share lexical scope between clauses" do
assert expand_and_clean(quote do: (case w do 1 -> import List; 2 -> flatten([1, 2, 3]) end)) ==
assert expand(quote do: (case w do 1 -> import List; 2 -> flatten([1, 2, 3]) end)) ==
quote do: (case w() do 1 -> import :"Elixir.List", []; 2 -> flatten([1, 2, 3]) end)
end
test "case: expands guards" do
assert expand_and_clean(quote do: (case w do x when x when __ENV__.context -> true end)) ==
assert expand(quote do: (case w do x when x when __ENV__.context -> true end)) ==
quote do: (case w() do x when x when :guard -> true end)
end
test "case: does not leaks vars on head" do
assert expand_and_clean(quote do: (case w do x -> x; y -> y end; :erlang.+(x, y))) ==
assert expand(quote do: (case w do x -> x; y -> y end; :erlang.+(x, y))) ==
quote do: (case w() do x -> x; y -> y end; :erlang.+(x(), y()))
end
test "case: leaks vars" do
assert expand_and_clean(quote do: (case w do x -> x = x; y -> y = y end; :erlang.+(x, y))) ==
assert expand(quote do: (case w do x -> x = x; y -> y = y end; :erlang.+(x, y))) ==
quote do: (case w() do x -> x = x; y -> y = y end; :erlang.+(x, y))
end
@@ -398,32 +398,32 @@ defmodule Kernel.ExpansionTest do
## Receive
test "receive: expands each clause" do
assert expand_and_clean(quote do: (receive do x -> x; _ -> x end)) ==
assert expand(quote do: (receive do x -> x; _ -> x end)) ==
quote do: (receive do x -> x; _ -> x() end)
end
test "receive: does not share lexical scope between clauses" do
assert expand_and_clean(quote do: (receive do 1 -> import List; 2 -> flatten([1, 2, 3]) end)) ==
assert expand(quote do: (receive do 1 -> import List; 2 -> flatten([1, 2, 3]) end)) ==
quote do: (receive do 1 -> import :"Elixir.List", []; 2 -> flatten([1, 2, 3]) end)
end
test "receive: expands guards" do
assert expand_and_clean(quote do: (receive do x when x when __ENV__.context -> true end)) ==
assert expand(quote do: (receive do x when x when __ENV__.context -> true end)) ==
quote do: (receive do x when x when :guard -> true end)
end
test "receive: does not leaks clause vars" do
assert expand_and_clean(quote do: (receive do x -> x; y -> y end; :erlang.+(x, y))) ==
assert expand(quote do: (receive do x -> x; y -> y end; :erlang.+(x, y))) ==
quote do: (receive do x -> x; y -> y end; :erlang.+(x(), y()))
end
test "receive: leaks vars" do
assert expand_and_clean(quote do: (receive do x -> x = x; y -> y = y end; :erlang.+(x, y))) ==
assert expand(quote do: (receive do x -> x = x; y -> y = y end; :erlang.+(x, y))) ==
quote do: (receive do x -> x = x; y -> y = y end; :erlang.+(x, y))
end
test "receive: leaks vars on after" do
assert expand_and_clean(quote do: (receive do x -> x = x after y -> y; w = y end; :erlang.+(x, w))) ==
assert expand(quote do: (receive do x -> x = x after y -> y; w = y end; :erlang.+(x, w))) ==
quote do: (receive do x -> x = x after y() -> y(); w = y() end; :erlang.+(x, w))
end
@@ -540,8 +540,8 @@ defmodule Kernel.ExpansionTest do
13
end
defp expand_and_clean(expr) do
cleaner = &Keyword.drop(&1, [:export])
defp expand_and_clean(expr, vars) do
cleaner = &Keyword.drop(&1, vars)
expr
|> expand_env(__ENV__)
|> elem(0)
@@ -434,4 +434,18 @@ defmodule Kernel.QuoteTest.ImportsHygieneTest do
test "explicitly overridden imports" do
assert with_length() == 5
end
defmodule BinaryUtils do
defmacro int32 do
quote do
integer-size(32)
end
end
end
test "checks the context also for variables to zero-arity functions" do
import BinaryUtils
{:int32, meta, __MODULE__} = quote do: int32
assert meta[:import] == BinaryUtils
end
end
@@ -685,12 +685,11 @@ defmodule Kernel.WarningTest do
Code.eval_string """
defmodule Sample do
@at "Something"
Module.put_attribute(__MODULE__, :put_attribute, "Something")
end
"""
end)
assert content =~ "module attribute @at was set but never used"
assert content =~ "module attribute @put_attribute was set but never used"
assert content =~ "nofile:2"
after
purge Sample
end
+106 -31
View File
@@ -1,20 +1,22 @@
defmodule IEx.Autocomplete do
@moduledoc false
def expand('') do
def expand(expr, server \\ IEx.Server)
def expand('', _server) do
expand_import("")
end
def expand([h | t]=expr) do
def expand([h | t]=expr, server) do
cond do
h === ?. and t != [] ->
expand_dot(reduce(t))
expand_dot(reduce(t), server)
h === ?: and t == [] ->
expand_erlang_modules()
identifier?(h) ->
expand_expr(reduce(expr))
expand_expr(reduce(expr), server)
(h == ?/) and t != [] and identifier?(hd(t)) ->
expand_expr(reduce(t))
expand_expr(reduce(t), server)
h in '([{' ->
expand('')
true ->
@@ -26,31 +28,33 @@ defmodule IEx.Autocomplete do
(h in ?a..?z) or (h in ?A..?Z) or (h in ?0..?9) or h in [?_, ??, ?!]
end
defp expand_dot(expr) do
defp expand_dot(expr, server) do
case Code.string_to_quoted expr do
{:ok, atom} when is_atom(atom) ->
expand_call(atom, "")
expand_call(atom, "", server)
{:ok, {:__aliases__, _, list}} ->
expand_elixir_modules(list, "")
expand_elixir_modules(list, "", server)
{:ok, {_, _, _} = ast_node} ->
expand_call(ast_node, "", server)
_ ->
no()
end
end
defp expand_expr(expr) do
defp expand_expr(expr, server) do
case Code.string_to_quoted expr do
{:ok, atom} when is_atom(atom) ->
expand_erlang_modules(Atom.to_string(atom))
{:ok, {atom, _, nil}} when is_atom(atom) ->
expand_import(Atom.to_string(atom))
{:ok, {:__aliases__, _, [root]}} ->
expand_elixir_modules([], Atom.to_string(root))
expand_elixir_modules([], Atom.to_string(root), server)
{:ok, {:__aliases__, _, [h | _] = list}} when is_atom(h) ->
hint = Atom.to_string(List.last(list))
list = Enum.take(list, length(list) - 1)
expand_elixir_modules(list, hint)
{:ok, {{:., _, [mod, fun]}, _, []}} when is_atom(fun) ->
expand_call(mod, Atom.to_string(fun))
expand_elixir_modules(list, hint, server)
{:ok, {{:., _, [ast_node, fun]}, _, []}} when is_atom(fun) ->
expand_call(ast_node, Atom.to_string(fun), server)
_ ->
no()
end
@@ -105,21 +109,37 @@ defmodule IEx.Autocomplete do
## Expand calls
# :atom.fun
defp expand_call(mod, hint) when is_atom(mod) do
defp expand_call(mod, hint, _server) when is_atom(mod) do
expand_require(mod, hint)
end
# Elixir.fun
defp expand_call({:__aliases__, _, list}, hint) do
expand_alias(list)
defp expand_call({:__aliases__, _, list}, hint, server) do
expand_alias(list, server)
|> normalize_module
|> expand_require(hint)
end
defp expand_call(_, _) do
# variable.fun_or_key
defp expand_call({_, _, _} = ast_node, hint, server) do
case value_from_binding(ast_node, server) do
{:ok, mod} when is_atom(mod) -> expand_call(mod, hint, server)
{:ok, map} when is_map(map) -> expand_map_field_access(map, hint)
_otherwise -> no()
end
end
defp expand_call(_, _, _) do
no()
end
defp expand_map_field_access(map, hint) do
case match_map_fields(map, hint) do
[%{kind: :map_key, name: name, value_is_map: false}] when name == hint -> no()
map_fields when is_list(map_fields) -> format_expansion(map_fields, hint)
end
end
defp expand_require(mod, hint) do
format_expansion match_module_funs(mod, hint), hint
end
@@ -145,26 +165,27 @@ defmodule IEx.Autocomplete do
## Elixir modules
defp expand_elixir_modules([], hint) do
expand_elixir_modules(Elixir, hint, match_aliases(hint))
defp expand_elixir_modules([], hint, server) do
aliases = match_aliases(hint, server)
expand_elixir_modules_from_aliases(Elixir, hint, aliases)
end
defp expand_elixir_modules(list, hint) do
expand_alias(list)
defp expand_elixir_modules(list, hint, server) do
expand_alias(list, server)
|> normalize_module
|> expand_elixir_modules(hint, [])
|> expand_elixir_modules_from_aliases(hint, [])
end
defp expand_elixir_modules(mod, hint, aliases) do
defp expand_elixir_modules_from_aliases(mod, hint, aliases) do
aliases
|> Kernel.++(match_elixir_modules(mod, hint))
|> Kernel.++(match_module_funs(mod, hint))
|> format_expansion(hint)
end
defp expand_alias([name | rest] = list) do
defp expand_alias([name | rest] = list, server) do
module = Module.concat(Elixir, name)
Enum.find_value env_aliases(), list, fn {alias, mod} ->
Enum.find_value aliases_from_env(server), list, fn {alias, mod} ->
if alias === module do
case Atom.to_string(mod) do
"Elixir." <> mod ->
@@ -176,12 +197,8 @@ defmodule IEx.Autocomplete do
end
end
defp env_aliases do
Application.get_env(:iex, :autocomplete_server).current_env.aliases
end
defp match_aliases(hint) do
for {alias, _mod} <- env_aliases(),
defp match_aliases(hint, server) do
for {alias, _mod} <- aliases_from_env(server),
[name] = Module.split(alias),
starts_with?(name, hint) do
%{kind: :module, type: :alias, name: name}
@@ -269,6 +286,14 @@ defmodule IEx.Autocomplete do
end
end
defp match_map_fields(map, hint) do
for {key, value} <- map,
is_atom(key),
key = to_string(key),
String.starts_with?(key, hint),
do: %{kind: :map_key, name: key, value_is_map: is_map(value)}
end
defp get_module_funs(mod) do
docs = Code.get_docs(mod, :docs) || []
module_info_funs(mod) |> Enum.reject(&hidden_fun?(&1, docs))
@@ -316,6 +341,10 @@ defmodule IEx.Autocomplete do
for a <- :lists.sort(arities), do: "#{name}/#{a}"
end
defp to_entries(%{kind: :map_key, name: name}) do
[name]
end
defp to_uniq_entries(%{kind: :module}) do
[]
end
@@ -324,6 +353,10 @@ defmodule IEx.Autocomplete do
to_entries(fun)
end
defp to_uniq_entries(%{kind: :map_key}) do
[]
end
defp to_hint(%{kind: :module, name: name}, hint) when name == hint do
format_hint(name, name) <> "."
end
@@ -336,8 +369,50 @@ defmodule IEx.Autocomplete do
format_hint(name, hint)
end
defp to_hint(%{kind: :map_key, name: name, value_is_map: true}, hint) when name == hint do
format_hint(name, hint) <> "."
end
defp to_hint(%{kind: :map_key, name: name}, hint) do
format_hint(name, hint)
end
defp format_hint(name, hint) do
hint_size = byte_size(hint)
:binary.part(name, hint_size, byte_size(name) - hint_size)
end
defp aliases_from_env(server) do
with evaluator when is_pid(evaluator) <- server.evaluator,
{:ok, aliases} <- IEx.Evaluator.value_from_env(evaluator, :aliases) do
aliases
else
_ -> []
end
end
defp value_from_binding(ast_node, server) do
with evaluator when is_pid(evaluator) <- server.evaluator(),
{var, map_key_path} <- extract_from_ast(ast_node, []) do
IEx.Evaluator.value_from_binding(evaluator, var, map_key_path)
else
_ -> :error
end
end
defp extract_from_ast(var_name, acc) when is_atom(var_name) do
{var_name, acc}
end
defp extract_from_ast({var_name, _, nil}, acc) when is_atom(var_name) do
{var_name, acc}
end
defp extract_from_ast({{:., _, [ast_node, fun]}, _, []}, acc) when is_atom(fun) do
extract_from_ast(ast_node, [fun | acc])
end
defp extract_from_ast(_ast_node, _acc) do
:error
end
end
+46 -2
View File
@@ -24,20 +24,64 @@ defmodule IEx.Evaluator do
end
end
@doc """
Gets a value out of the binding, using the provided
variable name and map key path.
"""
@spec value_from_binding(pid, atom, [atom]) :: {:ok, any} | :error
def value_from_binding(evaluator, var_name, map_key_path) do
ref = make_ref()
send evaluator, {:value_from_binding, ref, self(), var_name, map_key_path}
receive do
{^ref, result} -> result
after
5000 -> :error
end
end
@doc """
Returns the current session environment if a session exists.
"""
@spec value_from_env(pid, atom) :: {:ok, term} | :error
def value_from_env(evaluator, key) do
ref = make_ref()
send evaluator, {:value_from_env, ref, self(), key}
receive do
{^ref, result} -> result
after
5000 -> :error
end
end
defp loop(server, history, state) do
receive do
{:eval, ^server, code, iex_state} ->
{result, history, state} = eval(code, iex_state, history, state)
send server, {:evaled, self(), result}
loop(server, history, state)
{:peek_env, receiver} ->
send receiver, {:peek_env, state.env}
{:value_from_env, ref, receiver, key} ->
send receiver, {ref, Map.fetch(state.env, key)}
loop(server, history, state)
{:value_from_binding, ref, receiver, var_name, map_key_path} ->
value = traverse_binding(state.binding, var_name, map_key_path)
send receiver, {ref, value}
loop(server, history, state)
{:done, ^server} ->
:ok
end
end
defp traverse_binding(binding, var_name, map_key_path) do
accumulator = Keyword.fetch(binding, var_name)
Enum.reduce map_key_path, accumulator, fn
key, {:ok, map} when is_map(map) -> Map.fetch(map, key)
_key, _acc -> :error
end
end
defp loop_state(opts) do
env =
if env = opts[:env] do
+13 -16
View File
@@ -43,19 +43,15 @@ defmodule IEx.Server do
end
@doc """
Returns the current session environment if a session exists.
Returns the PID of the IEx evaluator process if it exists.
"""
@spec current_env :: Macro.Env.t
def current_env() do
case IEx.Server.whereis() do
nil -> %Macro.Env{}
server ->
send(server, {:peek_env, self()})
receive do
{:peek_env, %Macro.Env{} = env} -> env
after
5000 -> %Macro.Env{}
end
@spec evaluator :: pid | nil
def evaluator() do
case IEx.Server.local do
nil -> nil
pid ->
{:dictionary, dictionary} = Process.info(pid, :dictionary)
dictionary[:evaluator]
end
end
@@ -144,7 +140,11 @@ defmodule IEx.Server do
loop(run_state(opts), evaluator, Process.monitor(evaluator))
end
defp start_evaluator(opts) do
@doc """
Starst an evaluator using the provided options.
"""
@spec start_evaluator(Keyword.t) :: pid
def start_evaluator(opts) do
self_pid = self()
self_leader = Process.group_leader
evaluator = opts[:evaluator] ||
@@ -192,9 +192,6 @@ defmodule IEx.Server do
exit_loop(evaluator, evaluator_ref)
{:input, ^input, {:error, :terminated}} ->
exit_loop(evaluator, evaluator_ref)
{:peek_env, receiver} ->
send evaluator, {:peek_env, receiver}
wait_input(state, evaluator, evaluator_ref, input)
msg ->
handle_take_over(msg, evaluator, evaluator_ref, input, fn ->
wait_input(state, evaluator, evaluator_ref, input)
-1
View File
@@ -11,7 +11,6 @@ defmodule IEx.Mixfile do
[registered: [IEx.Supervisor, IEx.Config],
mod: {IEx.App, []},
env: [
autocomplete_server: IEx.Server,
colors: [],
inspect: [pretty: true],
history_size: 20,
+80 -11
View File
@@ -3,8 +3,29 @@ Code.require_file "../test_helper.exs", __DIR__
defmodule IEx.AutocompleteTest do
use ExUnit.Case, async: true
setup context do
previous_line = context[:previous_line]
if previous_line do
ExUnit.CaptureIO.capture_io(fn ->
evaluator = IEx.Server.start_evaluator([])
Process.put(:evaluator, evaluator)
send evaluator, {:eval, self(), previous_line <> "\n", %IEx.State{}}
assert_receive {:evaled, _, _}
end)
end
:ok
end
defmodule MyServer do
def evaluator do
Process.get(:evaluator)
end
end
def expand(expr) do
IEx.Autocomplete.expand(Enum.reverse expr)
IEx.Autocomplete.expand(Enum.reverse(expr), MyServer)
end
test "Erlang module completion" do
@@ -93,6 +114,62 @@ defmodule IEx.AutocompleteTest do
assert expand('String.printable?/') == {:yes, '', ['printable?/1']}
end
@tag previous_line: "mod = String"
test "function completion using a variable bound to a module" do
assert expand('mod.print') == {:yes, 'able?', []}
end
@tag previous_line: "map = %{foo: 1, bar_1: 23, bar_2: 14}"
test "map atom key completion is supported" do
assert expand('map.f') == {:yes, 'oo', []}
assert expand('map.b') == {:yes, 'ar_', []}
assert expand('map.bar_') == {:yes, '', ['bar_1', 'bar_2']}
assert expand('map.c') == {:no, '', []}
assert expand('map.') == {:yes, '', ['bar_1', 'bar_2', 'foo']}
assert expand('map.foo') == {:no, '', []}
end
@tag previous_line: "map = %{nested: %{deeply: %{foo: 1, bar_1: 23, bar_2: 14, mod: String, num: 1}}}"
test "nested map atom key completion is supported" do
assert expand('map.nested.deeply.f') == {:yes, 'oo', []}
assert expand('map.nested.deeply.b') == {:yes, 'ar_', []}
assert expand('map.nested.deeply.bar_') == {:yes, '', ['bar_1', 'bar_2']}
assert expand('map.nested.deeply.') == {:yes, '', ['bar_1', 'bar_2', 'foo', 'mod', 'num']}
assert expand('map.nested.deeply.mod.print') == {:yes, 'able?', []}
assert expand('map.nested') == {:yes, '.', []}
assert expand('map.nested.deeply') == {:yes, '.', []}
assert expand('map.nested.deeply.foo') == {:no, '', []}
assert expand('map.nested.deeply.c') == {:no, '', []}
assert expand('map.a.b.c.f') == {:no, '', []}
end
@tag previous_line: ~s(map = %{"foo" => 1})
test "map string key completion is not supported" do
assert expand('map.f') == {:no, '', []}
end
@tag previous_line: "num = 5; map = %{nested: %{num: 23}}"
test "autocompletion off a bound variable only works for modules and maps" do
assert expand('num.print') == {:no, '', []}
assert expand('map.nested.num.f') == {:no, '', []}
assert expand('map.nested.num.key.f') == {:no, '', []}
end
@tag previous_line: "map = %{nested: %{deeply: %{num: 23}}}"
test "autocompletion using access syntax does is not supported" do
assert expand('map[:nested][:deeply].n') == {:no, '', []}
assert expand('map[:nested].deeply.n') == {:no, '', []}
assert expand('map.nested.[:deeply].n') == {:no, '', []}
end
@tag previous_line: "num = 5"
test "autocompletion off of unbound variables is not supported" do
assert expand('other_var.f') == {:no, '', []}
assert expand('a.b.c.d') == {:no, '', []}
end
test "macro completion" do
{:yes, '', list} = expand('Kernel.is_')
assert is_list(list)
@@ -136,23 +213,15 @@ defmodule IEx.AutocompleteTest do
assert expand('IEx.AutocompleteTest.SublevelTest.') == {:yes, 'LevelA', []}
end
defmodule MyServer do
def current_env do
%Macro.Env{aliases: [{MyList, List}, {EList, :lists}]}
end
end
@tag previous_line: "alias List, as: MyList"
test "complete aliases of Elixir modules" do
Application.put_env(:iex, :autocomplete_server, MyServer)
assert expand('MyL') == {:yes, 'ist', []}
assert expand('MyList') == {:yes, '.', []}
assert expand('MyList.to_integer') == {:yes, [], ['to_integer/1', 'to_integer/2']}
end
@tag previous_line: "alias :lists, as: EList"
test "complete aliases of Erlang modules" do
Application.put_env(:iex, :autocomplete_server, MyServer)
assert expand('EL') == {:yes, 'ist', []}
assert expand('EList') == {:yes, '.', []}
assert expand('EList.map') == {:yes, [], ['map/2', 'mapfoldl/3', 'mapfoldr/3']}
+14 -4
View File
@@ -7,14 +7,24 @@ defmodule Mix.Tasks.Archive.Build do
Builds an archive according to the specification of the
[Erlang Archive Format](http://www.erlang.org/doc/man/code.html).
Archives are meant to contain small projects, usually installed
locally. Archives may be installed into a Mix environment by
running `mix archive.install`. Once installed, the archive is
available to all Mix projects. For this reason, the functionality
behind archives is limited. For instance, archives do not include
dependencies, as those would conflict with any dependency in a
Mix project after the archive is installed. In general, we recommend
the usage of archives to be limited for extensions of Mix, such
as custom SCMs, package managers, etc. For general scripts to be
installed into machines, please see `mix escript.build`.
The archive will be created in the current directory (which is
expected to be the project root), unless an argument `-o` is
provided with the file name.
Archives are meant to contain small projects, usually installed
locally. By default, this command archives the current project
but the `-i` and `-o` options can be used to archive any directory.
For example, `mix archive.build` with no options translates to:
By default, this command archives the current project but the `-i`
option can be used to archive any directory. For example,
`mix archive.build` with no options translates to:
mix archive.build -i _build/ENV/lib/APP -o APP-VERSION.ez
+6 -5
View File
@@ -6,14 +6,15 @@ defmodule Mix.Tasks.Archive.Install do
@moduledoc """
Installs an archive locally.
If no argument is supplied but there is an archive in the project's root directory
(created with `mix archive.build`), then the archive will be installed
locally. For example:
If no argument is supplied but there is an archive in the project's
root directory (created with `mix archive.build`), then the archive
will be installed locally. For example:
mix do archive.build, archive.install
If an argument is provided, it should be a local path or a URL to a prebuilt archive,
a git repository, a github repository, or a hex package.
If an argument is provided, it should be a local path or a URL to a
prebuilt archive, a git repository, a github repository, or a hex
package.
mix archive.install archive.ez
mix archive.install path/to/archive.ez
+1 -1
View File
@@ -18,7 +18,7 @@ defmodule Mix.Tasks.Compile.App do
function in your `mix.exs` with the following options:
* `:applications` - all applications your application depends
on at runtime. By default, this list is automatically inflected
on at runtime. By default, this list is automatically inferred
from your dependencies. Any extra Erlang/Elixir dependency
must be specified in `:extra_applications`. Mix and other tools
use the application list in order to start your dependencies
+5 -2
View File
@@ -197,10 +197,13 @@ defmodule Mix.Tasks.Escript.Build do
extra_apps =
if function_exported?(mod, :application, 0) do
mod.application[:applications]
app = mod.application()
Keyword.get(app, :applications, []) ++ Keyword.get(app, :extra_applications, [])
else
[]
end
Enum.filter(extra_apps || [], &(&1 in [:eex, :ex_unit, :mix, :iex, :logger]))
Enum.filter(extra_apps, &(&1 in [:eex, :ex_unit, :mix, :iex, :logger]))
end
defp app_files(app) do
+1 -1
View File
@@ -85,7 +85,7 @@ defmodule Mix.Tasks.Compile.AppTest do
end
end
test "automatically inflects applications" do
test "automatically infers applications" do
Mix.Project.push CustomDeps
in_fixture "no_mixfile", fn ->
+1 -1
View File
@@ -47,7 +47,7 @@ defmodule Mix.Tasks.EscriptTest do
end
def application do
[applications: []]
[applications: [], extra_applications: [:crypto]]
end
end
+1 -1
View File
@@ -1,6 +1,6 @@
{application, elixir,
[{description, "elixir"},
{vsn, "1.4.0-rc.0"},
{vsn, "1.4.0-rc.1"},
{modules, [
elixir
]},