Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16a14c1f6d | ||
|
|
cfa6cf220e | ||
|
|
5e1b7d9470 | ||
|
|
38baa729da | ||
|
|
5fe53f7b7a | ||
|
|
276b0d3c72 | ||
|
|
abb8817a0a | ||
|
|
5309843d2b | ||
|
|
1a2c2dcda6 | ||
|
|
03b6c07215 | ||
|
|
d10b9d5b29 | ||
|
|
e38be075c3 | ||
|
|
28824e8171 | ||
|
|
3edc407713 |
+11
-6
@@ -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,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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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([]) ->
|
||||
|
||||
@@ -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__
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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']}
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -47,7 +47,7 @@ defmodule Mix.Tasks.EscriptTest do
|
||||
end
|
||||
|
||||
def application do
|
||||
[applications: []]
|
||||
[applications: [], extra_applications: [:crypto]]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{application, elixir,
|
||||
[{description, "elixir"},
|
||||
{vsn, "1.4.0-rc.0"},
|
||||
{vsn, "1.4.0-rc.1"},
|
||||
{modules, [
|
||||
elixir
|
||||
]},
|
||||
|
||||
Reference in New Issue
Block a user