Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44b0412042 | ||
|
|
d984a82587 | ||
|
|
e59d50a891 | ||
|
|
7bf568d0b3 | ||
|
|
f95cafe5ca | ||
|
|
f0e1443f21 | ||
|
|
cf619585a6 | ||
|
|
c639b01146 | ||
|
|
b4415f2a14 | ||
|
|
538b51c20a | ||
|
|
c8a0158791 | ||
|
|
b088d4b90e | ||
|
|
02a415facd | ||
|
|
5cee19067d | ||
|
|
3496681236 | ||
|
|
a7645a2c8b | ||
|
|
d9423627f3 | ||
|
|
5a8520fb0f | ||
|
|
4458ee1232 | ||
|
|
a6b1cca91f | ||
|
|
e2535731c8 | ||
|
|
393ff69b1e | ||
|
|
033747ad63 | ||
|
|
730d043631 | ||
|
|
c819bdbf74 | ||
|
|
64f8541ee6 | ||
|
|
d1f4e2d49b | ||
|
|
5a13aecbae | ||
|
|
61904ee24c | ||
|
|
5c7fdcc244 | ||
|
|
f772877039 | ||
|
|
e571218917 | ||
|
|
aa01dd1fc1 | ||
|
|
30971c9cb3 | ||
|
|
abfc69a6e2 | ||
|
|
ac1fc484e4 | ||
|
|
889bfebe4e | ||
|
|
e9899a5462 | ||
|
|
ec92bb118d | ||
|
|
fd9ca435b7 | ||
|
|
3ec0d20621 | ||
|
|
16f3b928a1 | ||
|
|
fb20b6ddf6 | ||
|
|
d77a4d6c3f | ||
|
|
d7900239c2 | ||
|
|
babd98be3f | ||
|
|
9a85b1556b | ||
|
|
d1da200af5 | ||
|
|
e260b92f89 | ||
|
|
e09e5c3f41 | ||
|
|
a76a281309 | ||
|
|
be42d00067 | ||
|
|
f21a998f81 | ||
|
|
89e0018d58 | ||
|
|
21b691cd4e | ||
|
|
a8a91c2282 | ||
|
|
5275646966 | ||
|
|
e60cd6a484 | ||
|
|
552006bea3 | ||
|
|
2077b584ad | ||
|
|
29f05fda35 | ||
|
|
cbb7c545e0 | ||
|
|
c909267077 | ||
|
|
a9bbabfabc | ||
|
|
c4d666a4ba | ||
|
|
8e824d3da1 | ||
|
|
41caa0d635 | ||
|
|
3a0108edf8 | ||
|
|
7123fb9d09 | ||
|
|
6d8e118192 | ||
|
|
846703b31c | ||
|
|
46905b39a7 | ||
|
|
0f43aca5bc | ||
|
|
587c83cbff | ||
|
|
e8194b1764 | ||
|
|
feb9cb645e | ||
|
|
29f9a4fd67 |
+34
-1
@@ -1,5 +1,38 @@
|
||||
# Changelog
|
||||
|
||||
## v0.14.2
|
||||
|
||||
* Enhancements
|
||||
* [Enum] Improve performance of `Enum.join/2` and `Enum.map_join/3` by using iolists
|
||||
* [Kernel] Ensure compatibility with Erlang 17.1
|
||||
* [Kernel] Support `@external_resource` attribute to external dependencies to a module
|
||||
* [Mix] Allow built Git dependencies to run on a system without Git by passing `--no-deps-check`
|
||||
* [Mix] Add `MIX_ARCHIVES` env variable (it is recommended for Elixir build tools to swap this environment)
|
||||
* [Task] Set `:proc_lib` initial call on task to aid debugging
|
||||
* [Typespec] Delay typespec compilation to after expansion
|
||||
* [URI] Allow `parse/1` now accepts `%URI{}` as argument and return the uri itself
|
||||
|
||||
* Bug fixes
|
||||
* [CLI] Support paths inside archives in `-pa` and `-pz` options
|
||||
* [IEx] Remove dely when printing data from the an application start callback
|
||||
* [IEx] Ensure we show a consistent error when we cannot evaluate `.iex.exs`
|
||||
* [Kernel] Ensure derived protocols are defined with a file
|
||||
* [Kernel] Change precedence of `&` to not special case `/`
|
||||
* [Kernel] Ensure we can only use variables and `\\` as arguments of bodyless clause
|
||||
|
||||
* Soft deprecations (no warnings emitted)
|
||||
* [EEx] Using `EEx.TransformerEngine` and `EEx.AssignsEngine` are deprecated in favor of function composition with `Macro.prewalk/1` (see `EEx.SmartEngine` for an example)
|
||||
* [Mix] `Mix.Generator.from_file/1` is deprecated in favor of passing `from_file: file` option to `embed_text/2` and `embed_template/2` (note though that `from_file/1` expects a path relative to the current file while the `from_file: file` expects one relative to the current working directory)
|
||||
|
||||
* Deprecations
|
||||
* [Kernel] `size/1` is deprecated in favor of `byte_size/1` and `tuple_size/1` (this change was soft deprecated two releases ago)
|
||||
|
||||
* Backwards incompatible changes
|
||||
* [CLI] Remove support for the `--gen-debug` option as its usage is not documented by OTP
|
||||
* [Kernel] Sigils no longer balance start and end tokens, e.g. the sigil `~s(f(o)o)` is no longer valid as it finishes in the first closing `)`
|
||||
* [Kernel] Variables set in `cond` clause heads are no longer available outside of that particular `cond` clause (this is the behaviour also found in `case`, `receive` and friends)
|
||||
* [System] `build_info/0` now returns a map
|
||||
|
||||
## v0.14.1 (2014-06-18)
|
||||
|
||||
* Enhancements
|
||||
@@ -18,7 +51,7 @@
|
||||
* [Path] Fix path expansion of `"/.."`
|
||||
* [Path] Do not match files starting with `.` in `Path.wildcard/2` by default
|
||||
* [Process] `Process.info(pid, :registered_name)` returns `{:registered_name, nil}` if there is no registered name
|
||||
* [String] `Enum.slice/2` and `Enum.slice/3` always returns a list (and never nil)
|
||||
* [String] `String.slice/2` and `String.slice/3` always returns a list (and never nil)
|
||||
* [URI] `encode/1` does not escape reserved/unreserved characters by default nor encodes whitespace as `+` (check `URI.encode_www_form/1` and `URI.decode_www_form/1` for previous behaviour)
|
||||
|
||||
* Deprecations
|
||||
|
||||
+4
-4
@@ -118,14 +118,14 @@ you're all set. Specifically, here's what you need to do to get up and running:
|
||||
[MinGW Installation Manager](http://sourceforge.net/projects/mingw/files/latest/download?source=files).
|
||||
2. Use the MinGW Installation Manager to install the msys-bash, msys-make, and
|
||||
msys-grep packages.
|
||||
3. Add `;C:\Program Files (x86)\Git\bin;C:\Program Files\erl6.0\bin;C:\MinGW\msys\1.0\bin`
|
||||
3. Add `;C:\Program Files (x86)\Git\bin;C:\Program Files\erl6.0\bin;C:\Program Files\erl6.0\erts-6.0\bin;C:\MinGW\msys\1.0\bin`
|
||||
to your "Path" environment variable . (This is under Control Panel > System
|
||||
and Security > System > Advanced system settings > Environment Variables >
|
||||
System variables)
|
||||
|
||||
You can now work in the Command Prompt (but currently **not in PowerShell**)
|
||||
similar to how you would on other OS'es, except for some things (like creating
|
||||
symlinks) you'll need to run the Command Prompt as an Administrator.
|
||||
You can now work in the Command Prompt similar to how you would on other OS'es,
|
||||
except for some things (like creating symlinks) you'll need to run the Command
|
||||
Prompt as an Administrator.
|
||||
|
||||
## Contributing Documentation
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ install: compile
|
||||
$(INSTALL_DATA) $$dir/ebin/* "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/$$dir/ebin"; \
|
||||
done
|
||||
$(Q) $(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(Q) $(INSTALL_PROGRAM) $(filter-out %.bat, $(wildcard bin/*)) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(Q) $(INSTALL_PROGRAM) $(filter-out %.ps1, $(filter-out %.bat, $(wildcard bin/*))) "$(DESTDIR)$(PREFIX)/$(LIBDIR)/elixir/bin"
|
||||
$(Q) $(INSTALL_DIR) "$(DESTDIR)$(PREFIX)/bin"
|
||||
$(Q) for file in "$(DESTDIR)$(PREFIX)"/$(LIBDIR)/elixir/bin/* ; do \
|
||||
ln -sf "../$(LIBDIR)/elixir/bin/$${file##*/}" "$(DESTDIR)$(PREFIX)/bin/" ; \
|
||||
@@ -172,7 +172,7 @@ test_doc_test: compile
|
||||
|
||||
test_stdlib: compile
|
||||
@ echo "==> elixir (exunit)"
|
||||
$(Q) epmd -daemon
|
||||
$(Q) exec epmd & exit
|
||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
|
||||
|
||||
.dialyzer.base_plt:
|
||||
|
||||
+12
-10
@@ -2,25 +2,27 @@
|
||||
|
||||
This document simply outlines the release process:
|
||||
|
||||
1) Remove `-dev` extension from VERSION
|
||||
1. Remove `-dev` extension from VERSION
|
||||
|
||||
2) Ensure CHANGELOG is updated and timestamp
|
||||
2. Ensure CHANGELOG is updated and timestamp
|
||||
|
||||
3) Commit changes above with title "Release vVERSION" and generate new tag
|
||||
3. Commit changes above with title "Release vVERSION" and generate new tag
|
||||
|
||||
4) Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
4. Run `make clean test` to ensure all tests pass from scratch and the CI is green
|
||||
|
||||
5) Push master and tags
|
||||
5. Push master and tags
|
||||
|
||||
6) Release new docs with `make release_docs`, move docs to `docs/stable`
|
||||
6. Release new docs with `make release_docs`, move docs to `docs/stable`
|
||||
|
||||
7) Release new zip with `make release_zip`, push new zip to GitHub Releases
|
||||
7. Release new zip with `make release_zip`, push new zip to GitHub Releases
|
||||
|
||||
8) Merge master into stable branch and push it
|
||||
8. Merge master into stable branch and push it
|
||||
|
||||
9) After release, bump versions, add `-dev` back and commit
|
||||
9. After release, bump versions, add `-dev` back and commit
|
||||
|
||||
10) `make release_docs` once again and push it to `elixir-lang.github.com`
|
||||
10. `make release_docs` once again and push it to `elixir-lang.org`
|
||||
|
||||
11. Also update `release` file in `elixir-lang.org`
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
|
||||
+2
-6
@@ -17,7 +17,6 @@ if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--no-halt Does not halt the Erlang VM after execution
|
||||
--gen-debug Turns on default debugging for all GenServers
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
** Options given after the .exs file or -- are passed down to the executed code
|
||||
@@ -72,9 +71,6 @@ while [ $I -le $# ]; do
|
||||
eval "VAL=\${$I}"
|
||||
ERL="$ERL "$VAL""
|
||||
;;
|
||||
--gen-debug)
|
||||
ERL="$ERL -generic_debug"
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
@@ -84,7 +80,7 @@ done
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
if [ "$MODE" != "iex" ]; then ERL="$ERL -s elixir start_cli"; fi
|
||||
if [ "$MODE" != "iex" ]; then ERL="$ERL -noshell -s elixir start_cli"; fi
|
||||
|
||||
if [ -z "$ERL_PATH" ]; then
|
||||
if [ -f "$SCRIPT_PATH/../releases/RELEASES" ] && [ -f "$SCRIPT_PATH/erl" ]; then
|
||||
@@ -94,4 +90,4 @@ if [ -z "$ERL_PATH" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin -noshell $ELIXIR_ERL_OPTIONS $ERL -extra "$@"
|
||||
exec "$ERL_PATH" -pa "$SCRIPT_PATH"/../lib/*/ebin $ELIXIR_ERL_OPTIONS $ERL -extra "$@"
|
||||
|
||||
+1
-3
@@ -23,7 +23,6 @@ echo --cookie cookie Sets a cookie for this distributed node
|
||||
echo --hidden Makes a hidden node
|
||||
echo --detached Starts the Erlang VM detached from console
|
||||
echo --no-halt Does not halt the Erlang VM after execution
|
||||
echo --gen-debug Turns on default debugging for all GenServers
|
||||
echo.
|
||||
echo ** Options marked with (*) can be given more than once
|
||||
echo ** Options given after the .exs file or -- are passed down to the executed code
|
||||
@@ -68,7 +67,6 @@ IF NOT "%par%"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 &
|
||||
IF NOT "%par%"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
|
||||
IF NOT "%par%"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
|
||||
IF NOT "%par%"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
|
||||
IF NOT "%par%"=="%par:--gen-debug=%" (Set parsErlang=%parsErlang% -generic_debug)
|
||||
rem ******* elixir parameters **********************
|
||||
rem Note: we don't have to do anything with options that don't take an argument
|
||||
IF NOT "%par%"=="%par:-e=%" (shift)
|
||||
@@ -91,7 +89,7 @@ for /d %%d in ("%originPath%..\lib\*.") do (
|
||||
SETLOCAL disabledelayedexpansion
|
||||
:run
|
||||
IF %useWerl% EQU 1 (
|
||||
werl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
werl.exe %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
) ELSE (
|
||||
erl.exe %ext_libs% -noshell %ELIXIR_ERL_OPTIONS% %parsErlang% -s elixir start_cli %beforeExtra% -extra %*
|
||||
)
|
||||
|
||||
@@ -16,7 +16,6 @@ if [ $# -gt 0 ] && ([ "$1" = "--help" ] || [ "$1" = "-h" ]); then
|
||||
--cookie \"cookie\" Sets a cookie for this distributed node
|
||||
--hidden Makes a hidden node
|
||||
--detached Starts the Erlang VM detached from console
|
||||
--gen-debug Turns on default debugging for all GenServers
|
||||
--remsh \"name\" Connects to a node using a remote shell
|
||||
--dot-iex \"path\" Overrides default .iex.exs file and uses path instead;
|
||||
path can be empty, then no file will be loaded
|
||||
|
||||
+4
-1
@@ -82,7 +82,7 @@ defmodule EEx do
|
||||
iex> EEx.eval_string "<%= @foo %>", assigns: [foo: 1]
|
||||
"1"
|
||||
|
||||
In other words, <%= @foo %> is simply translated to:
|
||||
In other words, `<%= @foo %>` is simply translated to:
|
||||
|
||||
<%= Dict.get assigns, :foo %>
|
||||
|
||||
@@ -92,6 +92,7 @@ defmodule EEx do
|
||||
|
||||
@doc """
|
||||
Generates a function definition from the string.
|
||||
|
||||
The kind (`:def` or `:defp`) must be given, the
|
||||
function name, its arguments and the compilation options.
|
||||
|
||||
@@ -120,6 +121,7 @@ defmodule EEx do
|
||||
|
||||
@doc """
|
||||
Generates a function definition from the file contents.
|
||||
|
||||
The kind (`:def` or `:defp`) must be given, the
|
||||
function name, its arguments and the compilation options.
|
||||
|
||||
@@ -147,6 +149,7 @@ defmodule EEx do
|
||||
args = Enum.map args, fn arg -> {arg, [line: 1], nil} end
|
||||
compiled = EEx.compile_file(file, info)
|
||||
|
||||
@external_resource file
|
||||
@file file
|
||||
case kind do
|
||||
:def -> def(unquote(name)(unquote_splicing(args)), do: unquote(compiled))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule EEx.Compiler do
|
||||
@moduledoc false
|
||||
|
||||
# when changing this setting, don't forget to update the docs for EEx
|
||||
# When changing this setting, don't forget to update the docs for EEx
|
||||
@default_engine EEx.SmartEngine
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
defmodule EEx.Engine do
|
||||
@moduledoc ~S"""
|
||||
This is the basic EEx engine that ships with Elixir.
|
||||
Basic EEx engine that ships with Elixir.
|
||||
|
||||
An engine needs to implement three functions:
|
||||
|
||||
* `handle_body(quoted)` - receives the final built quoted
|
||||
@@ -19,6 +20,9 @@ defmodule EEx.Engine do
|
||||
|
||||
Read `handle_expr/3` below for more information about the markers
|
||||
implemented by default by this engine.
|
||||
|
||||
`EEx.Engine` can be used directly if one desires to use the
|
||||
default implementations for the functions above.
|
||||
"""
|
||||
|
||||
use Behaviour
|
||||
@@ -27,6 +31,47 @@ defmodule EEx.Engine do
|
||||
defcallback handle_text(Macro.t, binary) :: Macro.t
|
||||
defcallback handle_expr(Macro.t, binary, Macro.t) :: Macro.t
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@behaviour EEx.Engine
|
||||
|
||||
def handle_body(body) do
|
||||
EEx.Engine.handle_body(body)
|
||||
end
|
||||
|
||||
def handle_text(buffer, text) do
|
||||
EEx.Engine.handle_text(buffer, text)
|
||||
end
|
||||
|
||||
def handle_expr(buffer, mark, expr) do
|
||||
EEx.Engine.handle_expr(buffer, mark, expr)
|
||||
end
|
||||
|
||||
defoverridable [handle_body: 1, handle_expr: 3, handle_text: 2]
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Handles assigns in quoted expressions.
|
||||
|
||||
This can be added to any custom engine by invoking
|
||||
`handle_assign/3` with `Macro.prewalk/1`:
|
||||
|
||||
def handle_expr(buffer, token, expr) do
|
||||
expr = Macro.prewalk(expr, &EEx.Engine.handle_assign/1)
|
||||
EEx.Engine.handle_expr(buffer, token, expr)
|
||||
end
|
||||
|
||||
"""
|
||||
def handle_assign({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
|
||||
quote line: line, do: Dict.get(var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
def handle_assign(arg) do
|
||||
arg
|
||||
end
|
||||
|
||||
@doc """
|
||||
The default implementation implementation simply returns the
|
||||
given expression.
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
defmodule EEx.TransformerEngine do
|
||||
@moduledoc """
|
||||
An abstract engine that is meant to be used and
|
||||
built upon in other modules. This engine implements
|
||||
the `EEx.Engine` behaviour and provides a `transform`
|
||||
overridable directive that allows a developer to
|
||||
customize the expression returned by the engine.
|
||||
|
||||
Check `EEx.AssignsEngine` and `EEx.SmartEngine` for
|
||||
examples of using this module.
|
||||
"""
|
||||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
@@ -49,20 +40,33 @@ defmodule EEx.TransformerEngine do
|
||||
end
|
||||
|
||||
defmodule EEx.AssignsEngine do
|
||||
@moduledoc """
|
||||
An abstract engine that, when used with the
|
||||
`TransformerEngine`, allows a developer to access
|
||||
assigns using `@` as syntax.
|
||||
@moduledoc false
|
||||
|
||||
This engine is included by default on the SmartEngine.
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote unquote: false do
|
||||
defp transform({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
|
||||
quote do: Dict.get(var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
defp transform(arg) do
|
||||
super(arg)
|
||||
end
|
||||
|
||||
defoverridable [transform: 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EEx.SmartEngine do
|
||||
@moduledoc """
|
||||
The default engine used by EEx.
|
||||
|
||||
It includes assigns (like `@foo`) and possibly other
|
||||
conveniences in the future.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule MyEngine do
|
||||
use EEx.TransformerEngine
|
||||
use EEx.AssignsEngine
|
||||
end
|
||||
|
||||
iex> EEx.eval_string("<%= @foo %>", assigns: [foo: 1])
|
||||
"1"
|
||||
|
||||
@@ -88,29 +92,10 @@ defmodule EEx.AssignsEngine do
|
||||
|
||||
"""
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote unquote: false do
|
||||
defp transform({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
|
||||
quote do: Dict.get(var!(assigns), unquote(name))
|
||||
end
|
||||
use EEx.Engine
|
||||
|
||||
defp transform(arg) do
|
||||
super(arg)
|
||||
end
|
||||
|
||||
defoverridable [transform: 1]
|
||||
end
|
||||
def handle_expr(buffer, mark, expr) do
|
||||
expr = Macro.prewalk(expr, &EEx.Engine.handle_assign/1)
|
||||
super(buffer, mark, expr)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule EEx.SmartEngine do
|
||||
use EEx.TransformerEngine
|
||||
use EEx.AssignsEngine
|
||||
|
||||
@moduledoc """
|
||||
An engine meant for end-user usage that includes
|
||||
`EEx.AssignsEngine` and other conveniences. Read
|
||||
`EEx.AssignsEngine` for examples.
|
||||
"""
|
||||
end
|
||||
|
||||
+23
-13
@@ -2,7 +2,7 @@ Code.require_file "test_helper.exs", __DIR__
|
||||
|
||||
require EEx
|
||||
|
||||
defmodule EExText.Compiled do
|
||||
defmodule EExTest.Compiled do
|
||||
def before_compile do
|
||||
fill_in_stacktrace
|
||||
{__ENV__.line, hd(tl(System.stacktrace))}
|
||||
@@ -12,6 +12,10 @@ defmodule EExText.Compiled do
|
||||
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
|
||||
EEx.function_from_file :defp, :private_file_sample, filename, [:bar]
|
||||
|
||||
filename = Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")
|
||||
EEx.function_from_file :def, :public_file_sample, filename, [:bar]
|
||||
|
||||
def file_sample(arg), do: private_file_sample(arg)
|
||||
|
||||
def after_compile do
|
||||
@@ -319,39 +323,45 @@ foo
|
||||
end
|
||||
end
|
||||
|
||||
test "sets external resource attribute" do
|
||||
assert EExTest.Compiled.__info__(:attributes)[:external_resource] ==
|
||||
[Path.join(__DIR__, "fixtures/eex_template_with_bindings.eex")]
|
||||
end
|
||||
|
||||
test "defined from string" do
|
||||
assert EExText.Compiled.string_sample(1, 2) == "3"
|
||||
assert EExTest.Compiled.string_sample(1, 2) == "3"
|
||||
end
|
||||
|
||||
test "defined from file" do
|
||||
assert EExText.Compiled.file_sample(1) == "foo 1\n"
|
||||
assert EExTest.Compiled.file_sample(1) == "foo 1\n"
|
||||
assert EExTest.Compiled.public_file_sample(1) == "foo 1\n"
|
||||
end
|
||||
|
||||
test "defined from file do not affect backtrace" do
|
||||
assert EExText.Compiled.before_compile ==
|
||||
assert EExTest.Compiled.before_compile ==
|
||||
{8,
|
||||
{EExText.Compiled,
|
||||
{EExTest.Compiled,
|
||||
:before_compile,
|
||||
0,
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 7]
|
||||
}
|
||||
}
|
||||
|
||||
assert EExText.Compiled.after_compile ==
|
||||
{19,
|
||||
{EExText.Compiled,
|
||||
assert EExTest.Compiled.after_compile ==
|
||||
{23,
|
||||
{EExTest.Compiled,
|
||||
:after_compile,
|
||||
0,
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 18]
|
||||
[file: to_char_list(Path.relative_to_cwd(__ENV__.file)), line: 22]
|
||||
}
|
||||
}
|
||||
|
||||
assert EExText.Compiled.unknown ==
|
||||
{25,
|
||||
{EExText.Compiled,
|
||||
assert EExTest.Compiled.unknown ==
|
||||
{29,
|
||||
{EExTest.Compiled,
|
||||
:unknown,
|
||||
0,
|
||||
[file: 'unknown', line: 24]
|
||||
[file: 'unknown', line: 28]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@@ -4,7 +4,7 @@ defprotocol Access do
|
||||
empowers the nested update functions in Kernel.
|
||||
|
||||
For instance, `foo[bar]` translates `Access.get(foo, bar)`.
|
||||
`Kernel.get_in/2`, Kernel.put_in/3`, `Kernel.update_in/3` and
|
||||
`Kernel.get_in/2`, `Kernel.put_in/3`, `Kernel.update_in/3` and
|
||||
`Kernel.get_and_update_in/3` are also all powered by the Access
|
||||
protocol.
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ defmodule Agent do
|
||||
Agents are a simple abstraction around state.
|
||||
|
||||
Often in Elixir there is a need to share or store state that
|
||||
must be accessed from different processes or by a same process
|
||||
in different points in time.
|
||||
must be accessed from different processes or by the same process
|
||||
at different points in time.
|
||||
|
||||
The Agent module provides a basic server implementation that
|
||||
allows state to be retrieved and updated via a simple API.
|
||||
@@ -35,12 +35,12 @@ defmodule Agent do
|
||||
end
|
||||
end
|
||||
|
||||
Note that agents still provide a segregation in between the
|
||||
Note that agents still provide a segregation between the
|
||||
client and server APIs, as seen in GenServers. In particular,
|
||||
all code inside the function passed to the agent is executed
|
||||
by the agent. This distinction is important because you may
|
||||
want to avoid expensive operations inside the agent, as it will
|
||||
effectively block the agent until the request is fullfilled.
|
||||
effectively block the agent until the request is fulfilled.
|
||||
|
||||
Consider these two examples:
|
||||
|
||||
@@ -68,13 +68,13 @@ defmodule Agent do
|
||||
## A word on distributed agents
|
||||
|
||||
It is important to consider the limitations of distributed agents. Agents
|
||||
work by sending anonymous functions in between the caller and the agent.
|
||||
work by sending anonymous functions between the caller and the agent.
|
||||
In a distributed setup with multiple nodes, agents only work if the caller
|
||||
(client) and the agent have the same version of a given module.
|
||||
|
||||
This setup may exhibit issues when doing "rolling upgrades". By rolling
|
||||
upgrades we mean the following situation: you wish to deploy a new version of
|
||||
your software by *shutting down* some of your nodes and replacing them by
|
||||
your software by *shutting down* some of your nodes and replacing them with
|
||||
nodes running a new version of the software. In this setup, part of your
|
||||
environment will have one version of a given module and the other part
|
||||
another version (the newer one) of the same module; this may cause agents to
|
||||
@@ -125,7 +125,7 @@ defmodule Agent do
|
||||
documentation.
|
||||
|
||||
If the `:timeout` option is present, the agent is allowed to spend at most
|
||||
the given amount of milliseconds on initialization or it will be terminated
|
||||
the given number of milliseconds on initialization or it will be terminated
|
||||
and the start function will return `{:error, :timeout}`.
|
||||
|
||||
If the `:debug` option is present, the corresponding function in the
|
||||
@@ -137,7 +137,7 @@ defmodule Agent do
|
||||
## Return values
|
||||
|
||||
If the server is successfully created and initialized, the function returns
|
||||
`{:ok, pid}`, where pid is the pid of the server. If there already exists
|
||||
`{:ok, pid}`, where `pid` is the pid of the server. If there already exists
|
||||
an agent with the specified name, the function returns
|
||||
`{:error, {:already_started, pid}}` with the pid of that process.
|
||||
|
||||
@@ -178,7 +178,7 @@ defmodule Agent do
|
||||
|
||||
The function `fun` is sent to the `agent` which invokes the function
|
||||
passing the agent state. The function must return a tuple with two
|
||||
elements, the first being the value to return (i.e. the get value)
|
||||
elements, the first being the value to return (i.e. the `get` value)
|
||||
and the second one is the new state.
|
||||
|
||||
A timeout can also be specified (it has a default value of 5000).
|
||||
|
||||
@@ -13,7 +13,7 @@ defmodule Application do
|
||||
|
||||
In Elixir, Mix is responsible for compiling your source code and
|
||||
generating your application `.app` file. Furthermore, Mix is also
|
||||
responsible for configuring, starting and stoping your application
|
||||
responsible for configuring, starting and stopping your application
|
||||
and its dependencies. For this reason, this documentation will focus
|
||||
on the remaining aspects of your application: the application environment,
|
||||
and the application callback module.
|
||||
@@ -48,7 +48,7 @@ defmodule Application do
|
||||
developers to configure the environment of their dependencies.
|
||||
|
||||
Keep in mind that each application is responsible for its environment.
|
||||
Do not use the fucntions in this module for directly access or modify
|
||||
Do not use the functions in this module for directly access or modify
|
||||
the environment of other application (as it may lead to inconsistent
|
||||
data in the application environment).
|
||||
|
||||
@@ -230,7 +230,7 @@ defmodule Application do
|
||||
terminates abnormally, all other applications and the entire node are
|
||||
also terminated.
|
||||
|
||||
* `:temporary` - if `app` termiantes, it is reported but no other
|
||||
* `:temporary` - if `app` terminates, it is reported but no other
|
||||
applications are terminated (the default).
|
||||
|
||||
Note that it is always possible to stop an application explicitly by calling
|
||||
|
||||
+9
-13
@@ -937,13 +937,13 @@ defmodule Enum do
|
||||
|
||||
def join(collection, joiner) when is_binary(joiner) do
|
||||
reduced = reduce(collection, :first, fn
|
||||
entry, :first -> to_string(entry)
|
||||
entry, acc -> acc <> joiner <> to_string(entry)
|
||||
entry, :first -> enum_to_string(entry)
|
||||
entry, acc -> [acc, joiner|enum_to_string(entry)]
|
||||
end)
|
||||
if reduced == :first do
|
||||
""
|
||||
else
|
||||
reduced
|
||||
IO.iodata_to_binary reduced
|
||||
end
|
||||
end
|
||||
|
||||
@@ -996,14 +996,14 @@ defmodule Enum do
|
||||
|
||||
def map_join(collection, joiner, mapper) when is_binary(joiner) do
|
||||
reduced = reduce(collection, :first, fn
|
||||
entry, :first -> to_string(mapper, entry)
|
||||
entry, acc -> acc <> joiner <> to_string(mapper, entry)
|
||||
entry, :first -> enum_to_string(mapper.(entry))
|
||||
entry, acc -> [acc, joiner|enum_to_string(mapper.(entry))]
|
||||
end)
|
||||
|
||||
if reduced == :first do
|
||||
""
|
||||
else
|
||||
reduced
|
||||
IO.iodata_to_binary reduced
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1839,7 +1839,7 @@ defmodule Enum do
|
||||
|
||||
## Helpers
|
||||
|
||||
@compile {:inline, to_string: 2}
|
||||
@compile {:inline, enum_to_string: 1}
|
||||
|
||||
defp enumerate_and_count(collection, count) when is_list(collection) do
|
||||
{collection, length(collection) - abs(count)}
|
||||
@@ -1849,12 +1849,8 @@ defmodule Enum do
|
||||
map_reduce(collection, -abs(count), fn(x, acc) -> {x, acc + 1} end)
|
||||
end
|
||||
|
||||
defp to_string(mapper, entry) do
|
||||
case mapper.(entry) do
|
||||
x when is_binary(x) -> x
|
||||
o -> String.Chars.to_string(o)
|
||||
end
|
||||
end
|
||||
defp enum_to_string(entry) when is_binary(entry), do: entry
|
||||
defp enum_to_string(entry), do: String.Chars.to_string(entry)
|
||||
|
||||
## Implementations
|
||||
|
||||
|
||||
@@ -578,6 +578,14 @@ defmodule CaseClauseError do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule CondClauseError do
|
||||
defexception []
|
||||
|
||||
def message(_exception) do
|
||||
"no cond clause evaluated to a true value"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule TryClauseError do
|
||||
defexception [term: nil]
|
||||
|
||||
@@ -733,6 +741,10 @@ defmodule ErlangError do
|
||||
%SystemLimitError{}
|
||||
end
|
||||
|
||||
def normalize(:cond_clause, _stacktrace) do
|
||||
%CondClauseError{}
|
||||
end
|
||||
|
||||
def normalize({:badarity, {fun, args}}, _stacktrace) do
|
||||
%BadArityError{function: fun, args: args}
|
||||
end
|
||||
|
||||
+41
-184
@@ -1,157 +1,3 @@
|
||||
require Record
|
||||
|
||||
defmodule File.Stat do
|
||||
@moduledoc """
|
||||
A struct responsible to hold file information.
|
||||
|
||||
In Erlang, this struct is represented by a `:file_info` record.
|
||||
Therefore this module also provides functions for converting
|
||||
in between the Erlang record and the Elixir struct.
|
||||
|
||||
Its fields are:
|
||||
|
||||
* `size` - size of file in bytes.
|
||||
|
||||
* `type` - `:device | :directory | :regular | :other`; the type of the
|
||||
file.
|
||||
|
||||
* `access` - `:read | :write | :read_write | :none`; the current system
|
||||
access to the file.
|
||||
|
||||
* `atime` - the last time the file was read.
|
||||
|
||||
* `mtime` - the last time the file was written.
|
||||
|
||||
* `ctime` - the interpretation of this time field depends on the operating
|
||||
system. On Unix, it is the last time the file or the inode was changed.
|
||||
In Windows, it is the time of creation.
|
||||
|
||||
* `mode` - the file permissions.
|
||||
|
||||
* `links` - the number of links to this file. This is always 1 for file
|
||||
systems which have no concept of links.
|
||||
|
||||
* `major_device` - identifies the file system where the file is located.
|
||||
In windows, the number indicates a drive as follows: 0 means A:, 1 means
|
||||
B:, and so on.
|
||||
|
||||
* `minor_device` - only valid for character devices on Unix. In all other
|
||||
cases, this field is zero.
|
||||
|
||||
* `inode` - gives the inode number. On non-Unix file systems, this field
|
||||
will be zero.
|
||||
|
||||
* `uid` - indicates the owner of the file.
|
||||
|
||||
* `gid` - gives the group that the owner of the file belongs to. Will be
|
||||
zero for non-Unix file systems.
|
||||
|
||||
The time type returned in `atime`, `mtime`, and `ctime` is dependent on the
|
||||
time type set in options. `{:time, type}` where type can be `:local`,
|
||||
`:universal`, or `:posix`. Default is `:local`.
|
||||
"""
|
||||
|
||||
record = Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
keys = :lists.map(&elem(&1, 0), record)
|
||||
vals = :lists.map(&{&1, [], nil}, keys)
|
||||
pairs = :lists.zip(keys, vals)
|
||||
|
||||
defstruct keys
|
||||
|
||||
@doc """
|
||||
Converts a `File.Stat` struct to a `:file_info` record.
|
||||
"""
|
||||
def to_record(%File.Stat{unquote_splicing(pairs)}) do
|
||||
{:file_info, unquote_splicing(vals)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a `:file_info` record into a `File.Stat`.
|
||||
"""
|
||||
def from_record({:file_info, unquote_splicing(vals)}) do
|
||||
%File.Stat{unquote_splicing(pairs)}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule File.Stream do
|
||||
@moduledoc """
|
||||
Defines a `File.Stream` struct returned by `File.stream!/2`.
|
||||
|
||||
The following fields are public:
|
||||
|
||||
* `path` - the file path
|
||||
* `modes` - the file modes
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
|
||||
"""
|
||||
|
||||
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
|
||||
|
||||
defimpl Collectable do
|
||||
def empty(stream) do
|
||||
stream
|
||||
end
|
||||
|
||||
def into(%{path: path, modes: modes, raw: raw} = stream) do
|
||||
modes = for mode <- modes, not mode in [:read], do: mode
|
||||
|
||||
case :file.open(path, [:write|modes]) do
|
||||
{:ok, device} ->
|
||||
{:ok, into(device, stream, raw)}
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
defp into(device, stream, raw) do
|
||||
fn
|
||||
:ok, {:cont, x} ->
|
||||
case raw do
|
||||
true -> IO.binwrite(device, x)
|
||||
false -> IO.write(device, x)
|
||||
end
|
||||
:ok, :done ->
|
||||
:file.close(device)
|
||||
stream
|
||||
:ok, :halt ->
|
||||
:file.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable do
|
||||
def reduce(%{path: path, modes: modes, line_or_bytes: line_or_bytes, raw: raw}, acc, fun) do
|
||||
modes = for mode <- modes, not mode in [:write, :append], do: mode
|
||||
|
||||
start_fun =
|
||||
fn ->
|
||||
case :file.open(path, modes) do
|
||||
{:ok, device} -> device
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
next_fun =
|
||||
case raw do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
|
||||
Stream.resource(start_fun, next_fun, &:file.close/1).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _term) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule File do
|
||||
@moduledoc ~S"""
|
||||
This module contains functions to manipulate files.
|
||||
@@ -224,8 +70,7 @@ defmodule File do
|
||||
about such options and other performance considerations.
|
||||
"""
|
||||
|
||||
alias :file, as: F
|
||||
alias :filelib, as: FL
|
||||
alias :file, as: F
|
||||
|
||||
@type posix :: :file.posix()
|
||||
@type io_device :: :file.io_device()
|
||||
@@ -241,7 +86,7 @@ defmodule File do
|
||||
"""
|
||||
@spec regular?(Path.t) :: boolean
|
||||
def regular?(path) do
|
||||
FL.is_regular(IO.chardata_to_string(path))
|
||||
:elixir_utils.read_file_type(IO.chardata_to_string(path)) == {:ok, :regular}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -249,7 +94,7 @@ defmodule File do
|
||||
"""
|
||||
@spec dir?(Path.t) :: boolean
|
||||
def dir?(path) do
|
||||
FL.is_dir(IO.chardata_to_string(path))
|
||||
:elixir_utils.read_file_type(IO.chardata_to_string(path)) == {:ok, :directory}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -319,7 +164,31 @@ defmodule File do
|
||||
"""
|
||||
@spec mkdir_p(Path.t) :: :ok | {:error, posix}
|
||||
def mkdir_p(path) do
|
||||
FL.ensure_dir(Path.join(path, "."))
|
||||
do_mkdir_p(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
defp do_mkdir_p("/") do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp do_mkdir_p(path) do
|
||||
if dir?(path) do
|
||||
:ok
|
||||
else
|
||||
parent = Path.dirname(path)
|
||||
if parent == path do
|
||||
# Protect against infinite loop
|
||||
{:error, :einval}
|
||||
else
|
||||
_ = do_mkdir_p(parent)
|
||||
case :file.make_dir(path) do
|
||||
{:error, :eexist} = error ->
|
||||
if dir?(path), do: :ok, else: error
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -636,7 +505,7 @@ defmodule File do
|
||||
# src may be a file or a directory, dest is definitely
|
||||
# a directory. Returns nil unless an error is found.
|
||||
defp do_cp_r(src, dest, callback, acc) when is_list(acc) do
|
||||
case :elixir_utils.file_type(src) do
|
||||
case :elixir_utils.read_link_type(src) do
|
||||
{:ok, :regular} ->
|
||||
do_cp_file(src, dest, callback, acc)
|
||||
{:ok, :symlink} ->
|
||||
@@ -735,12 +604,11 @@ defmodule File do
|
||||
the parent directories
|
||||
* `:eisdir` - the named file is a directory
|
||||
|
||||
The writing is automatically done in `:raw` mode. Check
|
||||
`File.open/2` for other available options.
|
||||
Check `File.open/2` for other available options.
|
||||
"""
|
||||
@spec write(Path.t, iodata, list) :: :ok | {:error, posix}
|
||||
def write(path, content, modes \\ []) do
|
||||
F.write_file(IO.chardata_to_string(path), content, [:raw|modes])
|
||||
F.write_file(IO.chardata_to_string(path), content, modes)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -923,9 +791,9 @@ defmodule File do
|
||||
end
|
||||
|
||||
defp safe_list_dir(path) do
|
||||
case :elixir_utils.file_type(path) do
|
||||
case :elixir_utils.read_link_type(path) do
|
||||
{:ok, :symlink} ->
|
||||
case :elixir_utils.file_type(path, :read_file_info) do
|
||||
case :elixir_utils.read_file_type(path) do
|
||||
{:ok, :directory} -> {:ok, :directory}
|
||||
_ -> {:ok, :regular}
|
||||
end
|
||||
@@ -1038,14 +906,15 @@ defmodule File do
|
||||
|
||||
The file is opened, given to the function as argument and
|
||||
automatically closed after the function returns, regardless
|
||||
if there was an error or not.
|
||||
if there was an error when executing the function.
|
||||
|
||||
It returns `{:ok, function_result}` in case of success,
|
||||
`{:error, reason}` otherwise.
|
||||
|
||||
Do not use this function with `:delayed_write` option
|
||||
since automatically closing the file may fail
|
||||
(as writes are delayed).
|
||||
This function expects the file to be closed with success,
|
||||
which is usually the case unless the `:delayed_write` option
|
||||
is given. For this reason, we do not recommend passing
|
||||
`:delayed_write` to this function.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1069,6 +938,7 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Same as `open/2` but raises an error if file could not be opened.
|
||||
|
||||
Returns the `io_device` otherwise.
|
||||
"""
|
||||
@spec open!(Path.t, list) :: io_device | no_return
|
||||
@@ -1083,6 +953,7 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Same as `open/3` but raises an error if file could not be opened.
|
||||
|
||||
Returns the function result otherwise.
|
||||
"""
|
||||
@spec open!(Path.t, list, (io_device -> res)) :: res | no_return when res: var
|
||||
@@ -1233,21 +1104,7 @@ defmodule File do
|
||||
"""
|
||||
def stream!(path, modes \\ [], line_or_bytes \\ :line) do
|
||||
modes = open_defaults(modes, true)
|
||||
raw = :lists.keyfind(:encoding, 1, modes) == false
|
||||
|
||||
modes =
|
||||
if raw do
|
||||
if :lists.keyfind(:read_ahead, 1, modes) do
|
||||
[:raw|modes]
|
||||
else
|
||||
[:raw, :read_ahead|modes]
|
||||
end
|
||||
else
|
||||
modes
|
||||
end
|
||||
|
||||
%File.Stream{path: IO.chardata_to_string(path), modes: modes,
|
||||
raw: raw, line_or_bytes: line_or_bytes}
|
||||
File.Stream.__build__(IO.chardata_to_string(path), modes, line_or_bytes)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
require Record
|
||||
|
||||
defmodule File.Stat do
|
||||
@moduledoc """
|
||||
A struct responsible to hold file information.
|
||||
|
||||
In Erlang, this struct is represented by a `:file_info` record.
|
||||
Therefore this module also provides functions for converting
|
||||
in between the Erlang record and the Elixir struct.
|
||||
|
||||
Its fields are:
|
||||
|
||||
* `size` - size of file in bytes.
|
||||
|
||||
* `type` - `:device | :directory | :regular | :other`; the type of the
|
||||
file.
|
||||
|
||||
* `access` - `:read | :write | :read_write | :none`; the current system
|
||||
access to the file.
|
||||
|
||||
* `atime` - the last time the file was read.
|
||||
|
||||
* `mtime` - the last time the file was written.
|
||||
|
||||
* `ctime` - the interpretation of this time field depends on the operating
|
||||
system. On Unix, it is the last time the file or the inode was changed.
|
||||
In Windows, it is the time of creation.
|
||||
|
||||
* `mode` - the file permissions.
|
||||
|
||||
* `links` - the number of links to this file. This is always 1 for file
|
||||
systems which have no concept of links.
|
||||
|
||||
* `major_device` - identifies the file system where the file is located.
|
||||
In windows, the number indicates a drive as follows: 0 means A:, 1 means
|
||||
B:, and so on.
|
||||
|
||||
* `minor_device` - only valid for character devices on Unix. In all other
|
||||
cases, this field is zero.
|
||||
|
||||
* `inode` - gives the inode number. On non-Unix file systems, this field
|
||||
will be zero.
|
||||
|
||||
* `uid` - indicates the owner of the file.
|
||||
|
||||
* `gid` - gives the group that the owner of the file belongs to. Will be
|
||||
zero for non-Unix file systems.
|
||||
|
||||
The time type returned in `atime`, `mtime`, and `ctime` is dependent on the
|
||||
time type set in options. `{:time, type}` where type can be `:local`,
|
||||
`:universal`, or `:posix`. Default is `:local`.
|
||||
"""
|
||||
|
||||
record = Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
|
||||
keys = :lists.map(&elem(&1, 0), record)
|
||||
vals = :lists.map(&{&1, [], nil}, keys)
|
||||
pairs = :lists.zip(keys, vals)
|
||||
|
||||
defstruct keys
|
||||
|
||||
@doc """
|
||||
Converts a `File.Stat` struct to a `:file_info` record.
|
||||
"""
|
||||
def to_record(%File.Stat{unquote_splicing(pairs)}) do
|
||||
{:file_info, unquote_splicing(vals)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts a `:file_info` record into a `File.Stat`.
|
||||
"""
|
||||
def from_record({:file_info, unquote_splicing(vals)}) do
|
||||
%File.Stat{unquote_splicing(pairs)}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,96 @@
|
||||
defmodule File.Stream do
|
||||
@moduledoc """
|
||||
Defines a `File.Stream` struct returned by `File.stream!/3`.
|
||||
|
||||
The following fields are public:
|
||||
|
||||
* `path` - the file path
|
||||
* `modes` - the file modes
|
||||
* `raw` - a boolean indicating if bin functions should be used
|
||||
* `line_or_bytes` - if reading should read lines or a given amount of bytes
|
||||
|
||||
"""
|
||||
|
||||
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
|
||||
|
||||
@doc false
|
||||
def __build__(path, modes, line_or_bytes) do
|
||||
raw = :lists.keyfind(:encoding, 1, modes) == false
|
||||
|
||||
modes =
|
||||
if raw do
|
||||
if :lists.keyfind(:read_ahead, 1, modes) == {:read_ahead, false} do
|
||||
[:raw|modes]
|
||||
else
|
||||
[:raw, :read_ahead|modes]
|
||||
end
|
||||
else
|
||||
modes
|
||||
end
|
||||
|
||||
%File.Stream{path: path, modes: modes, raw: raw, line_or_bytes: line_or_bytes}
|
||||
end
|
||||
|
||||
defimpl Collectable do
|
||||
def empty(stream) do
|
||||
stream
|
||||
end
|
||||
|
||||
def into(%{path: path, modes: modes, raw: raw} = stream) do
|
||||
modes = for mode <- modes, not mode in [:read], do: mode
|
||||
|
||||
case :file.open(path, [:write|modes]) do
|
||||
{:ok, device} ->
|
||||
{:ok, into(device, stream, raw)}
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
defp into(device, stream, raw) do
|
||||
fn
|
||||
:ok, {:cont, x} ->
|
||||
case raw do
|
||||
true -> IO.binwrite(device, x)
|
||||
false -> IO.write(device, x)
|
||||
end
|
||||
:ok, :done ->
|
||||
:file.close(device)
|
||||
stream
|
||||
:ok, :halt ->
|
||||
:file.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable do
|
||||
def reduce(%{path: path, modes: modes, line_or_bytes: line_or_bytes, raw: raw}, acc, fun) do
|
||||
modes = for mode <- modes, not mode in [:write, :append], do: mode
|
||||
|
||||
start_fun =
|
||||
fn ->
|
||||
case :file.open(path, modes) do
|
||||
{:ok, device} -> device
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
next_fun =
|
||||
case raw do
|
||||
true -> &IO.each_binstream(&1, line_or_bytes)
|
||||
false -> &IO.each_stream(&1, line_or_bytes)
|
||||
end
|
||||
|
||||
Stream.resource(start_fun, next_fun, &:file.close/1).(acc, fun)
|
||||
end
|
||||
|
||||
def count(_stream) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
|
||||
def member?(_stream, _term) do
|
||||
{:error, __MODULE__}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -248,8 +248,7 @@ defmodule IO do
|
||||
"""
|
||||
@spec stream(device, :line | pos_integer) :: Enumerable.t
|
||||
def stream(device, line_or_codepoints) do
|
||||
struct IO.Stream,
|
||||
device: map_dev(device), raw: false, line_or_bytes: line_or_codepoints
|
||||
IO.Stream.__build__(map_dev(device), false, line_or_codepoints)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -267,8 +266,7 @@ defmodule IO do
|
||||
"""
|
||||
@spec binstream(device, :line | pos_integer) :: Enumerable.t
|
||||
def binstream(device, line_or_bytes) do
|
||||
struct IO.Stream,
|
||||
device: map_dev(device), raw: true, line_or_bytes: line_or_bytes
|
||||
IO.Stream.__build__(map_dev(device), true, line_or_bytes)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -155,7 +155,7 @@ defmodule IO.ANSI do
|
||||
|
||||
"""
|
||||
@spec escape(String.t, emit :: boolean) :: String.t
|
||||
def escape(string, emit \\ terminal?) do
|
||||
def escape(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) do
|
||||
{rendered, emitted} = do_escape(string, emit, false, nil, [])
|
||||
if emitted do
|
||||
rendered <> reset
|
||||
@@ -185,7 +185,7 @@ defmodule IO.ANSI do
|
||||
|
||||
"""
|
||||
@spec escape_fragment(String.t, emit :: boolean) :: String.t
|
||||
def escape_fragment(string, emit \\ terminal?) do
|
||||
def escape_fragment(string, emit \\ terminal?) when is_binary(string) and is_boolean(emit) do
|
||||
{escaped, _emitted} = do_escape(string, emit, false, nil, [])
|
||||
escaped
|
||||
end
|
||||
|
||||
@@ -22,6 +22,11 @@ defmodule IO.Stream do
|
||||
|
||||
defstruct device: nil, raw: true, line_or_bytes: :line
|
||||
|
||||
@doc false
|
||||
def __build__(device, raw, line_or_bytes) do
|
||||
%IO.Stream{device: device, raw: raw, line_or_bytes: line_or_bytes}
|
||||
end
|
||||
|
||||
defimpl Collectable do
|
||||
def empty(stream) do
|
||||
stream
|
||||
|
||||
+20
-232
@@ -88,145 +88,6 @@ defmodule Kernel do
|
||||
:erlang.apply(module, fun, args)
|
||||
end
|
||||
|
||||
## DEPRECATED
|
||||
|
||||
@doc false
|
||||
def atom_to_binary(some_atom) do
|
||||
:erlang.atom_to_binary(some_atom, :utf8)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def atom_to_list(atom) do
|
||||
:erlang.atom_to_list(atom)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def binary_to_atom(some_binary) do
|
||||
:erlang.binary_to_atom(some_binary, :utf8)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def binary_to_existing_atom(some_binary) do
|
||||
:erlang.binary_to_existing_atom(some_binary, :utf8)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def binary_to_integer(some_binary) do
|
||||
:erlang.binary_to_integer(some_binary)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def binary_to_integer(some_binary, base) do
|
||||
:erlang.binary_to_integer(some_binary, base)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def binary_to_float(some_binary) do
|
||||
:erlang.binary_to_float(some_binary)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def bitstring_to_list(bitstring) do
|
||||
:erlang.bitstring_to_list(bitstring)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def float_to_list(number) do
|
||||
:erlang.float_to_list(number)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def float_to_binary(some_float) do
|
||||
:erlang.float_to_binary(some_float)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def float_to_binary(float, options) do
|
||||
Float.to_string(float, options)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def float_to_list(float, options) do
|
||||
Float.to_char_list(float, options)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def iodata_length(item) do
|
||||
:erlang.iolist_size(item)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def iodata_to_binary(item) do
|
||||
:erlang.iolist_to_binary(item)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def integer_to_binary(some_integer) do
|
||||
:erlang.integer_to_binary(some_integer)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def integer_to_binary(some_integer, base) do
|
||||
:erlang.integer_to_binary(some_integer, base)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def integer_to_list(number) do
|
||||
:erlang.integer_to_list(number)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def integer_to_list(number, base) do
|
||||
:erlang.integer_to_list(number, base)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def list_to_atom(list) do
|
||||
:erlang.list_to_atom(list)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def list_to_bitstring(bitstring_list) do
|
||||
:erlang.list_to_bitstring(bitstring_list)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def list_to_existing_atom(list) do
|
||||
:erlang.list_to_existing_atom(list)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def list_to_float(list) do
|
||||
:erlang.list_to_float(list)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def list_to_integer(list) do
|
||||
:erlang.list_to_integer(list)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def list_to_integer(list, base) do
|
||||
:erlang.list_to_integer(list, base)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def list_to_tuple(list) do
|
||||
:erlang.list_to_tuple(list)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def tuple_to_list(tuple) do
|
||||
:erlang.tuple_to_list(tuple)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def size(arg) do
|
||||
:erlang.size(arg)
|
||||
end
|
||||
|
||||
## END OF DEPRECATED
|
||||
|
||||
@doc """
|
||||
Extracts the part of the binary starting at `start` with length `length`.
|
||||
Binaries are zero-indexed.
|
||||
@@ -665,6 +526,11 @@ defmodule Kernel do
|
||||
:erlang.self()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def size(arg) do
|
||||
:erlang.size(arg)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Spawns the given function and returns its pid.
|
||||
|
||||
@@ -1264,21 +1130,21 @@ defmodule Kernel do
|
||||
defmacro !(arg)
|
||||
|
||||
defmacro !({:!, _, [arg]}) do
|
||||
quote do
|
||||
optimize_boolean(quote do
|
||||
case unquote(arg) do
|
||||
unquote(cond_var) when unquote(cond_var) in [false, nil] -> false
|
||||
x when x in [false, nil] -> false
|
||||
_ -> true
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defmacro !(arg) do
|
||||
quote do
|
||||
optimize_boolean(quote do
|
||||
case unquote(arg) do
|
||||
unquote(cond_var) when unquote(cond_var) in [false, nil] -> true
|
||||
x when x in [false, nil] -> true
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -2234,94 +2100,12 @@ defmodule Kernel do
|
||||
do_clause = Keyword.get(clauses, :do, nil)
|
||||
else_clause = Keyword.get(clauses, :else, nil)
|
||||
|
||||
quote do
|
||||
optimize_boolean(quote do
|
||||
case unquote(condition) do
|
||||
unquote(cond_var) when unquote(cond_var) in [false, nil] -> unquote(else_clause)
|
||||
x when x in [false, nil] -> unquote(else_clause)
|
||||
_ -> unquote(do_clause)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evaluates the expression corresponding to the first clause that
|
||||
evaluates to true. Raises an error if all conditions evaluate to
|
||||
to nil or false.
|
||||
|
||||
## Examples
|
||||
|
||||
cond do
|
||||
1 + 1 == 1 ->
|
||||
"This will never match"
|
||||
2 * 2 != 4 ->
|
||||
"Nor this"
|
||||
true ->
|
||||
"This will"
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro cond([do: pairs]) do
|
||||
[{:->, meta, [[condition], clause]}|t] = :lists.reverse pairs
|
||||
|
||||
new_acc =
|
||||
case condition do
|
||||
{:_, _, atom} when is_atom(atom) ->
|
||||
raise ArgumentError, <<"unbound variable _ inside cond. ",
|
||||
"If you want the last clause to match, you probably meant to use true ->">>
|
||||
x when is_atom(x) and x != false and x != nil ->
|
||||
clause
|
||||
_ ->
|
||||
head = quote(do: unquote(cond_var) when unquote(cond_var) != false and unquote(cond_var) != nil)
|
||||
|
||||
quote line: get_line(meta) do
|
||||
case unquote(condition) do
|
||||
unquote(head) -> unquote(clause)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
build_cond_clauses(t, new_acc, meta)
|
||||
end
|
||||
|
||||
# Builds cond clauses by nesting them recursively.
|
||||
#
|
||||
# case !foo do
|
||||
# false -> 1
|
||||
# true ->
|
||||
# case !bar do
|
||||
# false -> 2
|
||||
# true -> 3
|
||||
# end
|
||||
# end
|
||||
#
|
||||
defp build_cond_clauses([{:->, new, [[condition], clause]}|t], acc, old) do
|
||||
clauses = [falsy_clause(old, acc), truthy_clause(new, clause)]
|
||||
acc = quote do: (case unquote(condition), do: unquote(clauses))
|
||||
build_cond_clauses(t, acc, new)
|
||||
end
|
||||
|
||||
defp build_cond_clauses([], acc, _), do: acc
|
||||
|
||||
defp falsy_clause(meta, acc) do
|
||||
head = quote(do: unquote(cond_var) when unquote(cond_var) == false or unquote(cond_var) == nil)
|
||||
{:->, meta, [[head], acc]}
|
||||
end
|
||||
|
||||
defp truthy_clause(meta, clause) do
|
||||
{:->, meta, [[quote(do: _)], clause]}
|
||||
end
|
||||
|
||||
# Setting cond: true in metadata turns on a small optimization
|
||||
# in Elixir compiler. In the long run, we want to bring this
|
||||
# optimization to Elixir land, but not right now.
|
||||
defp cond_var do
|
||||
{:x, [cond: true], Kernel}
|
||||
end
|
||||
|
||||
defp get_line(meta) do
|
||||
case :lists.keyfind(:line, 1, meta) do
|
||||
{:line, line} -> line
|
||||
false -> 0
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -2684,7 +2468,7 @@ defmodule Kernel do
|
||||
@doc """
|
||||
When used inside quoting, marks that the variable should
|
||||
not be hygienized. The argument can be either a variable
|
||||
unquoted or an atom representing the variable name.
|
||||
unquoted or in standard tuple form `{name, meta, context}`.
|
||||
|
||||
Check `Kernel.SpecialForms.quote/2` for more information.
|
||||
"""
|
||||
@@ -3027,7 +2811,7 @@ defmodule Kernel do
|
||||
|
||||
## Deriving
|
||||
|
||||
Alhought structs are maps, by default structs do not implement
|
||||
Although structs are maps, by default structs do not implement
|
||||
any of the protocols implemented for maps. For example, if you
|
||||
attempt to use the access protocol with the User struct, it
|
||||
will lead to an error:
|
||||
@@ -3734,6 +3518,10 @@ defmodule Kernel do
|
||||
|
||||
## Shared functions
|
||||
|
||||
defp optimize_boolean({:case, meta, args}) do
|
||||
{:case, [{:optimize_boolean, true}|meta], args}
|
||||
end
|
||||
|
||||
# We need this check only for bootstrap purposes.
|
||||
# Once Kernel is loaded and we recompile, it is a no-op.
|
||||
case :code.ensure_loaded(Kernel) do
|
||||
|
||||
@@ -129,12 +129,12 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp parse_shared(["-pa", h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), &Code.prepend_path(&1)
|
||||
add_code_path(h, &Code.prepend_path/1)
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
defp parse_shared(["-pz", h|t], config) do
|
||||
Enum.each Path.wildcard(Path.expand(h)), &Code.append_path(&1)
|
||||
add_code_path(h, &Code.append_path/1)
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
@@ -162,7 +162,7 @@ defmodule Kernel.CLI do
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden", "--gen-debug"] do
|
||||
defp parse_shared([erl|t], config) when erl in ["--detached", "--hidden"] do
|
||||
parse_shared t, config
|
||||
end
|
||||
|
||||
@@ -170,6 +170,15 @@ defmodule Kernel.CLI do
|
||||
{list, config}
|
||||
end
|
||||
|
||||
|
||||
defp add_code_path(path, fun) do
|
||||
path = Path.expand(path)
|
||||
case Path.wildcard(path) do
|
||||
[] -> fun.(path)
|
||||
list -> Enum.each(list, fun)
|
||||
end
|
||||
end
|
||||
|
||||
# Process init options
|
||||
|
||||
defp parse_argv(["--"|t], config) do
|
||||
@@ -240,7 +249,7 @@ defmodule Kernel.CLI do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, &parse_compiler(&1, &2)
|
||||
_ ->
|
||||
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
|
||||
pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
|
||||
parse_compiler t, %{config | compile: [pattern | config.compile]}
|
||||
end
|
||||
end
|
||||
@@ -315,7 +324,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:file, file}, _config) when is_binary(file) do
|
||||
if :filelib.is_regular(file) do
|
||||
if File.regular?(file) do
|
||||
wrapper fn -> Code.require_file(file) end
|
||||
else
|
||||
{:error, "No file named #{file}"}
|
||||
@@ -360,7 +369,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp filter_patterns(pattern) do
|
||||
Enum.filter(Enum.uniq(Path.wildcard(pattern)), &:filelib.is_regular(&1))
|
||||
Enum.filter(Enum.uniq(Path.wildcard(pattern)), &File.regular?(&1))
|
||||
end
|
||||
|
||||
defp filter_multiple_patterns(patterns) do
|
||||
|
||||
@@ -119,24 +119,24 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
|
||||
In practice though, structs are usually defined with the
|
||||
`Kernel.defstruct/2` macro:
|
||||
`Kernel.defstruct/1` macro:
|
||||
|
||||
defmodule User do
|
||||
defstruct name: "josé", age: 27
|
||||
end
|
||||
|
||||
Now a struct can be created as follow:
|
||||
Now a struct can be created as follows:
|
||||
|
||||
%User{}
|
||||
|
||||
Underneath a struct is just a map with a `__struct__` field
|
||||
pointing to the User module:
|
||||
pointing to the `User` module:
|
||||
|
||||
%User{} == %{__struct__: User, name: "josé", age: 27}
|
||||
|
||||
A struct also validates the given keys are part of the defined
|
||||
A struct also validates that the given keys are part of the defined
|
||||
struct. The example below will fail because there is no key
|
||||
`:full_name` in the user struct:
|
||||
`:full_name` in the `User` struct:
|
||||
|
||||
%User{full_name: "José Valim"}
|
||||
|
||||
@@ -155,7 +155,7 @@ defmodule Kernel.SpecialForms do
|
||||
compilation time and it will guarantee at runtime the given
|
||||
argument is a struct, failing with `BadStructError` otherwise.
|
||||
|
||||
Alhought structs are maps, by default structs do not implement
|
||||
Although structs are maps, by default structs do not implement
|
||||
any of the protocols implemented for maps. Check
|
||||
`Kernel.defprotocol/2` for more information on how structs
|
||||
can be used with protocols for polymorphic dispatch. Also
|
||||
@@ -1397,7 +1397,27 @@ defmodule Kernel.SpecialForms do
|
||||
case, clauses that do not explicitly bind a value have the variable
|
||||
bound to nil.
|
||||
"""
|
||||
defmacro case(condition, blocks)
|
||||
defmacro case(condition, clauses)
|
||||
|
||||
@doc """
|
||||
Evaluates the expression corresponding to the first clause that
|
||||
evaluates to truth value.
|
||||
|
||||
Raises an error if all conditions evaluate to to nil or false.
|
||||
|
||||
## Examples
|
||||
|
||||
cond do
|
||||
1 + 1 == 1 ->
|
||||
"This will never match"
|
||||
2 * 2 != 4 ->
|
||||
"Nor this"
|
||||
true ->
|
||||
"This will"
|
||||
end
|
||||
|
||||
"""
|
||||
defmacro cond(clauses)
|
||||
|
||||
@doc ~S"""
|
||||
Evaluate the given expressions and handle any error, exit
|
||||
|
||||
@@ -262,60 +262,18 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@doc """
|
||||
Defines a `type`, `typep` or `opaque` by receiving Erlang's typespec.
|
||||
Defines a `type`, `typep` or `opaque` by receiving a typespec expression.
|
||||
"""
|
||||
def define_type(caller, kind, {name, _, vars} = type) when kind in [:type, :typep, :opaque] do
|
||||
{kind, export} =
|
||||
case kind do
|
||||
:type -> {:type, true}
|
||||
:typep -> {:type, false}
|
||||
:opaque -> {:opaque, true}
|
||||
end
|
||||
|
||||
module = caller.module
|
||||
arity = length(vars)
|
||||
|
||||
Module.compile_typespec module, kind, type
|
||||
|
||||
if export do
|
||||
Module.compile_typespec(module, :export_type, [{name, arity}])
|
||||
end
|
||||
|
||||
define_doc(caller, kind, name, arity, export)
|
||||
type
|
||||
end
|
||||
|
||||
defp define_doc(caller, kind, name, arity, export) do
|
||||
module = caller.module
|
||||
doc = Module.get_attribute(module, :typedoc)
|
||||
|
||||
if doc do
|
||||
if export do
|
||||
Module.add_doc(module, caller.line, kind, {name, arity}, doc)
|
||||
else
|
||||
:elixir_errors.warn caller.line, caller.file, "type #{name}/#{arity} is private, " <>
|
||||
"@typedoc's are always discarded for private types\n"
|
||||
end
|
||||
end
|
||||
|
||||
Module.delete_attribute(module, :typedoc)
|
||||
def define_type(kind, expr, doc \\ nil, env) do
|
||||
Module.store_typespec(env.module, kind, {kind, expr, doc, env})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a `spec` by receiving Erlang's typespec.
|
||||
Defines a `spec` by receiving a typespec expression.
|
||||
"""
|
||||
def define_spec(module, tuple, definition) do
|
||||
Module.compile_typespec module, :spec, {tuple, definition}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a `callback` by receiving Erlang's typespec.
|
||||
"""
|
||||
def define_callback(module, tuple, definition) do
|
||||
Module.compile_typespec module, :callback, {tuple, definition}
|
||||
def define_spec(kind, expr, env) do
|
||||
Module.store_typespec(env.module, kind, {kind, expr, env})
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -324,9 +282,12 @@ defmodule Kernel.Typespec do
|
||||
for modules being compiled.
|
||||
"""
|
||||
def defines_type?(module, name, arity) do
|
||||
finder = &match?({^name, _, vars} when length(vars) == arity, &1)
|
||||
finder = fn {_kind, expr, _doc, _caller} ->
|
||||
type_to_signature(expr) == {name, arity}
|
||||
end
|
||||
|
||||
:lists.any(finder, Module.get_attribute(module, :type)) or
|
||||
:lists.any(finder, Module.get_attribute(module, :opaque))
|
||||
:lists.any(finder, Module.get_attribute(module, :opaque))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -334,8 +295,10 @@ defmodule Kernel.Typespec do
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_spec?(module, name, arity) do
|
||||
tuple = {name, arity}
|
||||
:lists.any(&match?(^tuple, &1), Module.get_attribute(module, :spec))
|
||||
finder = fn {_kind, expr, _caller} ->
|
||||
spec_to_signature(expr) == {name, arity}
|
||||
end
|
||||
:lists.any(finder, Module.get_attribute(module, :spec))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -343,8 +306,10 @@ defmodule Kernel.Typespec do
|
||||
This function is only available for modules being compiled.
|
||||
"""
|
||||
def defines_callback?(module, name, arity) do
|
||||
tuple = {name, arity}
|
||||
:lists.any(&match?(^tuple, &1), Module.get_attribute(module, :callback))
|
||||
finder = fn {_kind, expr, _caller} ->
|
||||
spec_to_signature(expr) == {name, arity}
|
||||
end
|
||||
:lists.any(finder, Module.get_attribute(module, :callback))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -514,10 +479,40 @@ defmodule Kernel.Typespec do
|
||||
binary
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@doc false
|
||||
def spec_to_signature({:when, _, [spec, _]}),
|
||||
do: type_to_signature(spec)
|
||||
def spec_to_signature(other),
|
||||
do: type_to_signature(other)
|
||||
|
||||
@doc false
|
||||
def type_to_signature({:::, _, [{name, _, nil}, _]}),
|
||||
do: {name, 0}
|
||||
def type_to_signature({:::, _, [{name, _, args}, _]}),
|
||||
do: {name, length(args)}
|
||||
|
||||
## Macro callbacks
|
||||
|
||||
@doc false
|
||||
def deftype(kind, {:::, _, [{name, _, args}, definition]}, caller) when is_atom(name) and name != ::: do
|
||||
def defspec(kind, expr, caller) do
|
||||
Module.store_typespec(caller.module, kind, {kind, expr, caller})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def deftype(kind, expr, caller) do
|
||||
module = caller.module
|
||||
doc = Module.get_attribute(module, :typedoc)
|
||||
|
||||
Module.delete_attribute(module, :typedoc)
|
||||
Module.store_typespec(module, kind, {kind, expr, doc, caller})
|
||||
end
|
||||
|
||||
## Translation from Elixir AST to typespec AST
|
||||
|
||||
@doc false
|
||||
def translate_type(kind, {:::, _, [{name, _, args}, definition]}, doc, caller) when is_atom(name) and name != ::: do
|
||||
args =
|
||||
if is_atom(args) do
|
||||
[]
|
||||
@@ -525,31 +520,43 @@ defmodule Kernel.Typespec do
|
||||
for(arg <- args, do: variable(arg))
|
||||
end
|
||||
|
||||
vars = for {:var, _, var} <- args, do: var
|
||||
spec = typespec(definition, vars, caller)
|
||||
vars = for {:var, _, var} <- args, do: var
|
||||
spec = typespec(definition, vars, caller)
|
||||
|
||||
vars = for {:var, _, _} = var <- args, do: var
|
||||
type = {name, spec, vars}
|
||||
vars = for {:var, _, _} = var <- args, do: var
|
||||
type = {name, spec, vars}
|
||||
arity = length(vars)
|
||||
|
||||
define_type(caller, kind, type)
|
||||
{kind, export} =
|
||||
case kind do
|
||||
:type -> {:type, true}
|
||||
:typep -> {:type, false}
|
||||
:opaque -> {:opaque, true}
|
||||
end
|
||||
|
||||
if not export and doc do
|
||||
:elixir_errors.warn(caller.line, caller.file, "type #{name}/#{arity} is private, " <>
|
||||
"@typedoc's are always discarded for private types\n")
|
||||
end
|
||||
|
||||
{{kind, {name, arity}, type}, caller.line, export, doc}
|
||||
end
|
||||
|
||||
def deftype(_kind, other, caller) do
|
||||
def translate_type(_kind, other, _doc, caller) do
|
||||
type_spec = Macro.to_string(other)
|
||||
compile_error caller, "invalid type specification: #{type_spec}"
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
||||
def defspec(type, {:when, _meta, [spec, guard]}, caller) do
|
||||
defspec(type, spec, guard, caller)
|
||||
def translate_spec(kind, {:when, _meta, [spec, guard]}, caller) do
|
||||
translate_spec(kind, spec, guard, caller)
|
||||
end
|
||||
|
||||
def defspec(type, spec, caller) do
|
||||
defspec(type, spec, [], caller)
|
||||
def translate_spec(kind, spec, caller) do
|
||||
translate_spec(kind, spec, [], caller)
|
||||
end
|
||||
|
||||
defp defspec(type, {:::, meta, [{name, _, args}, return]}, guard, caller) when is_atom(name) and name != ::: do
|
||||
defp translate_spec(kind, {:::, meta, [{name, _, args}, return]}, guard, caller) when is_atom(name) and name != ::: do
|
||||
if is_atom(args), do: args = []
|
||||
|
||||
unless Keyword.keyword?(guard) do
|
||||
@@ -565,12 +572,11 @@ defmodule Kernel.Typespec do
|
||||
spec = {:type, line(meta), :bounded_fun, [spec, constraints]}
|
||||
end
|
||||
|
||||
code = {{name, Kernel.length(args)}, spec}
|
||||
Module.compile_typespec(caller.module, type, code)
|
||||
code
|
||||
arity = length(args)
|
||||
{{kind, {name, arity}, spec}, caller.line}
|
||||
end
|
||||
|
||||
defp defspec(_type, spec, _guard, caller) do
|
||||
defp translate_spec(_kind, spec, _guard, caller) do
|
||||
spec = Macro.to_string(spec)
|
||||
compile_error caller, "invalid function type specification: #{spec}"
|
||||
end
|
||||
|
||||
@@ -247,6 +247,17 @@ defmodule Module do
|
||||
@vsn "1.0"
|
||||
end
|
||||
|
||||
* `@external_resource`
|
||||
|
||||
Specify an external resource to the current module.
|
||||
|
||||
Many times a module embeds information from an external file. This
|
||||
attribute allows the module to annotate which external resources
|
||||
have been used.
|
||||
|
||||
Tools like Mix may use this information to ensure the module is
|
||||
recompiled in case any of the external resources change.
|
||||
|
||||
The following attributes are part of typespecs and are also reserved by
|
||||
Elixir (see `Kernel.Typespec` for more information about typespecs):
|
||||
|
||||
@@ -349,8 +360,10 @@ defmodule Module do
|
||||
|
||||
@doc """
|
||||
Creates a module with the given name and defined by
|
||||
the given quoted expressions. The line where the module
|
||||
is defined and its file can be passed as options.
|
||||
the given quoted expressions.
|
||||
|
||||
The line where the module is defined and its file **must**
|
||||
be passed as options.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -376,13 +389,16 @@ defmodule Module do
|
||||
when defining the module, while `defmodule` automatically
|
||||
shares the same environment.
|
||||
"""
|
||||
def create(module, quoted, opts \\ [])
|
||||
def create(module, quoted, opts)
|
||||
|
||||
def create(module, quoted, %Macro.Env{} = env) do
|
||||
create(module, quoted, Map.to_list(env))
|
||||
end
|
||||
|
||||
def create(module, quoted, opts) when is_atom(module) do
|
||||
def create(module, quoted, opts) when is_atom(module) and is_list(opts) do
|
||||
unless Keyword.has_key?(opts, :file) do
|
||||
raise ArgumentError, "expected :file to be given as option"
|
||||
end
|
||||
:elixir_module.compile(module, quoted, [], :elixir.env_for_eval(opts))
|
||||
end
|
||||
|
||||
@@ -904,7 +920,7 @@ defmodule Module do
|
||||
@doc false
|
||||
# Used internally to compile types. This function
|
||||
# is private and must be used only internally.
|
||||
def compile_typespec(module, key, value) when is_atom(key) do
|
||||
def store_typespec(module, key, value) when is_atom(key) do
|
||||
assert_not_compiled!(:put_attribute, module)
|
||||
table = data_table_for(module)
|
||||
|
||||
|
||||
@@ -521,7 +521,7 @@ defmodule Path do
|
||||
* `*` - matches any number of characters up to the end of the filename, the
|
||||
next dot, or the next slash
|
||||
|
||||
* `**` - two adjacent <c>*</c>'s used as a single pattern will match all
|
||||
* `**` - two adjacent `*`'s used as a single pattern will match all
|
||||
files and zero or more directories and subdirectories
|
||||
|
||||
* `[char1,char2,...]` - matches any of the characters listed; two
|
||||
|
||||
@@ -374,7 +374,7 @@ defmodule Process do
|
||||
def info(pid, :registered_name) do
|
||||
case :erlang.process_info(pid, :registered_name) do
|
||||
:undefined -> nil
|
||||
[] -> {:registered_name, nil}
|
||||
[] -> {:registered_name, []}
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
@@ -580,20 +580,22 @@ defmodule Protocol do
|
||||
def __impl__(:target), do: unquote(impl)
|
||||
def __impl__(:protocol), do: unquote(protocol)
|
||||
def __impl__(:for), do: unquote(for)
|
||||
end)
|
||||
end, Macro.Env.location(env))
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __spec__?(module, name, arity) do
|
||||
tuple = {name, arity}
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
signature = {name, arity}
|
||||
specs = Module.get_attribute(module, :spec)
|
||||
|
||||
found = for {k, v} <- specs, k == tuple do
|
||||
Kernel.Typespec.define_callback(module, tuple, v)
|
||||
true
|
||||
end
|
||||
found =
|
||||
for {:spec, expr, caller} <- specs,
|
||||
Kernel.Typespec.spec_to_signature(expr) == signature do
|
||||
Kernel.Typespec.define_spec(:callback, expr, caller)
|
||||
true
|
||||
end
|
||||
|
||||
found != []
|
||||
end
|
||||
|
||||
@@ -989,7 +989,7 @@ defmodule Stream do
|
||||
Emits a sequence of values for the given accumulator.
|
||||
|
||||
Successive values are generated by calling `next_fun` with the previous
|
||||
accumulator and it must return a tuple with the current and next
|
||||
accumulator and it must return a tuple with the current value and next
|
||||
accumulator. The enumeration finishes if it returns `nil`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -60,9 +60,9 @@ defmodule System do
|
||||
|
||||
Returns a keyword list with Elixir version, git tag info and compilation date.
|
||||
"""
|
||||
@spec build_info() :: Keyword.t
|
||||
@spec build_info() :: map
|
||||
def build_info do
|
||||
[version: version, tag: get_describe, date: get_date]
|
||||
%{version: version, tag: get_describe, date: get_date}
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
+22
-19
@@ -11,12 +11,12 @@ defmodule Task do
|
||||
res = do_some_other_work()
|
||||
res + Task.await(task)
|
||||
|
||||
Tasks spawned with async can be awaited on by its caller
|
||||
Tasks spawned with `async` can be awaited on by its caller
|
||||
process (and only its caller) as shown in the example above.
|
||||
They are implemented by spawning a process that sends a message
|
||||
to the caller once the given computation is performed.
|
||||
|
||||
Besides `async/1` and `await/1`, tasks can also be used be
|
||||
Besides `async/1` and `await/2`, tasks can also be
|
||||
started as part of supervision trees and dynamically spawned
|
||||
in remote nodes. We will explore all three scenarios next.
|
||||
|
||||
@@ -25,11 +25,11 @@ defmodule Task do
|
||||
The most common way to spawn a task is with `Task.async/1`. A new
|
||||
process will be created, linked and monitored by the caller. Once
|
||||
the task action finishes, a message will be sent to the caller
|
||||
with its result.
|
||||
with the result.
|
||||
|
||||
`Task.await/1` is used to read the message sent by the task. On
|
||||
await, Elixir will also setup a monitor to verify if the process
|
||||
exited with any abnormal reason (or in case exits are being
|
||||
`Task.await/2` is used to read the message sent by the task. On
|
||||
`await`, Elixir will also setup a monitor to verify if the process
|
||||
exited for any abnormal reason (or in case exits are being
|
||||
trapped by the caller).
|
||||
|
||||
## Supervised tasks
|
||||
@@ -39,7 +39,7 @@ defmodule Task do
|
||||
|
||||
Task.start_link(fn -> IO.puts "ok" end)
|
||||
|
||||
Such can be mounted in your supervision tree as:
|
||||
Such tasks can be mounted in your supervision tree as:
|
||||
|
||||
import Supervisor.Spec
|
||||
|
||||
@@ -47,9 +47,9 @@ defmodule Task do
|
||||
worker(Task, [fn -> IO.puts "ok" end])
|
||||
]
|
||||
|
||||
Since such tasks are supervised and not directly linked to
|
||||
Since these tasks are supervised and not directly linked to
|
||||
the caller, they cannot be awaited on. Note `start_link/1`,
|
||||
differently from `async/1`, returns `{:ok, pid}` (which is
|
||||
unlike `async/1`, returns `{:ok, pid}` (which is
|
||||
the result expected by supervision trees).
|
||||
|
||||
## Supervision trees
|
||||
@@ -85,7 +85,7 @@ defmodule Task do
|
||||
|
||||
It contains two fields:
|
||||
|
||||
* `:pid` - the proces reference of the task process; it may be a pid
|
||||
* `:pid` - the process reference of the task process; it may be a pid
|
||||
or a tuple containing the process and node names
|
||||
|
||||
* `:ref` - the task monitor reference
|
||||
@@ -112,14 +112,14 @@ defmodule Task do
|
||||
@doc """
|
||||
Starts a task that can be awaited on.
|
||||
|
||||
This function spawns a process that is linked and monitored
|
||||
to the caller process. A `Task` struct is returned containing
|
||||
This function spawns a process that is linked to and monitored
|
||||
by the caller process. A `Task` struct is returned containing
|
||||
the relevant information.
|
||||
|
||||
## Task's message format
|
||||
|
||||
The reply sent by the task will be in the format `{ref, msg}`,
|
||||
where `ref` is the monitoring reference hold by the task.
|
||||
where `ref` is the monitoring reference held by the task.
|
||||
"""
|
||||
@spec async(fun) :: t
|
||||
def async(fun) do
|
||||
@@ -143,13 +143,16 @@ defmodule Task do
|
||||
|
||||
defp get_info(self) do
|
||||
{node(),
|
||||
self |> Process.info(:registered_name) |> elem(1) |> Kernel.||(self)}
|
||||
case Process.info(self, :registered_name) do
|
||||
{:registered_name, []} -> self()
|
||||
{:registered_name, name} -> name
|
||||
end}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Awaits for a task reply.
|
||||
|
||||
A timeout, in miliseconds, can be given with default value
|
||||
A timeout, in milliseconds, can be given with default value
|
||||
of `5000`. In case the task process dies, this function will
|
||||
exit with the same reason as the task.
|
||||
"""
|
||||
@@ -178,13 +181,13 @@ defmodule Task do
|
||||
This function returns a tuple with the task and the
|
||||
returned value in case the message matches a task that
|
||||
exited with success, it raises in case the found task
|
||||
failed or nil if no task was found.
|
||||
failed or `nil` if no task was found.
|
||||
|
||||
This function is useful in situations where multiple
|
||||
tasks are spawned and their results are collected just
|
||||
later on. For example, a GenServer can spawn tasks,
|
||||
tasks are spawned and their results are collected
|
||||
later on. For example, a `GenServer` can spawn tasks,
|
||||
store the tasks in a list and later use `Task.find/2`
|
||||
to see if upcoming messages are from any of the tasks.
|
||||
to see if incoming messages are from any of the tasks.
|
||||
"""
|
||||
@spec find([t], any) :: {term, t} | nil | no_return
|
||||
def find(tasks, msg)
|
||||
|
||||
@@ -10,11 +10,13 @@ defmodule Task.Supervised do
|
||||
end
|
||||
|
||||
def async(caller, info, mfa) do
|
||||
initial_call(mfa)
|
||||
ref = receive do: ({^caller, ref} -> ref)
|
||||
send caller, {ref, do_apply(info, mfa)}
|
||||
end
|
||||
|
||||
def reply(caller, info, mfa) do
|
||||
initial_call(mfa)
|
||||
:erlang.link(caller)
|
||||
:proc_lib.init_ack({:ok, self()})
|
||||
|
||||
@@ -45,9 +47,24 @@ defmodule Task.Supervised do
|
||||
end
|
||||
|
||||
def noreply(info, mfa) do
|
||||
initial_call(mfa)
|
||||
do_apply(info, mfa)
|
||||
end
|
||||
|
||||
defp initial_call(mfa) do
|
||||
Process.put(:"$initial_call", get_initial_call(mfa))
|
||||
end
|
||||
|
||||
defp get_initial_call({:erlang, :apply, [fun, []]}) when is_function(fun, 0) do
|
||||
{:module, module} = :erlang.fun_info(fun, :module)
|
||||
{:name, name} = :erlang.fun_info(fun, :name)
|
||||
{module, name, 0}
|
||||
end
|
||||
|
||||
defp get_initial_call({mod, fun, args}) do
|
||||
{mod, fun, length(args)}
|
||||
end
|
||||
|
||||
defp do_apply(info, {module, fun, args} = mfa) do
|
||||
try do
|
||||
apply(module, fun, args)
|
||||
|
||||
@@ -7,7 +7,7 @@ defmodule Task.Supervisor do
|
||||
`:simple_one_for_one` supervisor where the workers are temporary
|
||||
(i.e. they are not restarted after they die).
|
||||
|
||||
The functions in this module allow tasks can be spawned and awaited
|
||||
The functions in this module allow tasks to be spawned and awaited
|
||||
from a supervisor, similar to the functions defined in the `Task` module.
|
||||
|
||||
## Name Registration
|
||||
@@ -26,7 +26,7 @@ defmodule Task.Supervisor do
|
||||
docs;
|
||||
|
||||
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
|
||||
or an integer indicating the timeout value, defaults to 5000 miliseconds;
|
||||
or an integer indicating the timeout value, defaults to 5000 milliseconds;
|
||||
"""
|
||||
@spec start_link(Supervisor.options) :: Supervisor.on_start
|
||||
def start_link(opts \\ []) do
|
||||
@@ -63,7 +63,7 @@ defmodule Task.Supervisor do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Terminates the given child at pid.
|
||||
Terminates the child with the given `pid`.
|
||||
"""
|
||||
@spec terminate_child(Supervisor.supervisor, pid) :: :ok
|
||||
def terminate_child(supervisor, pid) when is_pid(pid) do
|
||||
@@ -81,9 +81,9 @@ defmodule Task.Supervisor do
|
||||
@doc """
|
||||
Starts a task as child of the given `supervisor`.
|
||||
|
||||
Note the spawned process is not linked to the caller but
|
||||
Note that the spawned process is not linked to the caller, but
|
||||
only to the supervisor. This command is useful in case the
|
||||
task needs to emit side-effects (like I/O) and does not need
|
||||
task needs to perform side-effects (like I/O) and does not need
|
||||
to report back to the caller.
|
||||
"""
|
||||
@spec start_child(Supervisor.supervisor, fun) :: {:ok, pid}
|
||||
@@ -104,6 +104,9 @@ defmodule Task.Supervisor do
|
||||
|
||||
defp get_info(self) do
|
||||
{node(),
|
||||
self |> Process.info(:registered_name) |> elem(1) |> Kernel.||(self)}
|
||||
case Process.info(self, :registered_name) do
|
||||
{:registered_name, []} -> self()
|
||||
{:registered_name, name} -> name
|
||||
end}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -287,6 +287,8 @@ defmodule URI do
|
||||
host: "elixir-lang.org", port: 80}
|
||||
|
||||
"""
|
||||
def parse(%URI{} = uri), do: uri
|
||||
|
||||
def parse(s) when is_binary(s) do
|
||||
# From http://tools.ietf.org/html/rfc3986#appendix-B
|
||||
regex = ~r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
|
||||
|
||||
+30
-23
@@ -18,21 +18,28 @@
|
||||
-export([start/2, stop/1, config_change/3]).
|
||||
|
||||
start(_Type, _Args) ->
|
||||
%% Set the shell to unicode so printing inside scripts work
|
||||
%% Those can take a while, so let's do it in a new process
|
||||
spawn(fun() ->
|
||||
io:setopts(standard_io, [binary,{encoding,utf8}]),
|
||||
io:setopts(standard_error, [{unicode,true}]),
|
||||
case file:native_name_encoding() of
|
||||
latin1 ->
|
||||
io:format(standard_error,
|
||||
"warning: the VM is running with native name encoding of latin1 which may cause "
|
||||
"Elixir to malfunction as it expects utf8. Please ensure your locale is set to UTF-8 "
|
||||
"(which can be verified by running \"locale\" in your shell)~n", []);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end),
|
||||
%% In case there is a shell, we can't really change its
|
||||
%% encoding, so we just set binary to true. Otherwise
|
||||
%% we must set the encoding as the user with no shell
|
||||
%% has encoding set to latin1.
|
||||
Opts =
|
||||
case init:get_argument(noshell) of
|
||||
{ok, _} -> [binary,{encoding,utf8}];
|
||||
error -> [binary]
|
||||
end,
|
||||
|
||||
io:setopts(standard_io, Opts),
|
||||
io:setopts(standard_error, [{unicode,true}]),
|
||||
case file:native_name_encoding() of
|
||||
latin1 ->
|
||||
io:format(standard_error,
|
||||
"warning: the VM is running with native name encoding of latin1 which may cause "
|
||||
"Elixir to malfunction as it expects utf8. Please ensure your locale is set to UTF-8 "
|
||||
"(which can be verified by running \"locale\" in your shell)~n", []);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
|
||||
elixir_sup:start_link().
|
||||
|
||||
stop(_S) ->
|
||||
@@ -65,37 +72,37 @@ env_for_eval(Opts) ->
|
||||
|
||||
env_for_eval(Env, Opts) ->
|
||||
Line = case lists:keyfind(line, 1, Opts) of
|
||||
{line, RawLine} when is_integer(RawLine) -> RawLine;
|
||||
{line, LineOpt} when is_integer(LineOpt) -> LineOpt;
|
||||
false -> ?m(Env, line)
|
||||
end,
|
||||
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
{file, RawFile} when is_binary(RawFile) -> RawFile;
|
||||
{file, FileOpt} when is_binary(FileOpt) -> FileOpt;
|
||||
false -> ?m(Env, file)
|
||||
end,
|
||||
|
||||
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{delegate_locals_to, LocalOpt} -> LocalOpt;
|
||||
{delegate_locals_to, LocalOpt} when is_atom(LocalOpt) -> LocalOpt;
|
||||
false -> ?m(Env, local)
|
||||
end,
|
||||
|
||||
Aliases = case lists:keyfind(aliases, 1, Opts) of
|
||||
{aliases, AliasesOpt} -> AliasesOpt;
|
||||
{aliases, AliasesOpt} when is_list(AliasesOpt) -> AliasesOpt;
|
||||
false -> ?m(Env, aliases)
|
||||
end,
|
||||
|
||||
Requires = case lists:keyfind(requires, 1, Opts) of
|
||||
{requires, List} -> ordsets:from_list(List);
|
||||
{requires, RequiresOpt} when is_list(RequiresOpt) -> ordsets:from_list(RequiresOpt);
|
||||
false -> ?m(Env, requires)
|
||||
end,
|
||||
|
||||
Functions = case lists:keyfind(functions, 1, Opts) of
|
||||
{functions, FunctionsOpt} -> FunctionsOpt;
|
||||
{functions, FunctionsOpt} when is_list(FunctionsOpt) -> FunctionsOpt;
|
||||
false -> ?m(Env, functions)
|
||||
end,
|
||||
|
||||
Macros = case lists:keyfind(macros, 1, Opts) of
|
||||
{macros, MacrosOpt} -> MacrosOpt;
|
||||
{macros, MacrosOpt} when is_list(MacrosOpt) -> MacrosOpt;
|
||||
false -> ?m(Env, macros)
|
||||
end,
|
||||
|
||||
@@ -108,7 +115,7 @@ env_for_eval(Env, Opts) ->
|
||||
file := File, local := Local, module := Module,
|
||||
macros := Macros, functions := Functions,
|
||||
requires := Requires, aliases := Aliases, line := Line
|
||||
}.
|
||||
}.
|
||||
|
||||
%% String evaluation
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_clauses).
|
||||
-export([match/3, clause/7, clauses/4, guards/4, get_pairs/2, get_pairs/3,
|
||||
-export([match/3, clause/7, clauses/4, guards/4, get_pairs/3, get_pairs/4,
|
||||
extract_splat_guards/1, extract_guards/1]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Get pairs from a clause.
|
||||
|
||||
get_pairs(Key, Clauses) ->
|
||||
get_pairs(Key, Clauses, false).
|
||||
get_pairs(Key, Clauses, AllowNil) ->
|
||||
get_pairs(Key, Clauses, As) ->
|
||||
get_pairs(Key, Clauses, As, false).
|
||||
get_pairs(Key, Clauses, As, AllowNil) ->
|
||||
case lists:keyfind(Key, 1, Clauses) of
|
||||
{Key, Pairs} when is_list(Pairs) ->
|
||||
[{Key, Meta, Left, Right} || {'->', Meta, [Left, Right]} <- Pairs];
|
||||
[{As, Meta, Left, Right} || {'->', Meta, [Left, Right]} <- Pairs];
|
||||
{Key, nil} when AllowNil ->
|
||||
[];
|
||||
false ->
|
||||
@@ -79,9 +79,8 @@ do_clauses(Meta, DecoupledClauses, Return, S) ->
|
||||
% Transform tree just passing the variables counter forward
|
||||
% and storing variables defined inside each clause.
|
||||
Transformer = fun(X, {SAcc, VAcc}) ->
|
||||
{TX, TS} = each_clause(X, Return, SAcc),
|
||||
{TX,
|
||||
{elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc]}}
|
||||
{TX, TS} = each_clause(Meta, X, Return, SAcc),
|
||||
{TX, {elixir_scope:mergec(S, TS), [TS#elixir_scope.export_vars|VAcc]}}
|
||||
end,
|
||||
|
||||
{TClauses, {TS, ReverseCV}} =
|
||||
@@ -140,19 +139,27 @@ expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
|
||||
|
||||
% Handle each key/value clause pair and translate them accordingly.
|
||||
|
||||
each_clause({do, Meta, [Condition], Expr}, Return, S) ->
|
||||
each_clause(Export, {match, Meta, [Condition], Expr}, Return, S) ->
|
||||
Fun = wrap_export_fun(Export, fun elixir_translator:translate_args/2),
|
||||
{Arg, Guards} = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
|
||||
clause(?line(Meta), Fun, [Arg], Expr, Guards, Return, S);
|
||||
|
||||
each_clause({else, Meta, [Condition], Expr}, Return, S) ->
|
||||
{Arg, Guards} = extract_guards(Condition),
|
||||
clause(?line(Meta), fun elixir_translator:translate_args/2, [Arg], Expr, Guards, Return, S);
|
||||
|
||||
each_clause({'after', Meta, [Condition], Expr}, Return, S) ->
|
||||
{TCondition, SC} = elixir_translator:translate(Condition, S),
|
||||
each_clause(Export, {expr, Meta, [Condition], Expr}, Return, S) ->
|
||||
{TCondition, SC} = (wrap_export_fun(Export, fun elixir_translator:translate/2))(Condition, S),
|
||||
{TExpr, SB} = elixir_translator:translate_block(Expr, Return, SC),
|
||||
{{clause, ?line(Meta), [TCondition], [], unblock(TExpr)}, SB}.
|
||||
|
||||
wrap_export_fun(Meta, Fun) ->
|
||||
case lists:keyfind(export_head, 1, Meta) of
|
||||
{export_head, true} ->
|
||||
Fun;
|
||||
_ ->
|
||||
fun(Args, S) ->
|
||||
{TArgs, TS} = Fun(Args, S),
|
||||
{TArgs, TS#elixir_scope{export_vars = S#elixir_scope.export_vars}}
|
||||
end
|
||||
end.
|
||||
|
||||
% Check if the given expression is a match tuple.
|
||||
% This is a small optimization to allow us to change
|
||||
% existing assignments instead of creating new ones every time.
|
||||
@@ -181,7 +188,7 @@ normalize_vars(Key, Value, #elixir_scope{vars=Vars,export_vars=ClauseVars} = S)
|
||||
VS = S#elixir_scope{
|
||||
vars=orddict:store(Key, Value, Vars),
|
||||
export_vars=orddict:store(Key, Value, ClauseVars)
|
||||
},
|
||||
},
|
||||
|
||||
Expr = case orddict:find(Key, Vars) of
|
||||
{ok, {PreValue, _}} -> {var, 0, PreValue};
|
||||
|
||||
@@ -194,6 +194,7 @@ core_main() ->
|
||||
<<"lib/elixir/lib/string/chars.ex">>,
|
||||
<<"lib/elixir/lib/io.ex">>,
|
||||
<<"lib/elixir/lib/path.ex">>,
|
||||
<<"lib/elixir/lib/file.ex">>,
|
||||
<<"lib/elixir/lib/system.ex">>,
|
||||
<<"lib/elixir/lib/kernel/cli.ex">>,
|
||||
<<"lib/elixir/lib/kernel/error_handler.ex">>,
|
||||
|
||||
@@ -129,20 +129,28 @@ make_struct_available(_, _, _, _) ->
|
||||
%% Retrieve location from meta file or @file, otherwise nil
|
||||
|
||||
retrieve_location(Line, File, Module) ->
|
||||
case not(elixir_compiler:get_opt(internal)) andalso
|
||||
'Elixir.Module':get_attribute(Module, file) of
|
||||
X when X == nil; X == false ->
|
||||
if
|
||||
is_binary(File) ->
|
||||
{elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line};
|
||||
true ->
|
||||
nil
|
||||
end;
|
||||
case get_location_attribute(Module) of
|
||||
nil when not is_binary(File) ->
|
||||
nil;
|
||||
nil ->
|
||||
{normalize_location(File), Line};
|
||||
X when is_binary(X) ->
|
||||
'Elixir.Module':delete_attribute(Module, file),
|
||||
{elixir_utils:characters_to_list(X), 0}
|
||||
{normalize_location(X), 0};
|
||||
{X, L} when is_binary(X) andalso is_integer(L) ->
|
||||
'Elixir.Module':delete_attribute(Module, file),
|
||||
{normalize_location(X), L}
|
||||
end.
|
||||
|
||||
get_location_attribute(Module) ->
|
||||
case elixir_compiler:get_opt(internal) of
|
||||
true -> nil;
|
||||
false -> 'Elixir.Module':get_attribute(Module, file)
|
||||
end.
|
||||
|
||||
normalize_location(X) ->
|
||||
elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(X)).
|
||||
|
||||
%% Compile super
|
||||
|
||||
compile_super(Module, true, #{function := Function}) ->
|
||||
@@ -158,16 +166,10 @@ translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_in
|
||||
{EArgs, EGuards, EBody, _} = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
|
||||
Args, Guards, expr_from_body(Line, Body), E),
|
||||
|
||||
%% Macros receive a special argument on invocation. Notice it does
|
||||
%% not affect the arity of the stored function, but the clause
|
||||
%% already contains it.
|
||||
EAllArgs = case is_macro(Kind) of
|
||||
true -> [{'_@CALLER', [{line,Line}], nil}|EArgs];
|
||||
false -> EArgs
|
||||
end,
|
||||
Body == nil andalso check_args_for_bodyless_clause(Line, EArgs, E),
|
||||
|
||||
S = elixir_env:env_to_scope(E),
|
||||
{Unpacked, Defaults} = elixir_def_defaults:unpack(Kind, Name, EAllArgs, S),
|
||||
{Unpacked, Defaults} = elixir_def_defaults:unpack(Kind, Name, EArgs, S),
|
||||
{Clauses, Super} = translate_clause(Body, Line, Kind, Unpacked, EGuards, EBody, S),
|
||||
|
||||
run_on_definition_callbacks(Kind, Line, Module, Name, EArgs, EGuards, EBody, E),
|
||||
@@ -182,22 +184,30 @@ translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
|
||||
{TClause, TS} = elixir_clauses:clause(Line,
|
||||
fun elixir_translator:translate_args/2, Args, Body, Guards, true, S),
|
||||
|
||||
%% Set __CALLER__ if used
|
||||
FClause = case is_macro(Kind) andalso TS#elixir_scope.caller of
|
||||
true ->
|
||||
FBody = {'match', Line,
|
||||
{'var', Line, '__CALLER__'},
|
||||
elixir_utils:erl_call(Line, elixir_env, linify, [{var, Line, '_@CALLER'}])
|
||||
},
|
||||
setelement(5, TClause, [FBody|element(5, TClause)]);
|
||||
false -> TClause
|
||||
FClause = case is_macro(Kind) of
|
||||
true ->
|
||||
FArgs = {var, Line, '_@CALLER'},
|
||||
MClause = setelement(3, TClause, [FArgs|element(3, TClause)]),
|
||||
|
||||
case TS#elixir_scope.caller of
|
||||
true ->
|
||||
FBody = {'match', Line,
|
||||
{'var', Line, '__CALLER__'},
|
||||
elixir_utils:erl_call(Line, elixir_env, linify, [{var, Line, '_@CALLER'}])
|
||||
},
|
||||
setelement(5, MClause, [FBody|element(5, TClause)]);
|
||||
false ->
|
||||
MClause
|
||||
end;
|
||||
false ->
|
||||
TClause
|
||||
end,
|
||||
|
||||
{[FClause], TS#elixir_scope.super}.
|
||||
|
||||
expr_from_body(_Line, nil) -> nil;
|
||||
expr_from_body(_Line, nil) -> nil;
|
||||
expr_from_body(_Line, [{do, Expr}]) -> Expr;
|
||||
expr_from_body(Line, Else) -> {'try', [{line,Line}], [Else]}.
|
||||
expr_from_body(Line, Else) -> {'try', [{line,Line}], [Else]}.
|
||||
|
||||
is_macro(defmacro) -> true;
|
||||
is_macro(defmacrop) -> true;
|
||||
@@ -327,6 +337,18 @@ defaults_conflict(A, D, Arity, Defaults) ->
|
||||
((Arity >= (A - D)) andalso (Arity < A)) orelse
|
||||
((A >= (Arity - Defaults)) andalso (A < Arity)).
|
||||
|
||||
check_args_for_bodyless_clause(Line, Args, E) ->
|
||||
[ begin
|
||||
elixir_errors:form_error(Line, ?m(E, file), ?MODULE, invalid_args_for_bodyless_clause)
|
||||
end || Arg <- Args, invalid_arg(Arg) ].
|
||||
|
||||
invalid_arg({Name, _, Kind}) when is_atom(Name), is_atom(Kind) ->
|
||||
false;
|
||||
invalid_arg({'\\\\', _, [{Name, _, Kind}, _]}) when is_atom(Name), is_atom(Kind) ->
|
||||
false;
|
||||
invalid_arg(_) ->
|
||||
true.
|
||||
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, {no_alias, Atom});
|
||||
|
||||
@@ -366,5 +388,8 @@ format_error({no_alias, Atom}) ->
|
||||
format_error({invalid_def, Kind, NameAndArgs}) ->
|
||||
io_lib:format("invalid syntax in ~ts ~ts", [Kind, 'Elixir.Macro':to_string(NameAndArgs)]);
|
||||
|
||||
format_error(invalid_args_for_bodyless_clause) ->
|
||||
"can use only variables and \\\\ as arguments of bodyless clause";
|
||||
|
||||
format_error({missing_do, Kind}) ->
|
||||
io_lib:format("missing do keyword in ~ts", [Kind]).
|
||||
|
||||
@@ -21,17 +21,19 @@ unpack(Kind, Name, Args, S) ->
|
||||
%% Unpack default from given args.
|
||||
%% Returns the given arguments without their default
|
||||
%% clauses and a list of clauses for the default calls.
|
||||
unpack_each(Kind, Name, [{'\\\\', Line, [Expr, _]}|T] = List, Acc, Clauses, S) ->
|
||||
Base = build_match(Acc, Line, []),
|
||||
{Args, Invoke} = extract_defaults(List, Line, length(Base), [], []),
|
||||
unpack_each(Kind, Name, [{'\\\\', DefMeta, [Expr, _]}|T] = List, Acc, Clauses, S) ->
|
||||
Base = wrap_kind(Kind, build_match(Acc, [])),
|
||||
{Args, Invoke} = extract_defaults(List, length(Base), [], []),
|
||||
|
||||
{DefArgs, SA} = elixir_clauses:match(fun elixir_translator:translate_args/2, Base ++ Args, S),
|
||||
{DefInvoke, _} = elixir_translator:translate_args(Base ++ Invoke, SA),
|
||||
|
||||
Line = ?line(DefMeta),
|
||||
|
||||
Call = {call, Line,
|
||||
{atom, Line, name_for_kind(Kind, Name)},
|
||||
DefInvoke
|
||||
},
|
||||
},
|
||||
|
||||
Clause = {clause, Line, DefArgs, [], [Call]},
|
||||
unpack_each(Kind, Name, T, [Expr|Acc], [Clause|Clauses], S);
|
||||
@@ -44,25 +46,28 @@ unpack_each(_Kind, _Name, [], Acc, Clauses, _S) ->
|
||||
|
||||
% Extract default values from args following the current default clause.
|
||||
|
||||
extract_defaults([{'\\\\', _, [_Expr, Default]}|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
extract_defaults(T, Line, Counter, NewArgs, [Default|NewInvoke]);
|
||||
extract_defaults([{'\\\\', _, [_Expr, Default]}|T], Counter, NewArgs, NewInvoke) ->
|
||||
extract_defaults(T, Counter, NewArgs, [Default|NewInvoke]);
|
||||
|
||||
extract_defaults([_|T], Line, Counter, NewArgs, NewInvoke) ->
|
||||
H = {elixir_utils:atom_concat(["_@D", Counter]), Line, nil},
|
||||
extract_defaults(T, Line, Counter + 1, [H|NewArgs], [H|NewInvoke]);
|
||||
extract_defaults([_|T], Counter, NewArgs, NewInvoke) ->
|
||||
H = {elixir_utils:atom_concat(["x", Counter]), [], nil},
|
||||
extract_defaults(T, Counter + 1, [H|NewArgs], [H|NewInvoke]);
|
||||
|
||||
extract_defaults([], _Line, _Counter, NewArgs, NewInvoke) ->
|
||||
extract_defaults([], _Counter, NewArgs, NewInvoke) ->
|
||||
{lists:reverse(NewArgs), lists:reverse(NewInvoke)}.
|
||||
|
||||
% Build matches for all the previous argument until the current default clause.
|
||||
|
||||
build_match([], _Line, Acc) -> Acc;
|
||||
build_match([], Acc) -> Acc;
|
||||
|
||||
build_match([_|T], Line, Acc) ->
|
||||
Var = {elixir_utils:atom_concat(["_@D", length(T)]), Line, nil},
|
||||
build_match(T, Line, [Var|Acc]).
|
||||
build_match([_|T], Acc) ->
|
||||
Var = {elixir_utils:atom_concat(["x", length(T)]), [], nil},
|
||||
build_match(T, [Var|Acc]).
|
||||
|
||||
% Given the invoked function name based on the kind
|
||||
|
||||
wrap_kind(Kind, Args) when Kind == defmacro; Kind == defmacrop -> [{c, [], nil}|Args];
|
||||
wrap_kind(_Kind, Args) -> Args.
|
||||
|
||||
name_for_kind(Kind, Name) when Kind == defmacro; Kind == defmacrop -> elixir_utils:macro_name(Name);
|
||||
name_for_kind(_Kind, Name) -> Name.
|
||||
@@ -147,9 +147,17 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
|
||||
{function, Receiver} ->
|
||||
elixir_lexical:record_import(Receiver, ?m(E, lexical_tracker)),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
|
||||
case rewrite(Receiver, Name, Args, Arity) of
|
||||
{ok, _, _, _} = Res -> Res;
|
||||
false -> {ok, Receiver, Name, Args}
|
||||
|
||||
%% DEPRECATED
|
||||
case {Receiver, Name, Arity} of
|
||||
{?kernel, size, 1} ->
|
||||
check_deprecation(Meta, Receiver, Name, Arity, E),
|
||||
{ok, erlang, size, Args};
|
||||
_ ->
|
||||
case rewrite(Receiver, Name, Args, Arity) of
|
||||
{ok, _, _, _} = Res -> Res;
|
||||
false -> {ok, Receiver, Name, Args}
|
||||
end
|
||||
end;
|
||||
{macro, Receiver} ->
|
||||
check_deprecation(Meta, Receiver, Name, Arity, E),
|
||||
@@ -482,9 +490,6 @@ inline(?string, to_integer, 2) -> {erlang, binary_to_integer};
|
||||
inline(?system, stacktrace, 0) -> {erlang, get_stacktrace};
|
||||
inline(?tuple, to_list, 1) -> {erlang, tuple_to_list};
|
||||
|
||||
%% DEPRECATED
|
||||
inline(?kernel, size, 1) -> {erlang, size};
|
||||
|
||||
inline(_, _, _) -> false.
|
||||
|
||||
check_deprecation(Meta, Receiver, Name, Arity, #{file := File}) ->
|
||||
@@ -512,58 +517,14 @@ deprecation_message(Warning, Message) ->
|
||||
|
||||
deprecation('Elixir.Kernel', 'size', 1) ->
|
||||
"use byte_size/1 or tuple_size/1 instead";
|
||||
deprecation('Elixir.Kernel', 'atom_to_binary', 1) ->
|
||||
"use Atom.to_string/1 instead";
|
||||
deprecation('Elixir.Kernel', 'atom_to_list', 1) ->
|
||||
"use Atom.to_char_list/1 instead";
|
||||
deprecation('Elixir.Kernel', 'binary_to_atom', 1) ->
|
||||
"use String.to_atom/1 instead";
|
||||
deprecation('Elixir.Kernel', 'binary_to_existing_atom', 1) ->
|
||||
"use String.to_existing_atom/1 instead";
|
||||
deprecation('Elixir.Kernel', 'binary_to_integer', 1) ->
|
||||
"use String.to_integer/1 instead";
|
||||
deprecation('Elixir.Kernel', 'binary_to_integer', 2) ->
|
||||
"use String.to_integer/2 instead";
|
||||
deprecation('Elixir.Kernel', 'binary_to_float', 1) ->
|
||||
"use String.to_float/1 instead";
|
||||
deprecation('Elixir.Kernel', 'bitstring_to_list', 1) ->
|
||||
"use BitString.to_list/1 instead";
|
||||
deprecation('Elixir.Kernel', 'float_to_binary', 1) ->
|
||||
"use Float.to_string/1 instead";
|
||||
deprecation('Elixir.Kernel', 'float_to_binary', 2) ->
|
||||
"use Float.to_string/2 instead";
|
||||
deprecation('Elixir.Kernel', 'float_to_list', 1) ->
|
||||
"use Float.to_char_list/1 instead";
|
||||
deprecation('Elixir.Kernel', 'float_to_list', 2) ->
|
||||
"use Float.to_char_list/2 instead";
|
||||
deprecation('Elixir.Kernel', 'iodata_length', 1) ->
|
||||
"use IO.iodata_length/1 instead";
|
||||
deprecation('Elixir.Kernel', 'iodata_to_binary', 1) ->
|
||||
"use IO.iodata_to_binary/1 instead";
|
||||
deprecation('Elixir.Kernel', 'integer_to_binary', 1) ->
|
||||
"use Integer.to_string/1 instead";
|
||||
deprecation('Elixir.Kernel', 'integer_to_binary', 2) ->
|
||||
"use Integer.to_string/2 instead";
|
||||
deprecation('Elixir.Kernel', 'integer_to_list', 1) ->
|
||||
"use Integer.to_char_list/1 instead";
|
||||
deprecation('Elixir.Kernel', 'integer_to_list', 2) ->
|
||||
"use Integer.to_char_list/2 instead";
|
||||
deprecation('Elixir.Kernel', 'list_to_atom', 1) ->
|
||||
"use List.to_atom/1 instead";
|
||||
deprecation('Elixir.Kernel', 'list_to_bitstring', 1) ->
|
||||
true;
|
||||
deprecation('Elixir.Kernel', 'list_to_existing_atom', 1) ->
|
||||
"use List.to_existing_atom/1 instead";
|
||||
deprecation('Elixir.Kernel', 'list_to_float', 1) ->
|
||||
"use List.to_float/2 instead";
|
||||
deprecation('Elixir.Kernel', 'list_to_integer', 1) ->
|
||||
"use List.to_integer/1 instead";
|
||||
deprecation('Elixir.Kernel', 'list_to_integer', 2) ->
|
||||
"use List.to_integer/2 instead";
|
||||
deprecation('Elixir.Kernel', 'list_to_tuple', 1) ->
|
||||
"use List.to_tuple/1 instead";
|
||||
deprecation('Elixir.Kernel', 'tuple_to_list', 1) ->
|
||||
"use Tuple.to_list/1 instead";
|
||||
% deprecation('Elixir.Mix.Generator', 'from_file', _) ->
|
||||
% "instead pass [from_file: file] to embed_text/2 and embed_template/2 macros. "
|
||||
% "Note that [from_file: file] expects paths relative to the current working "
|
||||
% "directory and not to the current file";
|
||||
% deprecation('Elixir.EEx.TransformerEngine', '__using__', _) ->
|
||||
% "check EEx.SmartEngine for how to build custom engines";
|
||||
% deprecation('Elixir.EEx.AssignsEngine', '__using__', _) ->
|
||||
% "check EEx.SmartEngine for how to build custom engines";
|
||||
|
||||
deprecation(_, _, _) ->
|
||||
false.
|
||||
|
||||
@@ -225,11 +225,22 @@ expand({fn, Meta, Pairs}, E) ->
|
||||
|
||||
%% Case/Receive/Try
|
||||
|
||||
expand({'cond', Meta, [KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'cond', E),
|
||||
{EClauses, EC} = elixir_exp_clauses:'cond'(Meta, KV, E),
|
||||
{{'cond', Meta, [EClauses]}, EC};
|
||||
|
||||
expand({'case', Meta, [Expr, KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'case', E),
|
||||
{EExpr, EE} = expand(Expr, E),
|
||||
{EClauses, EC} = elixir_exp_clauses:'case'(Meta, KV, EE),
|
||||
{{'case', Meta, [EExpr, EClauses]}, EC};
|
||||
FClauses =
|
||||
case (lists:keyfind(optimize_boolean, 1, Meta) == {optimize_boolean, true}) and
|
||||
elixir_utils:returns_boolean(EExpr) of
|
||||
true -> rewrite_case_clauses(EClauses);
|
||||
false -> EClauses
|
||||
end,
|
||||
{{'case', Meta, [EExpr, FClauses]}, EC};
|
||||
|
||||
expand({'receive', Meta, [KV]}, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'receive', E),
|
||||
@@ -272,7 +283,7 @@ expand({'_', _, Kind} = Var, E) when is_atom(Kind) ->
|
||||
expand({Name, Meta, Kind} = Var, #{context := match, export_vars := Export} = E) when is_atom(Name), is_atom(Kind) ->
|
||||
Pair = {Name, var_kind(Meta, Kind)},
|
||||
NewVars = ordsets:add_element(Pair, ?m(E, vars)),
|
||||
NewExport = case (Export /= nil) andalso (lists:keyfind(export, 1, Meta) /= {export, false}) of
|
||||
NewExport = case (Export /= nil) of
|
||||
true -> ordsets:add_element(Pair, Export);
|
||||
false -> Export
|
||||
end,
|
||||
@@ -401,6 +412,9 @@ expand_arg(Arg, {Acc1, Acc2}) ->
|
||||
{EArg, EAcc} = expand(Arg, Acc1),
|
||||
{EArg, {elixir_env:mergea(Acc1, EAcc), elixir_env:mergev(Acc2, EAcc)}}.
|
||||
|
||||
expand_args([Arg], E) ->
|
||||
{EArg, EE} = expand(Arg, E),
|
||||
{[EArg], EE};
|
||||
expand_args(Args, #{context := match} = E) ->
|
||||
expand_many(Args, E);
|
||||
expand_args(Args, E) ->
|
||||
@@ -525,6 +539,27 @@ expand_as({as, Other}, Meta, _IncludeByDefault, _Ref, E) ->
|
||||
|
||||
%% Assertions
|
||||
|
||||
rewrite_case_clauses([{do,[
|
||||
{'->', FalseMeta, [
|
||||
[{'when', _, [Var, {'__op__', _,[
|
||||
'orelse',
|
||||
{{'.', _, [erlang, '=:=']}, _, [Var, nil]},
|
||||
{{'.', _, [erlang, '=:=']}, _, [Var, false]}
|
||||
]}]}],
|
||||
FalseExpr
|
||||
]},
|
||||
{'->', TrueMeta, [
|
||||
[{'_', _, _}],
|
||||
TrueExpr
|
||||
]}
|
||||
]}]) ->
|
||||
[{do, [
|
||||
{'->', FalseMeta, [[false], FalseExpr]},
|
||||
{'->', TrueMeta, [[true], TrueExpr]}
|
||||
]}];
|
||||
rewrite_case_clauses(Clauses) ->
|
||||
Clauses.
|
||||
|
||||
assert_no_match_or_guard_scope(Meta, Kind, E) ->
|
||||
assert_no_match_scope(Meta, Kind, E),
|
||||
assert_no_guard_scope(Meta, Kind, E).
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_exp_clauses).
|
||||
-export([match/3, clause/5, 'case'/3, 'receive'/3, 'try'/3, def/5]).
|
||||
-export([match/3, clause/5, def/5, head/2,
|
||||
'case'/3, 'receive'/3, 'try'/3, 'cond'/3]).
|
||||
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
@@ -18,19 +19,19 @@ def(Fun, Args, Guards, Body, E) ->
|
||||
clause(Meta, Kind, Fun, {'->', ClauseMeta, [_, _]} = Clause, E) when is_function(Fun, 3) ->
|
||||
clause(Meta, Kind, fun(X, Acc) -> Fun(ClauseMeta, X, Acc) end, Clause, E);
|
||||
clause(_Meta, _Kind, Fun, {'->', Meta, [Left, Right]}, E) ->
|
||||
{ELeft, EL} = head(Fun, Left, E),
|
||||
{ELeft, EL} = Fun(Left, E),
|
||||
{ERight, ER} = elixir_exp:expand(Right, EL),
|
||||
{{'->', Meta, [ELeft, ERight]}, ER};
|
||||
clause(Meta, Kind, _Fun, _, E) ->
|
||||
compile_error(Meta, ?m(E, file), "expected -> clauses in ~ts", [Kind]).
|
||||
|
||||
head(Fun, [{'when', Meta, [_,_|_] = All}], E) ->
|
||||
head([{'when', Meta, [_,_|_] = All}], E) ->
|
||||
{Args, Guard} = elixir_utils:split_last(All),
|
||||
{EArgs, EA} = match(Fun, Args, E),
|
||||
{EArgs, EA} = match(fun elixir_exp:expand_args/2, Args, E),
|
||||
{EGuard, EG} = guard(Guard, EA#{context := guard}),
|
||||
{[{'when', Meta, EArgs ++ [EGuard]}], EG#{context := ?m(E, context)}};
|
||||
head(Fun, Args, E) ->
|
||||
match(Fun, Args, E).
|
||||
head(Args, E) ->
|
||||
match(fun elixir_exp:expand_args/2, Args, E).
|
||||
|
||||
guard({'when', Meta, [Left, Right]}, E) ->
|
||||
{ELeft, EL} = guard(Left, E),
|
||||
@@ -51,10 +52,28 @@ guard(Other, E) ->
|
||||
{EClauses, elixir_env:mergev(EVars, E)}.
|
||||
|
||||
do_case(Meta, {'do', _} = Do, Acc, E) ->
|
||||
expand_with_export(Meta, 'case', expand_arg(Meta, 'case', 'do'), Do, Acc, E);
|
||||
Fun = expand_one(Meta, 'case', 'do', fun head/2),
|
||||
expand_with_export(Meta, 'case', Fun, Do, Acc, E);
|
||||
do_case(Meta, {Key, _}, _Acc, E) ->
|
||||
compile_error(Meta, ?m(E, file), "unexpected keyword ~ts in case", [Key]).
|
||||
|
||||
%% Cond
|
||||
|
||||
'cond'(Meta, [], E) ->
|
||||
compile_error(Meta, ?m(E, file), "missing do keyword in cond");
|
||||
'cond'(Meta, KV, E) when not is_list(KV) ->
|
||||
compile_error(Meta, ?m(E, file), "invalid arguments for cond");
|
||||
'cond'(Meta, KV, E) ->
|
||||
EE = E#{export_vars := []},
|
||||
{EClauses, EVars} = lists:mapfoldl(fun(X, Acc) -> do_cond(Meta, X, Acc, EE) end, [], KV),
|
||||
{EClauses, elixir_env:mergev(EVars, E)}.
|
||||
|
||||
do_cond(Meta, {'do', _} = Do, Acc, E) ->
|
||||
Fun = expand_one(Meta, 'cond', 'do', fun elixir_exp:expand_args/2),
|
||||
expand_with_export(Meta, 'cond', Fun, Do, Acc, E);
|
||||
do_cond(Meta, {Key, _}, _Acc, E) ->
|
||||
compile_error(Meta, ?m(E, file), "unexpected keyword ~ts in cond", [Key]).
|
||||
|
||||
%% Receive
|
||||
|
||||
'receive'(Meta, [], E) ->
|
||||
@@ -69,12 +88,11 @@ do_case(Meta, {Key, _}, _Acc, E) ->
|
||||
do_receive(_Meta, {'do', nil} = Do, Acc, _E) ->
|
||||
{Do, Acc};
|
||||
do_receive(Meta, {'do', _} = Do, Acc, E) ->
|
||||
expand_with_export(Meta, 'receive', expand_arg(Meta, 'receive', 'do'), Do, Acc, E);
|
||||
do_receive(_Meta, {'after', [{'->', Meta, [[Left], Right]}]}, Acc, E) ->
|
||||
{ELeft, EL} = elixir_exp:expand(Left, E),
|
||||
{ERight, ER} = elixir_exp:expand(Right, EL),
|
||||
EClause = {'after', [{'->', Meta, [[ELeft], ERight]}]},
|
||||
{EClause, elixir_env:merge_vars(Acc, ?m(ER, export_vars))};
|
||||
Fun = expand_one(Meta, 'receive', 'do', fun head/2),
|
||||
expand_with_export(Meta, 'receive', Fun, Do, Acc, E);
|
||||
do_receive(Meta, {'after', [_]} = After, Acc, E) ->
|
||||
Fun = expand_one(Meta, 'receive', 'after', fun elixir_exp:expand_args/2),
|
||||
expand_with_export(Meta, 'receive', Fun, After, Acc, E);
|
||||
do_receive(Meta, {'after', _}, _Acc, E) ->
|
||||
compile_error(Meta, ?m(E, file), "expected a single -> clause for after in receive");
|
||||
do_receive(Meta, {Key, _}, _Acc, E) ->
|
||||
@@ -96,14 +114,22 @@ do_try(_Meta, {'after', Expr}, E) ->
|
||||
{EExpr, _} = elixir_exp:expand(Expr, E),
|
||||
{'after', EExpr};
|
||||
do_try(Meta, {'else', _} = Else, E) ->
|
||||
expand_without_export(Meta, 'try', expand_arg(Meta, 'try', 'else'), Else, E);
|
||||
Fun = expand_one(Meta, 'try', 'else', fun head/2),
|
||||
expand_without_export(Meta, 'try', Fun, Else, E);
|
||||
do_try(Meta, {'catch', _} = Catch, E) ->
|
||||
expand_without_export(Meta, 'try', fun elixir_exp:expand_args/2, Catch, E);
|
||||
expand_without_export(Meta, 'try', fun expand_catch/3, Catch, E);
|
||||
do_try(Meta, {'rescue', _} = Rescue, E) ->
|
||||
expand_without_export(Meta, 'try', fun expand_rescue/3, Rescue, E);
|
||||
do_try(Meta, {Key, _}, E) ->
|
||||
compile_error(Meta, ?m(E, file), "unexpected keyword ~ts in try", [Key]).
|
||||
|
||||
expand_catch(_Meta, [_] = Args, E) ->
|
||||
head(Args, E);
|
||||
expand_catch(_Meta, [_, _] = Args, E) ->
|
||||
head(Args, E);
|
||||
expand_catch(Meta, _, E) ->
|
||||
compile_error(Meta, ?m(E, file), "expected one or two args for catch clauses (->) in try").
|
||||
|
||||
expand_rescue(Meta, [Arg], E) ->
|
||||
case expand_rescue(Arg, E) of
|
||||
{EArg, EA} ->
|
||||
@@ -121,14 +147,14 @@ expand_rescue({Name, _, Atom} = Var, E) when is_atom(Name), is_atom(Atom) ->
|
||||
|
||||
%% rescue var in [Exprs]
|
||||
expand_rescue({in, Meta, [Left, Right]}, E) ->
|
||||
{ERight, ER} = elixir_exp:expand(Right, E#{context := nil}),
|
||||
{ELeft, EL} = elixir_exp:expand(Left, ER#{context := match}),
|
||||
{ELeft, EL} = match(fun elixir_exp:expand/2, Left, E),
|
||||
{ERight, ER} = elixir_exp:expand(Right, EL),
|
||||
|
||||
case ELeft of
|
||||
{Name, _, Atom} when is_atom(Name), is_atom(Atom) ->
|
||||
case normalize_rescue(ERight) of
|
||||
false -> false;
|
||||
Other -> {{in, Meta, [ELeft, Other]}, EL}
|
||||
Other -> {{in, Meta, [ELeft, Other]}, ER}
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
@@ -145,34 +171,28 @@ normalize_rescue(Other) ->
|
||||
|
||||
%% Expansion helpers
|
||||
|
||||
export_vars({Left, Meta, Right}) when is_atom(Left), is_list(Meta), is_atom(Right) ->
|
||||
{Left, [{export,false}|Meta], Right};
|
||||
export_vars({Left, Meta, Right}) ->
|
||||
{export_vars(Left), Meta, export_vars(Right)};
|
||||
export_vars({Left, Right}) ->
|
||||
{export_vars(Left), export_vars(Right)};
|
||||
export_vars(List) when is_list(List) ->
|
||||
[export_vars(X) || X <- List];
|
||||
export_vars(Other) ->
|
||||
Other.
|
||||
|
||||
%% Returns a function that expands arguments
|
||||
%% considering we have at maximum one entry.
|
||||
expand_arg(Meta, Kind, Key) ->
|
||||
expand_one(Meta, Kind, Key, Fun) ->
|
||||
fun
|
||||
([Arg], E) ->
|
||||
{EArg, EA} = elixir_exp:expand(Arg, E),
|
||||
{[EArg], EA};
|
||||
([_] = Args, E) ->
|
||||
Fun(Args, E);
|
||||
(_, E) ->
|
||||
compile_error(Meta, ?m(E, file), "expected one arg for ~ts clauses (->) in ~ts", [Key, Kind])
|
||||
compile_error(Meta, ?m(E, file),
|
||||
"expected one arg for ~ts clauses (->) in ~ts", [Key, Kind])
|
||||
end.
|
||||
|
||||
%% Expands all -> pairs in a given key keeping the overall vars.
|
||||
expand_with_export(Meta, Kind, Fun, {Key, Clauses}, Acc, E) when is_list(Clauses) ->
|
||||
EFun =
|
||||
case lists:keyfind(export_head, 1, Meta) of
|
||||
{export_head, true} -> Fun;
|
||||
_ -> fun(ExportArgs, ExportE) -> Fun(export_vars(ExportArgs), ExportE) end
|
||||
{export_head, true} ->
|
||||
Fun;
|
||||
_ ->
|
||||
fun(Args, #{export_vars := ExportVars} = EE) ->
|
||||
{FArgs, FE} = Fun(Args, EE),
|
||||
{FArgs, FE#{export_vars := ExportVars}}
|
||||
end
|
||||
end,
|
||||
Transformer = fun(Clause, Vars) ->
|
||||
{EClause, EC} = clause(Meta, Kind, EFun, Clause, E),
|
||||
|
||||
@@ -30,7 +30,7 @@ translate_fn_match(Arg, S) ->
|
||||
|
||||
expand(Meta, Clauses, E) when is_list(Clauses) ->
|
||||
Transformer = fun(Clause) ->
|
||||
{EClause, _} = elixir_exp_clauses:clause(Meta, fn, fun elixir_exp:expand_args/2, Clause, E),
|
||||
{EClause, _} = elixir_exp_clauses:clause(Meta, fn, fun elixir_exp_clauses:head/2, Clause, E),
|
||||
EClause
|
||||
end,
|
||||
{{fn, Meta, lists:map(Transformer, Clauses)}, E}.
|
||||
|
||||
@@ -109,7 +109,7 @@ translate_filters(T, S) ->
|
||||
|
||||
translate_filter(Filter, S) ->
|
||||
{TFilter, TS} = elixir_translator:translate(Filter, S),
|
||||
case elixir_utils:returns_boolean(TFilter) of
|
||||
case elixir_utils:returns_boolean(Filter) of
|
||||
true ->
|
||||
{{nil, TFilter}, TS};
|
||||
false ->
|
||||
|
||||
@@ -205,6 +205,7 @@ special_form('unquote_splicing', 1) -> true;
|
||||
special_form('fn', _) -> true;
|
||||
special_form('super', _) -> true;
|
||||
special_form('for', _) -> true;
|
||||
special_form('cond', 1) -> true;
|
||||
special_form('case', 2) -> true;
|
||||
special_form('try', 2) -> true;
|
||||
special_form('receive', 1) -> true;
|
||||
|
||||
@@ -9,73 +9,53 @@ unescape_tokens/1, unescape_tokens/2, unescape_map/1]).
|
||||
extract(Line, Raw, Interpol, String, Last) ->
|
||||
%% Ignore whatever is in the scope and enable terminator checking.
|
||||
Scope = Raw#elixir_tokenizer{terminators=[], check_terminators=true},
|
||||
extract(Line, Scope, Interpol, String, [], 0, [], Last).
|
||||
extract(Line, Scope, Interpol, String, [], [], Last).
|
||||
|
||||
%% Terminators
|
||||
|
||||
extract(Line, _Scope, _Interpol, [], Buffer, 0, Output, []) ->
|
||||
extract(Line, _Scope, _Interpol, [], Buffer, Output, []) ->
|
||||
finish_extraction(Line, Buffer, Output, []);
|
||||
|
||||
extract(Line, _Scope, _Interpol, [], _Buffer, 0, _Output, Last) ->
|
||||
extract(Line, _Scope, _Interpol, [], _Buffer, _Output, Last) ->
|
||||
{error, {string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), []}};
|
||||
|
||||
extract(Line, _Scope, _Interpol, [Last|Remaining], Buffer, 0, Output, Last) ->
|
||||
extract(Line, _Scope, _Interpol, [Last|Remaining], Buffer, Output, Last) ->
|
||||
finish_extraction(Line, Buffer, Output, Remaining);
|
||||
|
||||
extract(Line, _Scope, _Interpol, [], _Buffer, _Search, _Output, Last) ->
|
||||
{error, {string, Line, io_lib:format("missing terminator: ~ts", [[Last]]), []}};
|
||||
|
||||
%% Going through the string
|
||||
|
||||
extract(Line, Scope, Interpol, [$\\, $\n|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line+1, Scope, Interpol, Rest, Buffer, Search, Output, Last);
|
||||
extract(Line, Scope, Interpol, [$\\, $\n|Rest], Buffer, Output, Last) ->
|
||||
extract(Line+1, Scope, Interpol, Rest, Buffer, Output, Last);
|
||||
|
||||
extract(Line, Scope, Interpol, [$\\, $\r, $\n|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line+1, Scope, Interpol, Rest, Buffer, Search, Output, Last);
|
||||
extract(Line, Scope, Interpol, [$\\, $\r, $\n|Rest], Buffer, Output, Last) ->
|
||||
extract(Line+1, Scope, Interpol, Rest, Buffer, Output, Last);
|
||||
|
||||
extract(Line, Scope, Interpol, [$\n|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line+1, Scope, Interpol, Rest, [$\n|Buffer], Search, Output, Last);
|
||||
extract(Line, Scope, Interpol, [$\n|Rest], Buffer, Output, Last) ->
|
||||
extract(Line+1, Scope, Interpol, Rest, [$\n|Buffer], Output, Last);
|
||||
|
||||
extract(Line, Scope, Interpol, [$\\, $#, ${|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [${,$#|Buffer], Search, Output, Last);
|
||||
extract(Line, Scope, Interpol, [$\\, $#, ${|Rest], Buffer, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [${,$#|Buffer], Output, Last);
|
||||
|
||||
extract(Line, Scope, Interpol, [$\\,Char|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [Char,$\\|Buffer], Search, Output, Last);
|
||||
extract(Line, Scope, Interpol, [$\\,Char|Rest], Buffer, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [Char,$\\|Buffer], Output, Last);
|
||||
|
||||
extract(Line, Scope, true, [$#, ${|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, Scope, true, [$#, ${|Rest], Buffer, Output, Last) ->
|
||||
Output1 = build_string(Line, Buffer, Output),
|
||||
|
||||
case elixir_tokenizer:tokenize(Rest, Line, Scope) of
|
||||
{error, {EndLine, _, "}"}, [$}|NewRest], Tokens} ->
|
||||
Output2 = build_interpol(Line, Tokens, Output1),
|
||||
extract(EndLine, Scope, true, NewRest, [], Search, Output2, Last);
|
||||
extract(EndLine, Scope, true, NewRest, [], Output2, Last);
|
||||
{error, Reason, _, _} ->
|
||||
{error, Reason};
|
||||
{ok, _EndLine, _} ->
|
||||
{error, {string, Line, "missing interpolation terminator:}", []}}
|
||||
end;
|
||||
|
||||
%% Matching () [] {} <> inside sigils
|
||||
|
||||
extract(Line, Scope, Interpol, [$(|Rest], Buffer, Search, Output, $)) ->
|
||||
extract(Line, Scope, Interpol, Rest, [$(|Buffer], Search + 1, Output, $));
|
||||
|
||||
extract(Line, Scope, Interpol, [$[|Rest], Buffer, Search, Output, $]) ->
|
||||
extract(Line, Scope, Interpol, Rest, [$[|Buffer], Search + 1, Output, $]);
|
||||
|
||||
extract(Line, Scope, Interpol, [${|Rest], Buffer, Search, Output, $}) ->
|
||||
extract(Line, Scope, Interpol, Rest, [${|Buffer], Search + 1, Output, $});
|
||||
|
||||
extract(Line, Scope, Interpol, [$<|Rest], Buffer, Search, Output, $>) ->
|
||||
extract(Line, Scope, Interpol, Rest, [$<|Buffer], Search + 1, Output, $>);
|
||||
|
||||
extract(Line, Scope, Interpol, [Last|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [Last|Buffer], Search - 1, Output, Last);
|
||||
|
||||
%% Catch all clause
|
||||
|
||||
extract(Line, Scope, Interpol, [Char|Rest], Buffer, Search, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [Char|Buffer], Search, Output, Last).
|
||||
extract(Line, Scope, Interpol, [Char|Rest], Buffer, Output, Last) ->
|
||||
extract(Line, Scope, Interpol, Rest, [Char|Buffer], Output, Last).
|
||||
|
||||
%% Unescape a series of tokens as returned by extract.
|
||||
|
||||
|
||||
@@ -55,8 +55,8 @@ do_compile(Line, Module, Block, Vars, E) ->
|
||||
|
||||
{All, Forms0} = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions),
|
||||
Forms1 = specs_form(Module, Private, Defmacro, Forms0),
|
||||
Forms2 = attributes_form(Line, File, Module, Forms1),
|
||||
Forms3 = typedocs_form(Module, Forms2),
|
||||
Forms2 = types_form(Module, Forms1),
|
||||
Forms3 = attributes_form(Line, File, Module, Forms2),
|
||||
|
||||
case ets:lookup(data_table(Module), 'on_load') of
|
||||
[] -> ok;
|
||||
@@ -111,7 +111,7 @@ build(Line, File, Module, Lexical) ->
|
||||
_ -> ets:insert(DataTable, {on_definition, []})
|
||||
end,
|
||||
|
||||
Attributes = [behaviour, on_load, spec, type, export_type, opaque, callback, compile],
|
||||
Attributes = [behaviour, on_load, spec, type, typep, opaque, callback, compile, external_resource],
|
||||
ets:insert(DataTable, {?acc_attr, [before_compile, after_compile, on_definition, derive|Attributes]}),
|
||||
ets:insert(DataTable, {?persisted_attr, [vsn|Attributes]}),
|
||||
ets:insert(DataTable, {?docs_attr, ets:new(DataTable, [ordered_set, public])}),
|
||||
@@ -156,7 +156,7 @@ functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, BaseFunct
|
||||
|
||||
%% Add attributes handling to the form
|
||||
|
||||
attributes_form(Line, _File, Module, Current) ->
|
||||
attributes_form(Line, File, Module, Current) ->
|
||||
Table = data_table(Module),
|
||||
|
||||
AccAttrs = ets:lookup_element(Table, '__acc_attributes', 2),
|
||||
@@ -166,69 +166,128 @@ attributes_form(Line, _File, Module, Current) ->
|
||||
case lists:member(Key, PersistedAttrs) of
|
||||
false -> Acc;
|
||||
true ->
|
||||
Attrs = case lists:member(Key, AccAttrs) of
|
||||
true -> Value;
|
||||
false -> [Value]
|
||||
end,
|
||||
lists:foldl(fun(X, Final) -> [{attribute, Line, Key, X}|Final] end, Acc, Attrs)
|
||||
Values =
|
||||
case lists:member(Key, AccAttrs) of
|
||||
true -> Value;
|
||||
false -> [Value]
|
||||
end,
|
||||
|
||||
lists:foldl(fun(X, Final) ->
|
||||
[{attribute, Line, Key, X}|Final]
|
||||
end, Acc, process_attribute(Line, File, Key, Values))
|
||||
end
|
||||
end,
|
||||
|
||||
ets:foldl(Transform, Current, Table).
|
||||
|
||||
%% Add typedocs to the form
|
||||
typedocs_form(Module, Current) ->
|
||||
Table = docs_table(Module),
|
||||
Transform = fun({Tuple, Line, Kind, _Sig, Doc}, Acc) ->
|
||||
case Kind of
|
||||
type -> [{attribute, Line, typedoc, {Tuple, Doc}} | Acc];
|
||||
opaque -> [{attribute, Line, typedoc, {Tuple, Doc}} | Acc];
|
||||
_ -> Acc
|
||||
end
|
||||
process_attribute(Line, File, external_resource, Values) ->
|
||||
lists:usort([process_external_resource(Line, File, Value) || Value <- Values]);
|
||||
process_attribute(_Line, _File, _Key, Values) ->
|
||||
Values.
|
||||
|
||||
process_external_resource(_Line, _File, Value) when is_binary(Value) ->
|
||||
Value;
|
||||
process_external_resource(Line, File, Value) ->
|
||||
elixir_errors:handle_file_error(File,
|
||||
{Line, ?MODULE, {invalid_external_resource, Value}}).
|
||||
|
||||
%% Types
|
||||
|
||||
types_form(Module, Forms0) ->
|
||||
case code:ensure_loaded('Elixir.Kernel.Typespec') of
|
||||
{module, 'Elixir.Kernel.Typespec'} ->
|
||||
Types0 = 'Elixir.Module':get_attribute(Module, type) ++
|
||||
'Elixir.Module':get_attribute(Module, typep) ++
|
||||
'Elixir.Module':get_attribute(Module, opaque),
|
||||
|
||||
Types1 = ['Elixir.Kernel.Typespec':translate_type(Kind, Expr, Doc, Caller) ||
|
||||
{Kind, Expr, Doc, Caller} <- Types0],
|
||||
|
||||
'Elixir.Module':delete_attribute(Module, type),
|
||||
'Elixir.Module':delete_attribute(Module, typep),
|
||||
'Elixir.Module':delete_attribute(Module, opaque),
|
||||
|
||||
Forms1 = types_attributes(Types1, Forms0),
|
||||
Forms2 = export_types_attributes(Types1, Forms1),
|
||||
typedocs_attributes(Types1, Forms2);
|
||||
|
||||
{error, _} ->
|
||||
Forms0
|
||||
end.
|
||||
|
||||
types_attributes(Types, Forms) ->
|
||||
Fun = fun({{Kind, _NameArity, Expr}, Line, _Export, _Doc}, Acc) ->
|
||||
[{attribute, Line, Kind, Expr}|Acc]
|
||||
end,
|
||||
ets:foldl(Transform, Current, Table).
|
||||
lists:foldl(Fun, Forms, Types).
|
||||
|
||||
export_types_attributes(Types, Forms) ->
|
||||
Fun = fun
|
||||
({{_Kind, NameArity, _Expr}, Line, true, _Doc}, Acc) ->
|
||||
[{attribute, Line, export_type, [NameArity]}|Acc];
|
||||
({_Type, _Line, false, _Doc}, Acc) ->
|
||||
Acc
|
||||
end,
|
||||
lists:foldl(Fun, Forms, Types).
|
||||
|
||||
typedocs_attributes(Types, Forms) ->
|
||||
Fun = fun
|
||||
({{_Kind, NameArity, _Expr}, Line, true, Doc}, Acc) when Doc =/= nil ->
|
||||
[{attribute, Line, typedoc, {NameArity, Doc}}|Acc];
|
||||
({_Type, _Line, _Export, _Doc}, Acc) ->
|
||||
Acc
|
||||
end,
|
||||
lists:foldl(Fun, Forms, Types).
|
||||
|
||||
%% Specs
|
||||
|
||||
specs_form(Module, Private, Defmacro, Forms) ->
|
||||
Defmacrop = [Tuple || {Tuple, defmacrop, _, _, _} <- Private],
|
||||
case code:ensure_loaded('Elixir.Kernel.Typespec') of
|
||||
{module, 'Elixir.Kernel.Typespec'} ->
|
||||
Callbacks = 'Elixir.Module':get_attribute(Module, callback),
|
||||
Specs = [translate_spec(Spec, Defmacro, Defmacrop) ||
|
||||
Spec <- 'Elixir.Module':get_attribute(Module, spec)],
|
||||
Defmacrop = [Tuple || {Tuple, defmacrop, _, _, _} <- Private],
|
||||
|
||||
Specs0 = 'Elixir.Module':get_attribute(Module, spec) ++
|
||||
'Elixir.Module':get_attribute(Module, callback),
|
||||
|
||||
Specs1 = ['Elixir.Kernel.Typespec':translate_spec(Kind, Expr, Caller) ||
|
||||
{Kind, Expr, Caller} <- Specs0],
|
||||
Specs2 = lists:flatmap(fun(Spec) ->
|
||||
translate_macro_spec(Spec, Defmacro, Defmacrop)
|
||||
end, Specs1),
|
||||
|
||||
'Elixir.Module':delete_attribute(Module, spec),
|
||||
'Elixir.Module':delete_attribute(Module, callback),
|
||||
specs_attributes(Forms, Specs2);
|
||||
|
||||
Temp = specs_attributes(spec, Forms, Specs),
|
||||
specs_attributes(callback, Temp, Callbacks);
|
||||
{error, _} ->
|
||||
Forms
|
||||
end.
|
||||
|
||||
specs_attributes(Type, Forms, Specs) ->
|
||||
Keys = lists:foldl(fun({Tuple, Value}, Acc) ->
|
||||
lists:keystore(Tuple, 1, Acc, {Tuple, Value})
|
||||
end, [], Specs),
|
||||
lists:foldl(fun({Tuple, _}, Acc) ->
|
||||
Values = [V || {K, V} <- Specs, K == Tuple],
|
||||
{type, Line, _, _} = hd(Values),
|
||||
[{attribute, Line, Type, {Tuple, Values}}|Acc]
|
||||
end, Forms, Keys).
|
||||
specs_attributes(Forms, Specs) ->
|
||||
Dict = lists:foldl(fun({{Kind, NameArity, Spec}, Line}, Acc) ->
|
||||
dict:append({Kind, NameArity}, {Spec, Line}, Acc)
|
||||
end, dict:new(), Specs),
|
||||
dict:fold(fun({Kind, NameArity}, ExprsLines, Acc) ->
|
||||
{Exprs, Lines} = lists:unzip(ExprsLines),
|
||||
Line = lists:min(Lines),
|
||||
[{attribute, Line, Kind, {NameArity, Exprs}}|Acc]
|
||||
end, Forms, Dict).
|
||||
|
||||
translate_spec({Spec, Rest}, Defmacro, Defmacrop) ->
|
||||
case ordsets:is_element(Spec, Defmacrop) of
|
||||
true -> {Spec, Rest};
|
||||
translate_macro_spec({{spec, NameArity, Spec}, Line}, Defmacro, Defmacrop) ->
|
||||
case ordsets:is_element(NameArity, Defmacrop) of
|
||||
true -> [];
|
||||
false ->
|
||||
case ordsets:is_element(Spec, Defmacro) of
|
||||
case ordsets:is_element(NameArity, Defmacro) of
|
||||
true ->
|
||||
{Name, Arity} = Spec,
|
||||
{{elixir_utils:macro_name(Name), Arity + 1}, spec_for_macro(Rest)};
|
||||
{Name, Arity} = NameArity,
|
||||
[{{spec, {elixir_utils:macro_name(Name), Arity + 1}, spec_for_macro(Spec)}, Line}];
|
||||
false ->
|
||||
{Spec, Rest}
|
||||
[{{spec, NameArity, Spec}, Line}]
|
||||
end
|
||||
end.
|
||||
end;
|
||||
|
||||
translate_macro_spec({{callback, NameArity, Spec}, Line}, _Defmacro, _Defmacrop) ->
|
||||
[{{callback, NameArity, Spec}, Line}].
|
||||
|
||||
spec_for_macro({type, Line, 'fun', [{type, _, product, Args}|T]}) ->
|
||||
NewArgs = [{type, Line, term, []}|Args],
|
||||
@@ -419,6 +478,9 @@ prune_stacktrace(Info, []) ->
|
||||
|
||||
format_error({invalid_clause, {Name, Arity}}) ->
|
||||
io_lib:format("empty clause provided for nonexistent function or macro ~ts/~B", [Name, Arity]);
|
||||
format_error({invalid_external_resource, Value}) ->
|
||||
io_lib:format("expected a string value for @external_resource, got: ~p",
|
||||
['Elixir.Kernel':inspect(Value)]);
|
||||
format_error({unused_doc, typedoc}) ->
|
||||
"@typedoc provided but no type follows it";
|
||||
format_error({unused_doc, doc}) ->
|
||||
@@ -426,7 +488,7 @@ format_error({unused_doc, doc}) ->
|
||||
format_error({internal_function_overridden, {Name, Arity}}) ->
|
||||
io_lib:format("function ~ts/~B is internal and should not be overridden", [Name, Arity]);
|
||||
format_error({invalid_module, Module}) ->
|
||||
io_lib:format("invalid module name: ~p", [Module]);
|
||||
io_lib:format("invalid module name: ~ts", ['Elixir.Kernel':inspect(Module)]);
|
||||
format_error({module_defined, Module}) ->
|
||||
io_lib:format("redefining module ~ts", [elixir_aliases:inspect(Module)]);
|
||||
format_error({module_reserved, Module}) ->
|
||||
|
||||
@@ -3,7 +3,7 @@ Nonterminals
|
||||
expr container_expr block_expr no_parens_expr no_parens_one_expr access_expr
|
||||
bracket_expr bracket_at_expr bracket_arg matched_expr unmatched_expr max_expr
|
||||
op_expr matched_op_expr no_parens_op_expr
|
||||
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol
|
||||
comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol capture_op_eol
|
||||
add_op_eol mult_op_eol exp_op_eol two_op_eol pipe_op_eol stab_op_eol
|
||||
arrow_op_eol match_op_eol when_op_eol in_op_eol in_match_op_eol type_op_eol
|
||||
open_paren close_paren empty_paren
|
||||
@@ -29,7 +29,7 @@ Terminals
|
||||
number signed_number atom atom_safe atom_unsafe bin_string list_string sigil
|
||||
dot_call_op op_identifier
|
||||
comp_op at_op unary_op and_op or_op arrow_op match_op in_op in_match_op type_op
|
||||
dual_op add_op mult_op exp_op two_op pipe_op stab_op when_op assoc_op
|
||||
dual_op add_op mult_op exp_op two_op pipe_op stab_op when_op assoc_op capture_op
|
||||
'true' 'false' 'nil' 'do' eol ',' '.'
|
||||
'(' ')' '[' ']' '{' '}' '<<' '>>' '%{}' '%'
|
||||
.
|
||||
@@ -45,6 +45,7 @@ Expect 2.
|
||||
Left 5 do.
|
||||
Right 10 stab_op_eol. %% ->
|
||||
Left 20 ','.
|
||||
Nonassoc 30 capture_op_eol. %% &
|
||||
Left 40 in_match_op_eol. %% <-, \\ (allowed in matches along =)
|
||||
Right 50 when_op_eol. %% when
|
||||
Right 60 type_op_eol. %% ::
|
||||
@@ -60,7 +61,7 @@ Right 200 two_op_eol. %% ++, --, .., <>
|
||||
Left 210 add_op_eol. %% + (op), - (op)
|
||||
Left 220 mult_op_eol. %% * (op), / (op)
|
||||
Left 250 exp_op_eol. %% ^ (op) (e.g ^^^)
|
||||
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, &, ~~~
|
||||
Nonassoc 300 unary_op_eol. %% +, -, !, ^, not, ~~~
|
||||
Left 310 dot_call_op.
|
||||
Left 310 dot_op. %% .
|
||||
Nonassoc 320 at_op_eol. %% @
|
||||
@@ -112,6 +113,8 @@ matched_expr -> unary_op_eol matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> unary_op_eol no_parens_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> at_op_eol matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> at_op_eol no_parens_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> capture_op_eol matched_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> capture_op_eol no_parens_expr : build_unary_op('$1', '$2').
|
||||
matched_expr -> no_parens_one_expr : '$1'.
|
||||
matched_expr -> access_expr : '$1'.
|
||||
|
||||
@@ -123,6 +126,7 @@ unmatched_expr -> matched_expr op_expr : build_op(element(1, '$2'), '$1', elemen
|
||||
unmatched_expr -> unmatched_expr op_expr : build_op(element(1, '$2'), '$1', element(2, '$2')).
|
||||
unmatched_expr -> unary_op_eol expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> at_op_eol expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> capture_op_eol expr : build_unary_op('$1', '$2').
|
||||
unmatched_expr -> block_expr : '$1'.
|
||||
|
||||
block_expr -> parens_call call_args_parens do_block : build_identifier('$1', '$2' ++ '$3').
|
||||
@@ -189,7 +193,9 @@ no_parens_one_expr -> dot_identifier : build_identifier('$1', nil).
|
||||
%% marks identifiers followed by brackets as bracket_identifier.
|
||||
access_expr -> bracket_at_expr : '$1'.
|
||||
access_expr -> bracket_expr : '$1'.
|
||||
access_expr -> at_op_eol number : build_unary_op('$1', ?exprs('$2')).
|
||||
access_expr -> unary_op_eol number : build_unary_op('$1', ?exprs('$2')).
|
||||
access_expr -> capture_op_eol number : build_unary_op('$1', ?exprs('$2')).
|
||||
access_expr -> fn_eol stab end_eol : build_fn('$1', build_stab(reverse('$2'))).
|
||||
access_expr -> open_paren stab close_paren : build_stab(reverse('$2')).
|
||||
access_expr -> number : ?exprs('$1').
|
||||
@@ -313,6 +319,9 @@ two_op_eol -> two_op eol : '$1'.
|
||||
pipe_op_eol -> pipe_op : '$1'.
|
||||
pipe_op_eol -> pipe_op eol : '$1'.
|
||||
|
||||
capture_op_eol -> capture_op : '$1'.
|
||||
capture_op_eol -> capture_op eol : '$1'.
|
||||
|
||||
unary_op_eol -> unary_op : '$1'.
|
||||
unary_op_eol -> unary_op eol : '$1'.
|
||||
unary_op_eol -> dual_op : '$1'.
|
||||
@@ -532,12 +541,6 @@ meta(Node) -> meta(?line(Node)).
|
||||
|
||||
%% Operators
|
||||
|
||||
build_op({_Kind, Line, '/'}, {'&', _, [{Kind, _, Atom} = Left]}, Right) when is_number(Right), is_atom(Atom), is_atom(Kind) ->
|
||||
{'&', meta(Line), [{'/', meta(Line), [Left, Right]}]};
|
||||
|
||||
build_op({_Kind, Line, '/'}, {'&', _, [{{'.', _, [_, _]}, _, []} = Left]}, Right) when is_number(Right) ->
|
||||
{'&', meta(Line), [{'/', meta(Line), [Left, Right]}]};
|
||||
|
||||
build_op({_Kind, Line, 'in'}, {UOp, _, [Left]}, Right) when ?rearrange_uop(UOp) ->
|
||||
{UOp, meta(Line), [{'in', meta(Line), [Left, Right]}]};
|
||||
|
||||
|
||||
@@ -27,29 +27,27 @@ translate_var(Meta, Name, Kind, S) when is_atom(Kind); is_integer(Kind) ->
|
||||
true ->
|
||||
{{var, Line, Current}, S};
|
||||
false ->
|
||||
%% If the variable is not exported, we use a counter name.
|
||||
%% The same if the variable already exists or we are in a
|
||||
%% noname context.
|
||||
Private = (lists:keyfind(export, 1, Meta) == {export, false}),
|
||||
|
||||
%% We attempt to give vars a nice name because we
|
||||
%% still use the unused vars warnings from erl_lint.
|
||||
%%
|
||||
%% Once we move the warning to Elixir compiler, we
|
||||
%% can name vars as _@COUNTER.
|
||||
{NewVar, Counter, NS} =
|
||||
if
|
||||
Kind /= nil ->
|
||||
build_var('_', S);
|
||||
Private orelse Exists orelse S#elixir_scope.noname ->
|
||||
build_var(Name, S);
|
||||
true ->
|
||||
{Name, 0, S}
|
||||
build_var(Name, S)
|
||||
end,
|
||||
|
||||
FS = NS#elixir_scope{
|
||||
vars=orddict:store(Tuple, {NewVar, Counter}, Vars),
|
||||
match_vars=ordsets:add_element(Tuple, MatchVars),
|
||||
export_vars=case S#elixir_scope.export_vars of
|
||||
EV when Private; EV == nil -> EV;
|
||||
EV -> orddict:store(Tuple, {NewVar, Counter}, EV)
|
||||
nil -> nil;
|
||||
EV -> orddict:store(Tuple, {NewVar, Counter}, EV)
|
||||
end
|
||||
},
|
||||
},
|
||||
|
||||
{{var, Line, NewVar}, FS}
|
||||
end;
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
-define(at_op(T),
|
||||
T == $@).
|
||||
|
||||
-define(capture_op(T),
|
||||
T == $&).
|
||||
|
||||
-define(unary_op(T),
|
||||
T == $&;
|
||||
T == $!;
|
||||
T == $^).
|
||||
|
||||
@@ -294,7 +296,7 @@ tokenize([$:,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
|
||||
% ## Single Token Operators
|
||||
tokenize([$:,T|Rest], Line, Scope, Tokens) when
|
||||
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?at_op(T); ?unary_op(T); ?capture_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?match_op(T); ?pipe_op(T); T == $. ->
|
||||
tokenize(Rest, Line, Scope, [{atom, Line, list_to_atom([T])}|Tokens]);
|
||||
|
||||
@@ -386,6 +388,9 @@ tokenize([T1,T2|Rest], Line, Scope, Tokens) when ?stab_op(T1, T2) ->
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?at_op(T) ->
|
||||
handle_unary_op(Rest, Line, at_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?capture_op(T) ->
|
||||
handle_unary_op(Rest, Line, capture_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
tokenize([T|Rest], Line, Scope, Tokens) when ?unary_op(T) ->
|
||||
handle_unary_op(Rest, Line, unary_op, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
@@ -549,7 +554,7 @@ handle_dot([$.,T1,T2|Rest], Line, Scope, Tokens) when
|
||||
|
||||
% ## Single Token Operators
|
||||
handle_dot([$.,T|Rest], Line, Scope, Tokens) when
|
||||
?at_op(T); ?unary_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?at_op(T); ?unary_op(T); ?capture_op(T); ?dual_op(T); ?mult_op(T); ?comp_op(T);
|
||||
?match_op(T); ?pipe_op(T); T == $% ->
|
||||
handle_call_identifier(Rest, Line, list_to_atom([T]), Scope, Tokens);
|
||||
|
||||
|
||||
@@ -70,24 +70,38 @@ translate({'&', Meta, [Arg]}, S) when is_integer(Arg) ->
|
||||
translate({fn, Meta, Clauses}, S) ->
|
||||
elixir_fn:translate(Meta, Clauses, S);
|
||||
|
||||
%% Cond
|
||||
|
||||
translate({'cond', _Meta, [[{do, Pairs}]]}, S) ->
|
||||
[{'->', Meta, [[Condition], Body]}|T] = lists:reverse(Pairs),
|
||||
Case =
|
||||
case Condition of
|
||||
{'_', _, Atom} when is_atom(Atom) ->
|
||||
compile_error(Meta, S#elixir_scope.file, "unbound variable _ inside cond. "
|
||||
"If you want the last clause to always match, you probably meant to use: true ->");
|
||||
X when is_atom(X) and (X /= false) and (X /= nil) ->
|
||||
build_cond_clauses(T, Body, Meta);
|
||||
_ ->
|
||||
{Truthy, Other} = build_truthy_clause(Meta, Condition, Body),
|
||||
Error = {{'.', Meta, [erlang, error]}, [], [cond_clause]},
|
||||
Falsy = {'->', Meta, [[Other], Error]},
|
||||
Acc = {'case', Meta, [Condition, [{do, [Truthy, Falsy]}]]},
|
||||
build_cond_clauses(T, Acc, Meta)
|
||||
end,
|
||||
translate(Case, S);
|
||||
|
||||
%% Case
|
||||
|
||||
translate({'case', Meta, [Expr, KV]}, #elixir_scope{return=Return} = RS) when is_list(KV) ->
|
||||
S = RS#elixir_scope{noname=true, return=true},
|
||||
Clauses = elixir_clauses:get_pairs(do, KV),
|
||||
translate({'case', Meta, [Expr, KV]}, #elixir_scope{return=Return} = RS) ->
|
||||
S = RS#elixir_scope{return=true},
|
||||
Clauses = elixir_clauses:get_pairs(do, KV, match),
|
||||
{TExpr, NS} = translate(Expr, S),
|
||||
|
||||
RClauses = case elixir_utils:returns_boolean(TExpr) of
|
||||
true -> rewrite_case_clauses(Clauses);
|
||||
false -> Clauses
|
||||
end,
|
||||
|
||||
{TClauses, TS} = elixir_clauses:clauses(Meta, RClauses, Return, NS),
|
||||
{TClauses, TS} = elixir_clauses:clauses(Meta, Clauses, Return, NS),
|
||||
{{'case', ?line(Meta), TExpr, TClauses}, TS};
|
||||
|
||||
%% Try
|
||||
|
||||
translate({'try', Meta, [Clauses]}, #elixir_scope{return=Return} = RS) when is_list(Clauses) ->
|
||||
translate({'try', Meta, [Clauses]}, #elixir_scope{return=Return} = RS) ->
|
||||
S = RS#elixir_scope{noname=true, return=true},
|
||||
Do = proplists:get_value('do', Clauses, nil),
|
||||
{TDo, SB} = elixir_translator:translate(Do, S),
|
||||
@@ -103,7 +117,7 @@ translate({'try', Meta, [Clauses]}, #elixir_scope{return=Return} = RS) when is_l
|
||||
{TAfter, SA} = {[], mergec(S, SC)}
|
||||
end,
|
||||
|
||||
Else = elixir_clauses:get_pairs(else, Clauses),
|
||||
Else = elixir_clauses:get_pairs(else, Clauses, match),
|
||||
{TElse, SE} = elixir_clauses:clauses(Meta, Else, Return, mergec(S, SA)),
|
||||
|
||||
SF = (mergec(S, SE))#elixir_scope{noname=RS#elixir_scope.noname},
|
||||
@@ -111,16 +125,16 @@ translate({'try', Meta, [Clauses]}, #elixir_scope{return=Return} = RS) when is_l
|
||||
|
||||
%% Receive
|
||||
|
||||
translate({'receive', Meta, [KV]}, #elixir_scope{return=Return} = RS) when is_list(KV) ->
|
||||
translate({'receive', Meta, [KV]}, #elixir_scope{return=Return} = RS) ->
|
||||
S = RS#elixir_scope{return=true},
|
||||
Do = elixir_clauses:get_pairs(do, KV, true),
|
||||
Do = elixir_clauses:get_pairs(do, KV, match, true),
|
||||
|
||||
case lists:keyfind('after', 1, KV) of
|
||||
false ->
|
||||
{TClauses, SC} = elixir_clauses:clauses(Meta, Do, Return, S),
|
||||
{{'receive', ?line(Meta), TClauses}, SC};
|
||||
_ ->
|
||||
After = elixir_clauses:get_pairs('after', KV),
|
||||
After = elixir_clauses:get_pairs('after', KV, expr),
|
||||
{TClauses, SC} = elixir_clauses:clauses(Meta, Do ++ After, Return, S),
|
||||
{FClauses, TAfter} = elixir_utils:split_last(TClauses),
|
||||
{_, _, [FExpr], _, FAfter} = TAfter,
|
||||
@@ -154,7 +168,7 @@ translate({super, Meta, Args}, S) when is_list(Args) ->
|
||||
|
||||
%% Variables
|
||||
|
||||
translate({'^', Meta, [ {Name, VarMeta, Kind} ]}, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
|
||||
translate({'^', Meta, [{Name, VarMeta, Kind}]}, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
|
||||
Tuple = {Name, var_kind(VarMeta, Kind)},
|
||||
case orddict:find(Tuple, S#elixir_scope.backup_vars) of
|
||||
{ok, {Value, _Counter}} ->
|
||||
@@ -316,24 +330,9 @@ var_kind(Meta, Kind) ->
|
||||
false -> Kind
|
||||
end.
|
||||
|
||||
%% Case
|
||||
|
||||
rewrite_case_clauses([
|
||||
{do,Meta1,[{'when',_,[{V,M,C},{'__op__',_,['orelse',_,_]}]}],False},
|
||||
{do,Meta2,[{'_',_,UC}],True}] = Clauses)
|
||||
when is_atom(V), is_list(M), is_atom(C), is_atom(UC) ->
|
||||
case lists:keyfind('cond', 1, M) of
|
||||
{'cond',true} ->
|
||||
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
|
||||
_ ->
|
||||
Clauses
|
||||
end;
|
||||
rewrite_case_clauses(Clauses) ->
|
||||
Clauses.
|
||||
|
||||
%% Pack a list of expressions from a block.
|
||||
unblock({'block', _, Exprs}) -> Exprs;
|
||||
unblock(Expr) -> [Expr].
|
||||
unblock(Expr) -> [Expr].
|
||||
|
||||
%% Translate args
|
||||
|
||||
@@ -367,15 +366,43 @@ translate_block(Expr, Return, S) ->
|
||||
false -> translate(Expr, S)
|
||||
end.
|
||||
|
||||
%% Expressions that can handle no return may receive
|
||||
%% return=false but must always return return=true.
|
||||
%% return is typically true, except when we find one
|
||||
%% of the expressions below, which may handle return=false
|
||||
%% but must always return return=true.
|
||||
handles_no_return({'try', _, [_]}) -> true;
|
||||
handles_no_return({'cond', _, [_]}) -> true;
|
||||
handles_no_return({'for', _, [_|_]}) -> true;
|
||||
handles_no_return({'case', _, [_, _]}) -> true;
|
||||
handles_no_return({'receive', _, [_]}) -> true;
|
||||
handles_no_return({'__block__', _, [_|_]}) -> true;
|
||||
handles_no_return(_) -> false.
|
||||
|
||||
%% Cond
|
||||
|
||||
build_cond_clauses([{'->', NewMeta, [[Condition], Body]}|T], Acc, OldMeta) ->
|
||||
{Truthy, Other} = build_truthy_clause(NewMeta, Condition, Body),
|
||||
Falsy = {'->', OldMeta, [[Other], Acc]},
|
||||
Case = {'case', NewMeta, [Condition, [{do, [Truthy, Falsy]}]]},
|
||||
build_cond_clauses(T, Case, NewMeta);
|
||||
build_cond_clauses([], Acc, _) ->
|
||||
Acc.
|
||||
|
||||
build_truthy_clause(Meta, Condition, Body) ->
|
||||
case elixir_utils:returns_boolean(Condition) of
|
||||
true ->
|
||||
{{'->', Meta, [[true], Body]}, false};
|
||||
false ->
|
||||
Var = {'cond', [], 'Elixir'},
|
||||
Head = {'when', [], [Var,
|
||||
{'__op__', [], [
|
||||
'andalso',
|
||||
{{'.', [], [erlang, '/=']}, [], [Var, nil]},
|
||||
{{'.', [], [erlang, '/=']}, [], [Var, false]}
|
||||
]}
|
||||
]},
|
||||
{{'->', Meta, [[Head], Body]}, {'_', [], nil}}
|
||||
end.
|
||||
|
||||
%% Assertions
|
||||
|
||||
assert_module_scope(Meta, Kind, #elixir_scope{module=nil,file=File}) ->
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
-include("elixir.hrl").
|
||||
|
||||
clauses(_Meta, Clauses, Return, S) ->
|
||||
Catch = elixir_clauses:get_pairs('catch', Clauses),
|
||||
Rescue = elixir_clauses:get_pairs(rescue, Clauses),
|
||||
Catch = elixir_clauses:get_pairs('catch', Clauses, 'catch'),
|
||||
Rescue = elixir_clauses:get_pairs(rescue, Clauses, rescue),
|
||||
reduce_clauses(Rescue ++ Catch, [], S, Return, S).
|
||||
|
||||
reduce_clauses([H|T], Acc, SAcc, Return, S) ->
|
||||
@@ -18,10 +18,7 @@ each_clause({'catch', Meta, Raw, Expr}, Return, S) ->
|
||||
|
||||
Final = case Args of
|
||||
[X] -> [throw, X, {'_', Meta, nil}];
|
||||
[X,Y] -> [X, Y, {'_', Meta, nil}];
|
||||
_ ->
|
||||
elixir_errors:compile_error(Meta, S#elixir_scope.file,
|
||||
"too many arguments given for catch")
|
||||
[X,Y] -> [X, Y, {'_', Meta, nil}]
|
||||
end,
|
||||
|
||||
Condition = [{'{}', Meta, Final}],
|
||||
@@ -103,27 +100,17 @@ rescue_guards(Meta, Var, Aliases, S) ->
|
||||
%% Matching of variables is done with Erlang exceptions is done in
|
||||
%% function for optimization.
|
||||
|
||||
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, S) when
|
||||
H == 'Elixir.UndefinedFunctionError'; H == 'Elixir.ErlangError';
|
||||
H == 'Elixir.ArgumentError'; H == 'Elixir.ArithmeticError';
|
||||
H == 'Elixir.BadArityError'; H == 'Elixir.BadFunctionError';
|
||||
H == 'Elixir.MatchError'; H == 'Elixir.CaseClauseError';
|
||||
H == 'Elixir.TryClauseError'; H == 'Elixir.FunctionClauseError';
|
||||
H == 'Elixir.SystemLimitError'; H == 'Elixir.BadStructError' ->
|
||||
Expr = erl_rescue_guard_for(Meta, Var, H),
|
||||
rescue_each_ref(Meta, Var, T, [H|Elixir], [Expr|Erlang], S);
|
||||
|
||||
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, S) when is_atom(H) ->
|
||||
rescue_each_ref(Meta, Var, T, [H|Elixir], Erlang, S);
|
||||
case erl_rescue_guard_for(Meta, Var, H) of
|
||||
false -> rescue_each_ref(Meta, Var, T, [H|Elixir], Erlang, S);
|
||||
Expr -> rescue_each_ref(Meta, Var, T, [H|Elixir], [Expr|Erlang], S)
|
||||
end;
|
||||
|
||||
rescue_each_ref(_, _, [], Elixir, Erlang, _) ->
|
||||
{Elixir, Erlang}.
|
||||
|
||||
%% Handle erlang rescue matches.
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, List) when is_list(List) ->
|
||||
join(Meta, fun erl_or/3, [erl_rescue_guard_for(Meta, Var, X) || X <- List]);
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.UndefinedFunctionError') ->
|
||||
{erl(Meta, '=='), Meta, [Var, undef]};
|
||||
|
||||
@@ -136,6 +123,9 @@ erl_rescue_guard_for(Meta, Var, 'Elixir.SystemLimitError') ->
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.ArithmeticError') ->
|
||||
{erl(Meta, '=='), Meta, [Var, badarith]};
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.CondClauseError') ->
|
||||
{erl(Meta, '=='), Meta, [Var, cond_clause]};
|
||||
|
||||
erl_rescue_guard_for(Meta, Var, 'Elixir.BadArityError') ->
|
||||
erl_and(Meta,
|
||||
erl_tuple_size(Meta, Var, 2),
|
||||
@@ -178,7 +168,10 @@ erl_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
|
||||
IsException = {erl(Meta, '/='), Meta, [
|
||||
{erl(Meta, element), Meta, [2, Var]}, '__exception__'
|
||||
]},
|
||||
erl_or(Meta, IsNotTuple, IsException).
|
||||
erl_or(Meta, IsNotTuple, IsException);
|
||||
|
||||
erl_rescue_guard_for(_, _, _) ->
|
||||
false.
|
||||
|
||||
%% Helpers
|
||||
|
||||
@@ -191,9 +184,6 @@ erl_record_compare(Meta, Var, Expr) ->
|
||||
Expr
|
||||
]}.
|
||||
|
||||
join(Meta, Kind, [H|T]) ->
|
||||
lists:foldl(fun(X, Acc) -> Kind(Meta, Acc, X) end, H, T).
|
||||
|
||||
prepend_to_block(_Meta, Expr, {'__block__', Meta, Args}) ->
|
||||
{'__block__', Meta, [Expr|Args]};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
-export([elixir_to_erl/1, get_line/1, split_last/1,
|
||||
characters_to_list/1, characters_to_binary/1, macro_name/1,
|
||||
convert_to_boolean/4, returns_boolean/1, atom_concat/1,
|
||||
file_type/1, file_type/2, relative_to_cwd/1, erl_call/4]).
|
||||
read_file_type/1, read_link_type/1, relative_to_cwd/1, erl_call/4]).
|
||||
-include("elixir.hrl").
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
@@ -31,11 +31,14 @@ split_last(List) -> split_last(List, []).
|
||||
split_last([H], Acc) -> {lists:reverse(Acc), H};
|
||||
split_last([H|T], Acc) -> split_last(T, [H|Acc]).
|
||||
|
||||
file_type(File) ->
|
||||
file_type(File, read_link_info).
|
||||
read_file_type(File) ->
|
||||
case file:read_file_info(File) of
|
||||
{ok, #file_info{type=Type}} -> {ok, Type};
|
||||
{error, _} = Error -> Error
|
||||
end.
|
||||
|
||||
file_type(File, Op) ->
|
||||
case file:Op(File) of
|
||||
read_link_type(File) ->
|
||||
case file:read_link_info(File) of
|
||||
{ok, #file_info{type=Type}} -> {ok, Type};
|
||||
{error, _} = Error -> Error
|
||||
end.
|
||||
@@ -126,36 +129,43 @@ elixir_to_erl_cons_2([], Acc) ->
|
||||
|
||||
%% Boolean checks
|
||||
|
||||
returns_boolean({op, _, Op, _}) when Op == 'not' -> true;
|
||||
returns_boolean(Bool) when is_boolean(Bool) -> true;
|
||||
|
||||
returns_boolean({op, _, Op, _, _}) when
|
||||
returns_boolean({{'.', _, [erlang, Op]}, _, [_]}) when Op == 'not' -> true;
|
||||
|
||||
returns_boolean({{'.', _, [erlang, Op]}, _, [_, _]}) when
|
||||
Op == 'and'; Op == 'or'; Op == 'xor';
|
||||
Op == '=='; Op == '/='; Op == '=<'; Op == '>=';
|
||||
Op == '<'; Op == '>'; Op == '=:='; Op == '=/=' -> true;
|
||||
|
||||
returns_boolean({op, _, Op, _, Right}) when Op == 'andalso'; Op == 'orelse' ->
|
||||
returns_boolean({'__op__', _, [Op, _, Right]}) when Op == 'andalso'; Op == 'orelse' ->
|
||||
returns_boolean(Right);
|
||||
|
||||
returns_boolean({call, _, {remote, _, {atom, _, erlang}, {atom, _, Fun}}, [_]}) when
|
||||
returns_boolean({{'.', _, [erlang, Fun]}, _, [_]}) when
|
||||
Fun == is_atom; Fun == is_binary; Fun == is_bitstring; Fun == is_boolean;
|
||||
Fun == is_float; Fun == is_function; Fun == is_integer; Fun == is_list;
|
||||
Fun == is_number; Fun == is_pid; Fun == is_port; Fun == is_reference;
|
||||
Fun == is_tuple -> true;
|
||||
|
||||
returns_boolean({call, _, {remote, _, {atom, _, erlang}, {atom, _, Fun}}, [_,_]}) when
|
||||
returns_boolean({{'.', _, [erlang, Fun]}, _, [_, _]}) when
|
||||
Fun == is_function -> true;
|
||||
|
||||
returns_boolean({call, _, {remote, _, {atom, _, erlang}, {atom, _, Fun}}, [_,_,_]}) when
|
||||
returns_boolean({{'.', _, [erlang, Fun]}, _, [_, _, _]}) when
|
||||
Fun == function_exported -> true;
|
||||
|
||||
returns_boolean({atom, _, Bool}) when is_boolean(Bool) -> true;
|
||||
|
||||
returns_boolean({'case', _, _, Clauses}) ->
|
||||
returns_boolean({'case', _, [_, [{do, Clauses}]]}) ->
|
||||
lists:all(fun
|
||||
({clause,_,_,_,[Expr]}) -> returns_boolean(Expr);
|
||||
(_) -> false
|
||||
({'->',_,[_, Expr]}) -> returns_boolean(Expr)
|
||||
end, Clauses);
|
||||
|
||||
returns_boolean({'cond', _, [[{do, Clauses}]]}) ->
|
||||
lists:all(fun
|
||||
({'->',_,[_, Expr]}) -> returns_boolean(Expr)
|
||||
end, Clauses);
|
||||
|
||||
returns_boolean({'__block__', [], Exprs}) ->
|
||||
returns_boolean(lists:last(Exprs));
|
||||
|
||||
returns_boolean(_) -> false.
|
||||
|
||||
convert_to_boolean(Line, Expr, Bool, S) when is_integer(Line) ->
|
||||
|
||||
@@ -39,7 +39,7 @@ defmodule BehaviourTest do
|
||||
end
|
||||
|
||||
test :callbacks do
|
||||
assert Sample.__behaviour__(:callbacks) == [first: 1, foo: 2, bar: 2, guarded: 1, orr: 1, literal: 5, "MACRO-last": 2]
|
||||
assert Sample.__behaviour__(:callbacks) == [first: 1, guarded: 1, "MACRO-last": 2, literal: 5, orr: 1, foo: 2, bar: 2]
|
||||
end
|
||||
|
||||
test :specs do
|
||||
|
||||
@@ -51,6 +51,11 @@ defmodule GenServerTest do
|
||||
assert GenServer.call(pid, :stop) == :ok
|
||||
end
|
||||
|
||||
test "nil name" do
|
||||
{:ok, pid} = GenServer.start_link(Stack, [:hello], name: nil)
|
||||
assert Process.info(pid, :registered_name) == {:registered_name, []}
|
||||
end
|
||||
|
||||
test "start/2" do
|
||||
{:ok, pid} = GenServer.start(Stack, [:hello])
|
||||
{:links, links} = Process.info(self, :links)
|
||||
|
||||
@@ -71,11 +71,11 @@ bar """
|
||||
assert x == "foo"
|
||||
|
||||
assert_raise ErlangError, fn ->
|
||||
Code.eval_string(~s(<<x :: [binary, size(3), unit(4)]>> <> _ = "foobar"))
|
||||
Code.eval_string(~s{<<x :: [binary, size(3), unit(4)]>> <> _ = "foobar"})
|
||||
end
|
||||
|
||||
assert_raise ErlangError, fn ->
|
||||
Code.eval_string(~s(<<x :: [integer, size(4)]>> <> _ = "foobar"))
|
||||
Code.eval_string(~s{<<x :: [integer, size(4)]>> <> _ = "foobar"})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -167,9 +167,9 @@ bar """
|
||||
test :bitsyntax_translation do
|
||||
refb = "sample"
|
||||
sec_data = "another"
|
||||
<< size(refb) :: [size(1), big, signed, integer, unit(8)],
|
||||
<< byte_size(refb) :: [size(1), big, signed, integer, unit(8)],
|
||||
refb :: binary,
|
||||
size(sec_data) :: [size(1), big, signed, integer, unit(16)],
|
||||
byte_size(sec_data) :: [size(1), big, signed, integer, unit(16)],
|
||||
sec_data :: binary >>
|
||||
end
|
||||
|
||||
@@ -193,9 +193,9 @@ bar """
|
||||
test :bitsyntax_macro do
|
||||
refb = "sample"
|
||||
sec_data = "another"
|
||||
<< size(refb) :: refb_spec,
|
||||
<< byte_size(refb) :: refb_spec,
|
||||
refb :: binary,
|
||||
size(sec_data) :: [size(1), signed_16],
|
||||
byte_size(sec_data) :: [size(1), signed_16],
|
||||
sec_data :: binary >>
|
||||
end
|
||||
end
|
||||
|
||||
@@ -37,6 +37,17 @@ defmodule Kernel.CaseTest do
|
||||
refute 1.0 in [1, 2, 3], "not in assertion"
|
||||
end
|
||||
|
||||
test :in_cond_clause do
|
||||
assert (cond do
|
||||
format() && (f = format()) ->
|
||||
f
|
||||
true ->
|
||||
:text
|
||||
end) == :html
|
||||
end
|
||||
|
||||
defp format, do: :html
|
||||
|
||||
defp vars_case(x, vx) do
|
||||
case x > 400 do
|
||||
true ->
|
||||
|
||||
@@ -47,10 +47,6 @@ defmodule Kernel.ErrorsTest do
|
||||
assert_compile_fail TokenMissingError,
|
||||
"nofile:3: missing terminator: } (for sigil ~r{ starting at line 1)",
|
||||
'~r{foo\n\n'
|
||||
|
||||
assert_compile_fail TokenMissingError,
|
||||
"nofile:3: missing terminator: ) (for sigil ~r( starting at line 1)",
|
||||
'~r(f(oo)\n\n'
|
||||
end
|
||||
|
||||
test :dot_terminator do
|
||||
@@ -565,8 +561,9 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
|
||||
test :unbound_cond do
|
||||
assert_compile_fail ArgumentError,
|
||||
"unbound variable _ inside cond. If you want the last clause to match, you probably meant to use true ->",
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: unbound variable _ inside cond. If you want the last clause to always match, " <>
|
||||
"you probably meant to use: true ->",
|
||||
'cond do _ -> true end'
|
||||
end
|
||||
|
||||
@@ -603,6 +600,17 @@ defmodule Kernel.ErrorsTest do
|
||||
'''
|
||||
end
|
||||
|
||||
test :invalid_args_for_bodyless_clause do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:2: can use only variables and \\\\ as arguments of bodyless clause",
|
||||
'''
|
||||
defmodule ErrorsTest do
|
||||
def foo(arg // nil)
|
||||
def foo(_), do: :ok
|
||||
end
|
||||
'''
|
||||
end
|
||||
|
||||
test :invalid_function_on_match do
|
||||
assert_compile_fail CompileError,
|
||||
"nofile:1: cannot invoke function something_that_does_not_exist/0 inside match",
|
||||
|
||||
@@ -261,10 +261,13 @@ defmodule Kernel.ExpansionTest do
|
||||
quote do: (for(a <- b(), c = a, do: c))
|
||||
end
|
||||
|
||||
# test "variables inside comprehensions options do not leak" do
|
||||
# assert expand(quote do: (for(a <- b, into: c = [], do: 1); c)) ==
|
||||
# quote do: (for(a <- b(), do: 1, into: c = []); c())
|
||||
# end
|
||||
test "variables inside comprehensions options do not leak" do
|
||||
assert expand(quote do: (for(a <- c = b, into: [], do: 1); c)) ==
|
||||
quote do: (for(a <- c = b(), do: 1, into: []); c())
|
||||
|
||||
assert expand(quote do: (for(a <- b, into: c = [], do: 1); c)) ==
|
||||
quote do: (for(a <- b(), do: 1, into: c = []); c())
|
||||
end
|
||||
|
||||
## Capture
|
||||
|
||||
@@ -311,6 +314,28 @@ defmodule Kernel.ExpansionTest do
|
||||
quote do: (fn x -> x end; x())
|
||||
end
|
||||
|
||||
## Cond
|
||||
|
||||
test "cond: expands each clause" do
|
||||
assert expand_and_clean(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 in between clauses" do
|
||||
assert expand_and_clean(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))) ==
|
||||
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))) ==
|
||||
quote do: (cond do 1 -> x = 1; 2 -> y = 2 end; :erlang.+(x, y))
|
||||
end
|
||||
|
||||
## Case
|
||||
|
||||
test "case: expands each clause" do
|
||||
|
||||
@@ -22,7 +22,7 @@ defmodule Kernel.FnTest do
|
||||
assert (&List.flatten/1).([[0]]) == [0]
|
||||
assert (&(List.flatten/1)).([[0]]) == [0]
|
||||
assert (&List.flatten(&1)).([[0]]) == [0]
|
||||
assert &List.flatten(&1) == &List.flatten/1
|
||||
assert (&List.flatten(&1)) == (&List.flatten/1)
|
||||
end
|
||||
|
||||
test "capture local" do
|
||||
@@ -41,7 +41,7 @@ defmodule Kernel.FnTest do
|
||||
assert (&is_atom/1).(:a)
|
||||
assert (&(is_atom/1)).(:a)
|
||||
assert (&is_atom(&1)).(:a)
|
||||
assert &is_atom(&1) == &is_atom/1
|
||||
assert (&is_atom(&1)) == &is_atom/1
|
||||
end
|
||||
|
||||
test "capture macro" do
|
||||
@@ -54,6 +54,7 @@ defmodule Kernel.FnTest do
|
||||
test "capture operator" do
|
||||
assert is_function &+/2
|
||||
assert is_function &(&&/2)
|
||||
assert is_function & &1 + &2, 2
|
||||
end
|
||||
|
||||
test "capture with variable module" do
|
||||
|
||||
@@ -317,6 +317,18 @@ defmodule Kernel.RaiseTest do
|
||||
assert result == "no case clause matching: 0"
|
||||
end
|
||||
|
||||
test :cond_clause_error do
|
||||
result = try do
|
||||
cond do
|
||||
!zero(0) -> :ok
|
||||
end
|
||||
rescue
|
||||
x in [CondClauseError] -> Exception.message(x)
|
||||
end
|
||||
|
||||
assert result == "no cond clause evaluated to a true value"
|
||||
end
|
||||
|
||||
test :try_clause_error do
|
||||
f = fn() -> :example end
|
||||
result = try do
|
||||
|
||||
@@ -99,11 +99,9 @@ defmodule Kernel.SigilsTest do
|
||||
end
|
||||
|
||||
test :sigils_matching do
|
||||
assert ~s(f(o)o) == "f(o)o"
|
||||
assert ~s(f\(oo) == "f(oo"
|
||||
assert ~s(fo\)o) == "fo)o"
|
||||
assert ~s(f\(o\)o) == "f(o)o"
|
||||
|
||||
assert ~s(f[oo) == "f[oo"
|
||||
assert ~s(fo]o) == "fo]o"
|
||||
end
|
||||
|
||||
@@ -8,15 +8,33 @@ defmodule Kernel.TypespecTest do
|
||||
# module
|
||||
defmacrop test_module([{:do, block}]) do
|
||||
quote do
|
||||
{:module, _, _binary, result} = defmodule T do
|
||||
{:module, _, binary, _} = defmodule TestTypespec do
|
||||
unquote(block)
|
||||
end
|
||||
:code.delete(T)
|
||||
:code.purge(T)
|
||||
result
|
||||
:code.delete(TestTypespec)
|
||||
:code.purge(TestTypespec)
|
||||
binary
|
||||
end
|
||||
end
|
||||
|
||||
defp types(module) do
|
||||
Kernel.Typespec.beam_types(module)
|
||||
|> Enum.sort
|
||||
end
|
||||
|
||||
@skip_specs [__info__: 1]
|
||||
|
||||
defp specs(module) do
|
||||
Kernel.Typespec.beam_specs(module)
|
||||
|> Enum.reject(fn {sign, _} -> sign in @skip_specs end)
|
||||
|> Enum.sort()
|
||||
end
|
||||
|
||||
defp callbacks(module) do
|
||||
Kernel.Typespec.beam_callbacks(module)
|
||||
|> Enum.sort
|
||||
end
|
||||
|
||||
test "invalid type specification" do
|
||||
assert_raise CompileError, ~r"invalid type specification: mytype = 1", fn ->
|
||||
test_module do
|
||||
@@ -34,260 +52,301 @@ defmodule Kernel.TypespecTest do
|
||||
end
|
||||
|
||||
test "@type with a single type" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: term
|
||||
end
|
||||
assert {:mytype, {:type, _, :term, []}, []} = spec
|
||||
|
||||
assert [type: {:mytype, {:type, _, :term, []}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with an atom" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: :atom
|
||||
end
|
||||
assert {:mytype, {:atom, _, :atom}, []} = spec
|
||||
|
||||
assert [type: {:mytype, {:atom, _, :atom}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with an atom alias" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: Atom
|
||||
end
|
||||
assert {:mytype, {:atom, _, Atom}, []} = spec
|
||||
|
||||
assert [type: {:mytype, {:atom, _, Atom}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with an integer" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: 10
|
||||
end
|
||||
assert {:mytype, {:integer, _, 10}, []} = spec
|
||||
assert [type: {:mytype, {:integer, _, 10}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with a negative integer" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: -10
|
||||
end
|
||||
assert {:mytype, {:op, _, :-, {:integer, _, 10}}, []} = spec
|
||||
|
||||
assert [type: {:mytype, {:op, _, :-, {:integer, _, 10}}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with a remote type" do
|
||||
{spec1, spec2} = test_module do
|
||||
t1 = @type mytype :: Remote.Some.type
|
||||
t2 = @type mytype_arg :: Remote.type(integer)
|
||||
{t1, t2}
|
||||
module = test_module do
|
||||
@type mytype :: Remote.Some.type
|
||||
@type mytype_arg :: Remote.type(integer)
|
||||
end
|
||||
assert {:mytype, {:remote_type, _, [{:atom, _, Remote.Some}, {:atom, _, :type}, []]}, []} = spec1
|
||||
assert {:mytype_arg, {:remote_type, _, [{:atom, _, Remote}, {:atom, _, :type}, [{:type, _, :integer, []}]]}, []} = spec2
|
||||
|
||||
assert [type: {:mytype, {:remote_type, _, [{:atom, _, Remote.Some}, {:atom, _, :type}, []]}, []},
|
||||
type: {:mytype_arg, {:remote_type, _, [{:atom, _, Remote}, {:atom, _, :type}, [{:type, _, :integer, []}]]}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with a binary" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: binary
|
||||
end
|
||||
assert {:mytype, {:type, _, :binary, []}, []} = spec
|
||||
|
||||
assert [type: {:mytype, {:type, _, :binary, []}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with an empty binary" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: <<>>
|
||||
end
|
||||
assert {:mytype, {:type, _, :binary, [{:integer, _, 0}, {:integer, _, 0}]}, []} = spec
|
||||
|
||||
assert [type: {:mytype, {:type, _, :binary, [{:integer, _, 0}, {:integer, _, 0}]}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with a binary with a base size" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: <<_ :: 3>>
|
||||
end
|
||||
assert {:mytype, {:type, _, :binary, [{:integer, _, 3}, {:integer, _, 0}]}, []} = spec
|
||||
|
||||
assert [type: {:mytype, {:type, _, :binary, [{:integer, _, 3}, {:integer, _, 0}]}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with a binary with a unit size" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: <<_ :: _ * 8>>
|
||||
end
|
||||
assert {:mytype, {:type, _, :binary, [{:integer, _, 0}, {:integer, _, 8}]}, []} = spec
|
||||
|
||||
assert [type: {:mytype, {:type, _, :binary, [{:integer, _, 0}, {:integer, _, 8}]}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with a range" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: range(1, 10)
|
||||
end
|
||||
assert {:mytype, {:type, _, :range, [{:integer, _, 1}, {:integer, _, 10}]}, []} = spec
|
||||
|
||||
assert [type: {:mytype, {:type, _, :range, [{:integer, _, 1}, {:integer, _, 10}]}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with a range op" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: 1..10
|
||||
end
|
||||
assert {:mytype, {:type, _, :range, [{:integer, _, 1}, {:integer, _, 10}]}, []} = spec
|
||||
|
||||
assert [type: {:mytype, {:type, _, :range, [{:integer, _, 1}, {:integer, _, 10}]}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with a map" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: %{hello: :world}
|
||||
end
|
||||
assert {:mytype,
|
||||
|
||||
assert [type: {:mytype,
|
||||
{:type, _, :map, [
|
||||
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}}
|
||||
]},
|
||||
[]} = spec
|
||||
[]}] = types(module)
|
||||
end
|
||||
|
||||
test "@type with a struct" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: %User{hello: :world}
|
||||
end
|
||||
assert {:mytype,
|
||||
|
||||
assert [type: {:mytype,
|
||||
{:type, _, :map, [
|
||||
{:type, _, :map_field_assoc, {:atom, _, :__struct__}, {:atom, _, User}},
|
||||
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}}
|
||||
]},
|
||||
[]} = spec
|
||||
[]}] = types(module)
|
||||
end
|
||||
|
||||
test "@type with a tuple" do
|
||||
{spec1, spec2, spec3} = test_module do
|
||||
t1 = @type mytype :: tuple
|
||||
t2 = @type mytype1 :: {}
|
||||
t3 = @type mytype2 :: {1, 2}
|
||||
{t1, t2, t3}
|
||||
module = test_module do
|
||||
@type mytype :: tuple
|
||||
@type mytype1 :: {}
|
||||
@type mytype2 :: {1, 2}
|
||||
end
|
||||
assert {:mytype, {:type, _, :tuple, :any}, []} = spec1
|
||||
assert {:mytype1, {:type, _, :tuple, []}, []} = spec2
|
||||
assert {:mytype2, {:type, _, :tuple, [{:integer, _, 1}, {:integer, _, 2}]}, []} = spec3
|
||||
|
||||
assert [type: {:mytype, {:type, _, :tuple, :any}, []},
|
||||
type: {:mytype1, {:type, _, :tuple, []}, []},
|
||||
type: {:mytype2, {:type, _, :tuple, [{:integer, _, 1}, {:integer, _, 2}]}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with list shortcuts" do
|
||||
{spec1, spec2, spec3} = test_module do
|
||||
t1 = @type mytype :: []
|
||||
t2 = @type mytype1 :: [integer]
|
||||
t3 = @type mytype2 :: [integer, ...]
|
||||
{t1, t2, t3}
|
||||
module = test_module do
|
||||
@type mytype :: []
|
||||
@type mytype1 :: [integer]
|
||||
@type mytype2 :: [integer, ...]
|
||||
end
|
||||
assert {:mytype, {:type, _, :nil, []}, []} = spec1
|
||||
assert {:mytype1, {:type, _, :list, [{:type, _, :integer, []}]}, []} = spec2
|
||||
assert {:mytype2, {:type, _, :nonempty_list, [{:type, _, :integer, []}]}, []} = spec3
|
||||
|
||||
assert [type: {:mytype, {:type, _, :nil, []}, []},
|
||||
type: {:mytype1, {:type, _, :list, [{:type, _, :integer, []}]}, []},
|
||||
type: {:mytype2, {:type, _, :nonempty_list, [{:type, _, :integer, []}]}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with a fun" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: (... -> any)
|
||||
end
|
||||
assert {:mytype, {:type, _, :fun, []}, []} = spec
|
||||
|
||||
assert [type: {:mytype, {:type, _, :fun, []}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with a fun with multiple arguments and return type" do
|
||||
t = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: (integer, integer -> integer)
|
||||
end
|
||||
assert {:mytype, {:type, _, :fun, [{:type, _, :product,
|
||||
|
||||
assert [type: {:mytype, {:type, _, :fun, [{:type, _, :product,
|
||||
[{:type, _, :integer, []}, {:type, _, :integer, []}]},
|
||||
{:type, _, :integer, []}]}, []} = t
|
||||
{:type, _, :integer, []}]}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with a fun with no arguments and return type" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: (() -> integer)
|
||||
end
|
||||
assert {:mytype, {:type, _, :fun, [{:type, _, :product, []},
|
||||
{:type, _, :integer, []}]}, []} = spec
|
||||
|
||||
assert [type: {:mytype, {:type, _, :fun, [{:type, _, :product, []},
|
||||
{:type, _, :integer, []}]}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with a fun with any arity and return type" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: (... -> integer)
|
||||
end
|
||||
assert {:mytype, {:type, _, :fun, [{:type, _, :any},
|
||||
{:type, _, :integer, []}]}, []} = spec
|
||||
|
||||
assert [type: {:mytype, {:type, _, :fun, [{:type, _, :any},
|
||||
{:type, _, :integer, []}]}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with a union" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: integer | char_list | atom
|
||||
end
|
||||
assert {:mytype, {:type, _, :union, [{:type, _, :integer, []},
|
||||
|
||||
assert [type: {:mytype, {:type, _, :union, [{:type, _, :integer, []},
|
||||
{:remote_type, _, [{:atom, _, :elixir}, {:atom, _, :char_list}, []]},
|
||||
{:type, _, :atom, []}]}, []} = spec
|
||||
{:type, _, :atom, []}]}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with keywords" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: [first: integer, step: integer, last: integer]
|
||||
end
|
||||
assert {:mytype, {:type, _, :list, [
|
||||
|
||||
assert [type: {:mytype, {:type, _, :list, [
|
||||
{:type, _, :union, [
|
||||
{:type, _, :tuple, [{:atom, _, :first}, {:type, _, :integer, []}]},
|
||||
{:type, _, :tuple, [{:atom, _, :step}, {:type, _, :integer, []}]},
|
||||
{:type, _, :tuple, [{:atom, _, :last}, {:type, _, :integer, []}]}
|
||||
]}
|
||||
]}, []} = spec
|
||||
]}, []}] = types(module)
|
||||
end
|
||||
|
||||
test "@type with parameters" do
|
||||
{spec1, spec2, spec3} = test_module do
|
||||
t1 = @type mytype(x) :: x
|
||||
t2 = @type mytype1(x) :: list(x)
|
||||
t3 = @type mytype2(x, y) :: {x, y}
|
||||
{t1, t2, t3}
|
||||
module = test_module do
|
||||
@type mytype(x) :: x
|
||||
@type mytype1(x) :: list(x)
|
||||
@type mytype2(x, y) :: {x, y}
|
||||
end
|
||||
assert {:mytype, {:var, _, :x}, [{:var, _, :x}]} = spec1
|
||||
assert {:mytype1, {:type, _, :list, [{:var, _, :x}]}, [{:var, _, :x}]} = spec2
|
||||
assert {:mytype2, {:type, _, :tuple, [{:var, _, :x}, {:var, _, :y}]}, [{:var, _, :x}, {:var, _, :y}]} = spec3
|
||||
|
||||
assert [type: {:mytype, {:var, _, :x}, [{:var, _, :x}]},
|
||||
type: {:mytype1, {:type, _, :list, [{:var, _, :x}]}, [{:var, _, :x}]},
|
||||
type: {:mytype2, {:type, _, :tuple, [{:var, _, :x}, {:var, _, :y}]}, [{:var, _, :x}, {:var, _, :y}]}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with annotations" do
|
||||
{spec1, spec2} = test_module do
|
||||
t1 = @type mytype :: (named :: integer)
|
||||
t2 = @type mytype1 :: (a :: integer -> integer)
|
||||
{t1, t2}
|
||||
module = test_module do
|
||||
@type mytype :: (named :: integer)
|
||||
@type mytype1 :: (a :: integer -> integer)
|
||||
end
|
||||
assert {:mytype, {:ann_type, _, [{:var, _, :named}, {:type, _, :integer, []}]}, []} = spec1
|
||||
assert {:mytype1, {:type, _, :fun, [{:type, _, :product, [{:ann_type, _, [{:var, _, :a}, {:type, _, :integer, []}]}]}, {:type, _, :integer, []}]}, []} = spec2
|
||||
|
||||
assert [type: {:mytype, {:ann_type, _, [{:var, _, :named}, {:type, _, :integer, []}]}, []},
|
||||
type: {:mytype1, {:type, _, :fun, [{:type, _, :product, [{:ann_type, _, [{:var, _, :a}, {:type, _, :integer, []}]}]}, {:type, _, :integer, []}]}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@opaque(type)" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
@opaque mytype(x) :: x
|
||||
end
|
||||
assert {:mytype, {:var, _, :x}, [{:var, _, :x}]} = spec
|
||||
|
||||
assert [opaque: {:mytype, {:var, _, :x}, [{:var, _, :x}]}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type + opaque" do
|
||||
types = test_module do
|
||||
module = test_module do
|
||||
@type mytype :: tuple
|
||||
@opaque mytype1 :: {}
|
||||
@type ++ @opaque
|
||||
end
|
||||
assert [{:mytype, _, []}, {:mytype1, _, []}] = types
|
||||
|
||||
assert [opaque: {:mytype1, _, []},
|
||||
type: {:mytype, _, []},] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type from structs" do
|
||||
types = test_module do
|
||||
module = test_module do
|
||||
defstruct name: nil, age: 0 :: non_neg_integer
|
||||
@type
|
||||
end
|
||||
|
||||
assert [{:t, {:type, _, :map, [
|
||||
assert [type: {:t, {:type, _, :map, [
|
||||
{:type, _, :map_field_assoc, {:atom, _, :name}, {:type, _, :term, []}},
|
||||
{:type, _, :map_field_assoc, {:atom, _, :age}, {:type, _, :non_neg_integer, []}},
|
||||
{:type, _, :map_field_assoc, {:atom, _, :__struct__}, {:atom, _, Kernel.TypespecTest.T}}
|
||||
]}, []}] = types
|
||||
{:type, _, :map_field_assoc, {:atom, _, :__struct__}, {:atom, _, TestTypespec}}
|
||||
]}, []}] = types(module)
|
||||
end
|
||||
|
||||
test "@type from dynamic structs" do
|
||||
types = test_module do
|
||||
module = test_module do
|
||||
fields = [name: nil, age: 0]
|
||||
defstruct fields
|
||||
@type
|
||||
end
|
||||
|
||||
assert [{:t, {:type, _, :map, [
|
||||
assert [type: {:t, {:type, _, :map, [
|
||||
{:type, _, :map_field_assoc, {:atom, _, :name}, {:type, _, :term, []}},
|
||||
{:type, _, :map_field_assoc, {:atom, _, :age}, {:type, _, :term, []}},
|
||||
{:type, _, :map_field_assoc, {:atom, _, :__struct__}, {:atom, _, Kernel.TypespecTest.T}}
|
||||
]}, []}] = types
|
||||
{:type, _, :map_field_assoc, {:atom, _, :__struct__}, {:atom, _, TestTypespec}}
|
||||
]}, []}] = types(module)
|
||||
end
|
||||
|
||||
test "@type unquote fragment" do
|
||||
spec = test_module do
|
||||
module = test_module do
|
||||
quoted = quote unquote: false do
|
||||
name = :mytype
|
||||
type = :atom
|
||||
@@ -295,7 +354,9 @@ defmodule Kernel.TypespecTest do
|
||||
end
|
||||
Module.eval_quoted(__MODULE__, quoted) |> elem(0)
|
||||
end
|
||||
assert {:mytype, {:atom, _, :atom}, []} = spec
|
||||
|
||||
assert [type: {:mytype, {:atom, _, :atom}, []}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "defines_type?" do
|
||||
@@ -309,84 +370,84 @@ defmodule Kernel.TypespecTest do
|
||||
end
|
||||
|
||||
test "@spec(spec)" do
|
||||
{spec1, spec2, spec3, spec4} = test_module do
|
||||
module = test_module do
|
||||
def myfun1(x), do: x
|
||||
def myfun2(), do: :ok
|
||||
def myfun3(x, y), do: {x, y}
|
||||
def myfun4(x), do: x
|
||||
t1 = @spec myfun1(integer) :: integer
|
||||
t2 = @spec myfun2() :: integer
|
||||
t3 = @spec myfun3(integer, integer) :: {integer, integer}
|
||||
t4 = @spec myfun4(x :: integer) :: integer
|
||||
{t1, t2, t3, t4}
|
||||
@spec myfun1(integer) :: integer
|
||||
@spec myfun2() :: integer
|
||||
@spec myfun3(integer, integer) :: {integer, integer}
|
||||
@spec myfun4(x :: integer) :: integer
|
||||
end
|
||||
assert {{:myfun1, 1}, {:type, _, :fun, [{:type, _, :product, [{:type, _, :integer, []}]}, {:type, _, :integer, []}]}} = spec1
|
||||
assert {{:myfun2, 0}, {:type, _, :fun, [{:type, _, :product, []}, {:type, _, :integer, []}]}} = spec2
|
||||
assert {{:myfun3, 2}, {:type, _, :fun, [{:type, _, :product, [{:type, _, :integer, []}, {:type, _, :integer, []}]}, {:type, _, :tuple, [{:type, _, :integer, []}, {:type, _, :integer, []}]}]}} = spec3
|
||||
assert {{:myfun4, 1}, {:type, _, :fun, [{:type, _, :product, [{:ann_type, _, [{:var, _, :x}, {:type, _, :integer, []}]}]}, {:type, _, :integer, []}]}} = spec4
|
||||
|
||||
assert [{{:myfun1, 1}, [{:type, _, :fun, [{:type, _, :product, [{:type, _, :integer, []}]}, {:type, _, :integer, []}]}]},
|
||||
{{:myfun2, 0}, [{:type, _, :fun, [{:type, _, :product, []}, {:type, _, :integer, []}]}]},
|
||||
{{:myfun3, 2}, [{:type, _, :fun, [{:type, _, :product, [{:type, _, :integer, []}, {:type, _, :integer, []}]}, {:type, _, :tuple, [{:type, _, :integer, []}, {:type, _, :integer, []}]}]}]},
|
||||
{{:myfun4, 1}, [{:type, _, :fun, [{:type, _, :product, [{:ann_type, _, [{:var, _, :x}, {:type, _, :integer, []}]}]}, {:type, _, :integer, []}]}]}] =
|
||||
specs(module)
|
||||
end
|
||||
|
||||
test "@spec(spec) with guards" do
|
||||
{spec1, spec2, spec3} = test_module do
|
||||
module = test_module do
|
||||
def myfun1(x), do: x
|
||||
spec1 = @spec myfun1(x) :: boolean when [x: integer]
|
||||
@spec myfun1(x) :: boolean when [x: integer]
|
||||
|
||||
def myfun2(x), do: x
|
||||
spec2 = @spec myfun2(x) :: x when [x: var]
|
||||
@spec myfun2(x) :: x when [x: var]
|
||||
|
||||
def myfun3(_x, y), do: y
|
||||
spec3 = @spec myfun3(x, y) :: y when [y: x, x: var]
|
||||
|
||||
{spec1, spec2, spec3}
|
||||
@spec myfun3(x, y) :: y when [y: x, x: var]
|
||||
end
|
||||
assert {{:myfun1, 1}, {:type, _, :bounded_fun, [{:type, _, :fun, [{:type, _, :product, [{:var, _, :x}]}, {:type, _, :boolean, []}]}, [{:type, _, :constraint, [{:atom, _, :is_subtype}, [{:var, _, :x}, {:type, _, :integer, []}]]}]]}} = spec1
|
||||
assert {{:myfun2, 1}, {:type, _, :fun, [{:type, _, :product, [{:var, _, :x}]}, {:var, _, :x}]}} = spec2
|
||||
assert {{:myfun3, 2}, {:type, _, :bounded_fun, [{:type, _, :fun, [{:type, _, :product, [{:var, _, :x}, {:var, _, :y}]}, {:var, _, :y}]}, [{:type, _, :constraint, [{:atom, _, :is_subtype}, [{:var, _, :y}, {:var, _, :x}]]}]]}} = spec3
|
||||
|
||||
assert [{{:myfun1, 1}, [{:type, _, :bounded_fun, [{:type, _, :fun, [{:type, _, :product, [{:var, _, :x}]}, {:type, _, :boolean, []}]}, [{:type, _, :constraint, [{:atom, _, :is_subtype}, [{:var, _, :x}, {:type, _, :integer, []}]]}]]}]},
|
||||
{{:myfun2, 1}, [{:type, _, :fun, [{:type, _, :product, [{:var, _, :x}]}, {:var, _, :x}]}]},
|
||||
{{:myfun3, 2}, [{:type, _, :bounded_fun, [{:type, _, :fun, [{:type, _, :product, [{:var, _, :x}, {:var, _, :y}]}, {:var, _, :y}]}, [{:type, _, :constraint, [{:atom, _, :is_subtype}, [{:var, _, :y}, {:var, _, :x}]]}]]}]}] =
|
||||
specs(module)
|
||||
end
|
||||
|
||||
test "@callback(callback)" do
|
||||
{spec1, spec2, spec3} = test_module do
|
||||
t1 = @callback myfun(integer) :: integer
|
||||
t2 = @callback myfun() :: integer
|
||||
t3 = @callback myfun(integer, integer) :: {integer, integer}
|
||||
{t1, t2, t3}
|
||||
module = test_module do
|
||||
@callback myfun(integer) :: integer
|
||||
@callback myfun() :: integer
|
||||
@callback myfun(integer, integer) :: {integer, integer}
|
||||
end
|
||||
assert {{:myfun, 1}, {:type, _, :fun, [{:type, _, :product, [{:type, _, :integer, []}]}, {:type, _, :integer, []}]}} = spec1
|
||||
assert {{:myfun, 0}, {:type, _, :fun, [{:type, _, :product, []}, {:type, _, :integer, []}]}} = spec2
|
||||
assert {{:myfun, 2}, {:type, _, :fun, [{:type, _, :product, [{:type, _, :integer, []}, {:type, _, :integer, []}]}, {:type, _, :tuple, [{:type, _, :integer, []}, {:type, _, :integer, []}]}]}} = spec3
|
||||
|
||||
assert [{{:myfun, 0}, [{:type, _, :fun, [{:type, _, :product, []}, {:type, _, :integer, []}]}]},
|
||||
{{:myfun, 1}, [{:type, _, :fun, [{:type, _, :product, [{:type, _, :integer, []}]}, {:type, _, :integer, []}]}]},
|
||||
{{:myfun, 2}, [{:type, _, :fun, [{:type, _, :product, [{:type, _, :integer, []}, {:type, _, :integer, []}]}, {:type, _, :tuple, [{:type, _, :integer, []}, {:type, _, :integer, []}]}]}]}] =
|
||||
callbacks(module)
|
||||
end
|
||||
|
||||
test "@spec + @callback" do
|
||||
{specs, callbacks} = test_module do
|
||||
module = test_module do
|
||||
def myfun(x), do: x
|
||||
@spec myfun(integer) :: integer
|
||||
@spec myfun(char_list) :: char_list
|
||||
@callback cb(integer) :: integer
|
||||
{@spec, @callback}
|
||||
end
|
||||
|
||||
assert [
|
||||
{{:cb, 1}, {:type, _, :fun, [{:type, _, :product, [{:type, _, :integer, []}]}, {:type, _, :integer, []}]}}
|
||||
] = Enum.sort(callbacks)
|
||||
assert [{{:cb, 1}, [{:type, _, :fun, [{:type, _, :product, [{:type, _, :integer, []}]}, {:type, _, :integer, []}]}]}] =
|
||||
callbacks(module)
|
||||
|
||||
assert [
|
||||
{{:myfun, 1}, {:type, _, :fun, [{:type, _, :product, [{:type, _, :integer, []}]}, {:type, _, :integer, []}]}},
|
||||
{{:myfun, 1}, {:type, _, :fun, [{:type, _, :product, [
|
||||
{:remote_type, _, [{:atom, _, :elixir}, {:atom, _, :char_list}, []]}]},
|
||||
{:remote_type, _, [{:atom, _, :elixir}, {:atom, _, :char_list}, []]}]}}
|
||||
] = Enum.sort(specs)
|
||||
assert [{{:myfun, 1}, [
|
||||
{:type, _, :fun, [{:type, _, :product, [
|
||||
{:remote_type, _, [{:atom, _, :elixir}, {:atom, _, :char_list}, []]}]},
|
||||
{:remote_type, _, [{:atom, _, :elixir}, {:atom, _, :char_list}, []]}]},
|
||||
{:type, _, :fun, [{:type, _, :product, [{:type, _, :integer, []}]}, {:type, _, :integer, []}]}]}] =
|
||||
specs(module)
|
||||
end
|
||||
|
||||
test "block handling" do
|
||||
spec = test_module do
|
||||
spec = @spec foo((() -> [ integer ])) :: integer
|
||||
module = test_module do
|
||||
@spec foo((() -> [ integer ])) :: integer
|
||||
def foo(_), do: 1
|
||||
spec
|
||||
end
|
||||
assert {{:foo, 1},
|
||||
{:type, _, :fun, [{:type, _, :product, [
|
||||
assert [{{:foo, 1},
|
||||
[{:type, _, :fun, [{:type, _, :product, [
|
||||
{:type, _, :fun, [{:type, _, :product, []}, {:type, _, :list, [{:type, _, :integer, []}]}]}]},
|
||||
{:type, _, :integer, []}]}} = spec
|
||||
{:type, _, :integer, []}]}]}] =
|
||||
specs(module)
|
||||
end
|
||||
|
||||
# Conversion to AST
|
||||
@@ -413,19 +474,18 @@ defmodule Kernel.TypespecTest do
|
||||
(quote do: @type ops() :: {+1,-1}),
|
||||
(quote do: @type my_map() :: %{hello: :world}),
|
||||
(quote do: @type my_struct() :: %User{hello: :world}),
|
||||
]
|
||||
] |> Enum.sort
|
||||
|
||||
types = test_module do
|
||||
module = test_module do
|
||||
Module.eval_quoted __MODULE__, quote do: (unquote_splicing(quoted))
|
||||
@type ++ @opaque
|
||||
end
|
||||
|
||||
types = Enum.reverse(types)
|
||||
types = types(module)
|
||||
|
||||
for {type, definition} <- Enum.zip(types, quoted) do
|
||||
Enum.each(Enum.zip(types, quoted), fn {{:type, type}, definition} ->
|
||||
ast = Kernel.Typespec.type_to_ast(type)
|
||||
assert Macro.to_string(quote do: @type unquote(ast)) == Macro.to_string(definition)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
test "type_to_ast for paren_type" do
|
||||
@@ -435,60 +495,29 @@ defmodule Kernel.TypespecTest do
|
||||
end
|
||||
|
||||
test "spec_to_ast" do
|
||||
specs = [
|
||||
quoted = [
|
||||
(quote do: @spec a() :: integer()),
|
||||
(quote do: @spec a(atom()) :: integer() | [{}]),
|
||||
(quote do: @spec a(b) :: integer() when [b: integer()]),
|
||||
(quote do: @spec a(b) :: b when [b: var]),
|
||||
(quote do: @spec a(c :: atom()) :: atom()),
|
||||
]
|
||||
] |> Enum.sort
|
||||
|
||||
compiled = test_module do
|
||||
module = test_module do
|
||||
def a, do: 1
|
||||
def a(a), do: a
|
||||
Module.eval_quoted __MODULE__, quote do: (unquote_splicing(specs))
|
||||
Enum.reverse @spec
|
||||
Module.eval_quoted __MODULE__, quote do: (unquote_splicing(quoted))
|
||||
end
|
||||
|
||||
for {{{_, _}, spec}, definition} <- Enum.zip(compiled, specs) do
|
||||
quoted = quote do: @spec unquote(Kernel.Typespec.spec_to_ast(:a, spec))
|
||||
assert Macro.to_string(quoted) == Macro.to_string(definition)
|
||||
end
|
||||
end
|
||||
specs = Enum.flat_map(specs(module), fn {{_, _}, specs} ->
|
||||
Enum.map(specs, fn spec ->
|
||||
quote do: @spec unquote(Kernel.Typespec.spec_to_ast(:a, spec))
|
||||
end)
|
||||
end) |> Enum.sort
|
||||
|
||||
# types/specs retrieval
|
||||
|
||||
test "specs retrieval" do
|
||||
{:module, _, binary, _} = defmodule T do
|
||||
@spec a :: any
|
||||
def a, do: nil
|
||||
end
|
||||
|
||||
:code.delete(T)
|
||||
:code.purge(T)
|
||||
|
||||
assert [{{:a, _}, [{:type, _, :fun, [{:type, _, :product, []}, {:type, _, :any, []}]}]},
|
||||
{{:__info__, 1}, [{:type, _, :fun, [{:type, _, :product, [{:type, _, :atom, []}]}, {:type, _, :term, []}]}]}] =
|
||||
Kernel.Typespec.beam_specs(binary)
|
||||
end
|
||||
|
||||
test "types retrieval" do
|
||||
{:module, _, binary, _} = defmodule T do
|
||||
@type a :: any
|
||||
@typep b :: any
|
||||
@spec t(b) :: b
|
||||
def t(b), do: b
|
||||
@opaque c :: atom
|
||||
end
|
||||
|
||||
:code.delete(T)
|
||||
:code.purge(T)
|
||||
|
||||
assert [
|
||||
{:opaque, {:c, {:type, _, :atom, []}, []}},
|
||||
{:type, {:a, {:type, _, :any, []}, []}},
|
||||
{:typep, {:b, {:type, _, :any, []}, []}},
|
||||
] = Kernel.Typespec.beam_types(binary)
|
||||
Enum.each(Enum.zip(specs, quoted), fn {spec, definition} ->
|
||||
assert Macro.to_string(spec) == Macro.to_string(definition)
|
||||
end)
|
||||
end
|
||||
|
||||
test "typedoc retrieval" do
|
||||
|
||||
@@ -214,11 +214,10 @@ defmodule Kernel.WarningTest do
|
||||
purge [Sample1, Sample2]
|
||||
end
|
||||
|
||||
test :unused_docs do
|
||||
test :empty_clause do
|
||||
assert capture_err(fn ->
|
||||
Code.eval_string """
|
||||
defmodule Sample1 do
|
||||
@doc "Oops"
|
||||
def hello
|
||||
end
|
||||
"""
|
||||
|
||||
@@ -27,7 +27,7 @@ defmodule ProcessTest do
|
||||
nil
|
||||
|
||||
assert Process.info(self, :registered_name) ==
|
||||
{:registered_name, nil}
|
||||
{:registered_name, []}
|
||||
|
||||
Process.register(self, __MODULE__)
|
||||
assert Process.info(self, :registered_name) ==
|
||||
|
||||
@@ -174,6 +174,11 @@ defmodule ProtocolTest do
|
||||
assert WithAny.ok(struct) == {:ok, struct}
|
||||
end
|
||||
|
||||
test "derived protocol keeps local file/line info" do
|
||||
assert ProtocolTest.WithAny.ProtocolTest.ImplStruct.__info__(:compile)[:source] ==
|
||||
String.to_char_list(__ENV__.file)
|
||||
end
|
||||
|
||||
test "custom derive implementation" do
|
||||
struct = %ImplStruct{a: 1, b: 1}
|
||||
assert Derivable.ok(struct) == {:ok, struct, %ImplStruct{}, []}
|
||||
|
||||
@@ -5,6 +5,7 @@ defmodule SystemTest do
|
||||
import PathHelpers
|
||||
|
||||
test "build_info" do
|
||||
assert is_map System.build_info
|
||||
assert not nil?(System.build_info[:version])
|
||||
assert not nil?(System.build_info[:tag])
|
||||
assert not nil?(System.build_info[:date])
|
||||
|
||||
@@ -15,15 +15,15 @@ defmodule Task.SupervisorTest do
|
||||
end
|
||||
|
||||
def wait_and_send(caller, atom) do
|
||||
send caller, :ready
|
||||
receive do: (true -> true)
|
||||
send caller, atom
|
||||
end
|
||||
|
||||
test "async/1", config do
|
||||
task = Task.Supervisor.async config[:supervisor], fn ->
|
||||
receive do: (true -> true)
|
||||
:done
|
||||
end
|
||||
parent = self()
|
||||
fun = fn -> wait_and_send(parent, :done) end
|
||||
task = Task.Supervisor.async(config[:supervisor], fun)
|
||||
|
||||
assert Task.Supervisor.children(config[:supervisor]) == [task.pid]
|
||||
|
||||
@@ -36,6 +36,12 @@ defmodule Task.SupervisorTest do
|
||||
{:links, links} = Process.info(self, :links)
|
||||
assert task.pid in links
|
||||
|
||||
receive do: (:ready -> :ok)
|
||||
|
||||
# Assert the initial call
|
||||
{:name, fun_name} = :erlang.fun_info(fun, :name)
|
||||
assert {__MODULE__, fun_name, 0} === :proc_lib.translate_initial_call(task.pid)
|
||||
|
||||
# Run the task
|
||||
send task.pid, true
|
||||
|
||||
@@ -49,6 +55,9 @@ defmodule Task.SupervisorTest do
|
||||
task = Task.Supervisor.async(config[:supervisor], __MODULE__, :wait_and_send, [self(), :done])
|
||||
assert Task.Supervisor.children(config[:supervisor]) == [task.pid]
|
||||
|
||||
receive do: (:ready -> :ok)
|
||||
assert {__MODULE__, :wait_and_send, 2} === :proc_lib.translate_initial_call(task.pid)
|
||||
|
||||
send task.pid, true
|
||||
assert task.__struct__ == Task
|
||||
assert Task.await(task) == :done
|
||||
@@ -56,12 +65,17 @@ defmodule Task.SupervisorTest do
|
||||
|
||||
test "start_child/1", config do
|
||||
parent = self()
|
||||
{:ok, pid} = Task.Supervisor.start_child(config[:supervisor], fn -> wait_and_send(parent, :done) end)
|
||||
fun = fn -> wait_and_send(parent, :done) end
|
||||
{:ok, pid} = Task.Supervisor.start_child(config[:supervisor], fun)
|
||||
assert Task.Supervisor.children(config[:supervisor]) == [pid]
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
refute pid in links
|
||||
|
||||
receive do: (:ready -> :ok)
|
||||
{:name, fun_name} = :erlang.fun_info(fun, :name)
|
||||
assert {__MODULE__, fun_name, 0} === :proc_lib.translate_initial_call(pid)
|
||||
|
||||
send pid, true
|
||||
assert_receive :done
|
||||
end
|
||||
@@ -73,6 +87,9 @@ defmodule Task.SupervisorTest do
|
||||
{:links, links} = Process.info(self, :links)
|
||||
refute pid in links
|
||||
|
||||
receive do: (:ready -> :ok)
|
||||
assert {__MODULE__, :wait_and_send, 2} === :proc_lib.translate_initial_call(pid)
|
||||
|
||||
send pid, true
|
||||
assert_receive :done
|
||||
end
|
||||
|
||||
@@ -10,15 +10,15 @@ defmodule TaskTest do
|
||||
end
|
||||
|
||||
def wait_and_send(caller, atom) do
|
||||
send caller, :ready
|
||||
receive do: (true -> true)
|
||||
send caller, atom
|
||||
end
|
||||
|
||||
test "async/1" do
|
||||
task = Task.async fn ->
|
||||
receive do: (true -> true)
|
||||
:done
|
||||
end
|
||||
parent = self()
|
||||
fun = fn -> wait_and_send(parent, :done) end
|
||||
task = Task.async(fun)
|
||||
|
||||
# Assert the struct
|
||||
assert task.__struct__ == Task
|
||||
@@ -29,6 +29,12 @@ defmodule TaskTest do
|
||||
{:links, links} = Process.info(self, :links)
|
||||
assert task.pid in links
|
||||
|
||||
receive do: (:ready -> :ok)
|
||||
|
||||
# Assert the initial call
|
||||
{:name, fun_name} = :erlang.fun_info(fun, :name)
|
||||
assert {__MODULE__, fun_name, 0} === :proc_lib.translate_initial_call(task.pid)
|
||||
|
||||
# Run the task
|
||||
send task.pid, true
|
||||
|
||||
@@ -39,18 +45,35 @@ defmodule TaskTest do
|
||||
end
|
||||
|
||||
test "async/3" do
|
||||
task = Task.async(List, :flatten, [[1, [2], 3]])
|
||||
task = Task.async(__MODULE__, :wait_and_send, [self(), :done])
|
||||
assert task.__struct__ == Task
|
||||
assert Task.await(task) == [1, 2, 3]
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
assert task.pid in links
|
||||
|
||||
receive do: (:ready -> :ok)
|
||||
|
||||
assert {__MODULE__, :wait_and_send, 2} === :proc_lib.translate_initial_call(task.pid)
|
||||
|
||||
send(task.pid, true)
|
||||
|
||||
assert Task.await(task) === :done
|
||||
assert_receive :done
|
||||
end
|
||||
|
||||
test "start_link/1" do
|
||||
parent = self()
|
||||
{:ok, pid} = Task.start_link(fn -> wait_and_send(parent, :done) end)
|
||||
fun = fn -> wait_and_send(parent, :done) end
|
||||
{:ok, pid} = Task.start_link(fun)
|
||||
|
||||
{:links, links} = Process.info(self, :links)
|
||||
assert pid in links
|
||||
|
||||
receive do: (:ready -> :ok)
|
||||
|
||||
{:name, fun_name} = :erlang.fun_info(fun, :name)
|
||||
assert {__MODULE__, fun_name, 0} === :proc_lib.translate_initial_call(pid)
|
||||
|
||||
send pid, true
|
||||
assert_receive :done
|
||||
end
|
||||
@@ -61,6 +84,10 @@ defmodule TaskTest do
|
||||
{:links, links} = Process.info(self, :links)
|
||||
assert pid in links
|
||||
|
||||
receive do: (:ready -> :ok)
|
||||
|
||||
assert {__MODULE__, :wait_and_send, 2} === :proc_lib.translate_initial_call(pid)
|
||||
|
||||
send pid, true
|
||||
assert_receive :done
|
||||
end
|
||||
@@ -113,4 +140,5 @@ defmodule TaskTest do
|
||||
assert catch_exit(Task.find([task], msg)) ==
|
||||
{:kill, {Task, :find, [[task], msg]}}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -67,6 +67,10 @@ defmodule URITest do
|
||||
assert URI.decode_www_form("%E3%82%86+") == "ゆ "
|
||||
end
|
||||
|
||||
test :parse_uri do
|
||||
assert URI.parse(uri = %URI{scheme: "http", host: "foo.com"}) == uri
|
||||
end
|
||||
|
||||
test :parse_http do
|
||||
assert %URI{scheme: "http", host: "foo.com", path: "/path/to/something",
|
||||
query: "foo=bar&bar=foo", fragment: "fragment", port: 80,
|
||||
|
||||
@@ -89,9 +89,7 @@ defmodule ExUnit.Callbacks do
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
@ex_unit_setup []
|
||||
@ex_unit_teardown []
|
||||
@ex_unit_setup_all []
|
||||
@ex_unit_teardown_all []
|
||||
|
||||
@before_compile unquote(__MODULE__)
|
||||
import unquote(__MODULE__)
|
||||
@@ -101,9 +99,7 @@ defmodule ExUnit.Callbacks do
|
||||
@doc false
|
||||
defmacro __before_compile__(env) do
|
||||
[compile_callbacks(env, :setup),
|
||||
compile_callbacks(env, :teardown),
|
||||
compile_callbacks(env, :setup_all),
|
||||
compile_callbacks(env, :teardown_all)]
|
||||
compile_callbacks(env, :setup_all)]
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -128,32 +124,6 @@ defmodule ExUnit.Callbacks do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro teardown(var \\ quote(do: _), warn \\ true, block) do
|
||||
if warn do
|
||||
IO.write :stderr, "teardown in ExUnit is deprecated, please use on_exit/1 instead\n" <>
|
||||
Exception.format_stacktrace(Macro.Env.stacktrace(__CALLER__))
|
||||
end
|
||||
quote bind_quoted: [var: escape(var), block: escape(block)] do
|
||||
name = :"__ex_unit_teardown_#{length(@ex_unit_teardown)}"
|
||||
defp unquote(name)(unquote(var)), unquote(block)
|
||||
@ex_unit_teardown [name|@ex_unit_teardown]
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro teardown_all(var \\ quote(do: _), warn \\ true, block) do
|
||||
if warn do
|
||||
IO.write :stderr, "teardown_all in ExUnit is deprecated, please use on_exit/1 instead\n" <>
|
||||
Exception.format_stacktrace(Macro.Env.stacktrace(__CALLER__))
|
||||
end
|
||||
quote bind_quoted: [var: escape(var), block: escape(block)] do
|
||||
name = :"__ex_unit_teardown_all_#{length(@ex_unit_teardown_all)}"
|
||||
defp unquote(name)(unquote(var)), unquote(block)
|
||||
@ex_unit_teardown_all [name|@ex_unit_teardown_all]
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a callback that runs on the test (or test case) exit.
|
||||
|
||||
|
||||
@@ -56,14 +56,6 @@ defmodule ExUnit.CaseTemplate do
|
||||
setup context do
|
||||
unquote(module).__ex_unit__(:setup, context)
|
||||
end
|
||||
|
||||
teardown_all context, false do
|
||||
unquote(module).__ex_unit__(:teardown_all, context)
|
||||
end
|
||||
|
||||
teardown context, false do
|
||||
unquote(module).__ex_unit__(:teardown, context)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -147,7 +147,6 @@ defmodule ExUnit.Runner do
|
||||
case exec_case_setup(test_case) do
|
||||
{:ok, test_case, context} ->
|
||||
Enum.each(tests, &run_test(config, &1, context))
|
||||
test_case = exec_case_teardown(test_case, context)
|
||||
send parent, {self, :case_finished, test_case, []}
|
||||
|
||||
{:error, test_case} ->
|
||||
@@ -179,15 +178,6 @@ defmodule ExUnit.Runner do
|
||||
{:error, %{test_case | state: failed}}
|
||||
end
|
||||
|
||||
defp exec_case_teardown(%ExUnit.TestCase{name: case_name} = test_case, context) do
|
||||
case_name.__ex_unit__(:teardown_all, context)
|
||||
test_case
|
||||
catch
|
||||
kind, error ->
|
||||
failed = {:failed, {kind, Exception.normalize(kind, error), pruned_stacktrace}}
|
||||
%{test_case | state: failed}
|
||||
end
|
||||
|
||||
defp run_test(config, test, context) do
|
||||
EM.test_started(config.manager, test)
|
||||
|
||||
@@ -209,8 +199,7 @@ defmodule ExUnit.Runner do
|
||||
:timer.tc(fn ->
|
||||
case exec_test_setup(test, context) do
|
||||
{:ok, test, context} ->
|
||||
test = exec_test(test, context)
|
||||
exec_test_teardown(test, context)
|
||||
exec_test(test, context)
|
||||
{:error, test} ->
|
||||
test
|
||||
end
|
||||
@@ -249,15 +238,6 @@ defmodule ExUnit.Runner do
|
||||
%{test | state: failed}
|
||||
end
|
||||
|
||||
defp exec_test_teardown(%ExUnit.Test{case: case} = test, context) do
|
||||
{:ok, _context} = case.__ex_unit__(:teardown, context)
|
||||
test
|
||||
catch
|
||||
kind, error ->
|
||||
state = test.state || {:failed, {kind, Exception.normalize(kind, error), pruned_stacktrace()}}
|
||||
%{test | state: state}
|
||||
end
|
||||
|
||||
defp exec_on_exit(test_or_case, pid) do
|
||||
case ExUnit.OnExitHandler.run(pid) do
|
||||
:ok ->
|
||||
|
||||
+51
-64
@@ -317,25 +317,33 @@ defmodule IEx do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `true` if IEx was properly started.
|
||||
Returns `true` if IEx was started.
|
||||
"""
|
||||
def started? do
|
||||
Application.get_env(:iex, :started, false)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns `string` escaped using the specified color.
|
||||
Returns `string` escaped using the specified `color`.
|
||||
|
||||
ANSI escapes in `string` are not processed in any way.
|
||||
"""
|
||||
def color(color_name, string) do
|
||||
def color(color, string) do
|
||||
colors = Application.get_env(:iex, :colors)
|
||||
enabled = colors[:enabled]
|
||||
IO.ANSI.escape_fragment("%{#{colors[color_name]}}", enabled)
|
||||
<> string <> IO.ANSI.escape_fragment("%{reset}", enabled)
|
||||
|
||||
if colors[:enabled] do
|
||||
ansi = Keyword.get(colors, color, default_color(color))
|
||||
IO.ANSI.escape_fragment("%{#{ansi}}", true) <> string <>
|
||||
IO.ANSI.escape_fragment("%{reset}", true)
|
||||
else
|
||||
string
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get the width to be used on helpers with a maximum (and default) of 80 chars.
|
||||
Gets the IEx width for printing.
|
||||
|
||||
Used by helpers and it has a maximum cap of 80 chars.
|
||||
"""
|
||||
def width do
|
||||
case :io.columns() do
|
||||
@@ -344,6 +352,14 @@ defmodule IEx do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the options used for inspecting.
|
||||
"""
|
||||
def inspect_opts do
|
||||
Application.get_env(:iex, :inspect) ++
|
||||
[width: width(), pretty: true]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Pries into the process environment.
|
||||
|
||||
@@ -360,8 +376,6 @@ defmodule IEx do
|
||||
of the module being pried. Module functions still need to be
|
||||
accessed via `Mod.fun(args)`.
|
||||
|
||||
Status: This feature is experimental.
|
||||
|
||||
## Examples
|
||||
|
||||
Let's suppose you want to investigate what is happening
|
||||
@@ -429,81 +443,34 @@ defmodule IEx do
|
||||
# This is a callback invoked by Erlang shell utilities
|
||||
# when someone press Ctrl+G and adds 's Elixir.IEx'.
|
||||
@doc false
|
||||
def start(opts \\ [], callback \\ fn -> end) do
|
||||
def start(opts \\ [], mfa \\ {IEx, :dont_display_result, []}) do
|
||||
spawn fn ->
|
||||
case :init.notify_when_started(self()) do
|
||||
:started -> :ok
|
||||
_ -> :init.wait_until_started()
|
||||
end
|
||||
|
||||
callback = start_iex(callback)
|
||||
start_iex()
|
||||
set_expand_fun()
|
||||
run_after_spawn()
|
||||
IEx.Server.start(opts, callback)
|
||||
IEx.Server.start(opts, mfa)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def dont_display_result, do: :"do not show this result in output"
|
||||
|
||||
@doc false
|
||||
def default_colors do
|
||||
[# Enabled should be commented as it is calculated if missing
|
||||
# enabled: true,
|
||||
|
||||
# Used by default on evaluation cycle
|
||||
eval_interrupt: "yellow",
|
||||
eval_result: "yellow",
|
||||
eval_error: "red",
|
||||
eval_info: "normal",
|
||||
stack_app: "red,bright",
|
||||
stack_info: "red",
|
||||
|
||||
# Used by ls
|
||||
ls_directory: "blue",
|
||||
ls_device: "green",
|
||||
|
||||
# Used by ansi docs
|
||||
doc_bold: "bright",
|
||||
doc_code: "cyan,bright",
|
||||
doc_headings: "yellow,bright",
|
||||
doc_inline_code: "cyan",
|
||||
doc_underline: "underline",
|
||||
doc_title: "reverse,yellow,bright"]
|
||||
end
|
||||
|
||||
@doc false
|
||||
def default_inspect do
|
||||
[structs: true, binaries: :infer,
|
||||
char_lists: :infer, limit: 50, pretty: true]
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp start_iex(callback) do
|
||||
if started? do
|
||||
callback
|
||||
else
|
||||
defp start_iex() do
|
||||
unless started? do
|
||||
Application.start(:elixir)
|
||||
Application.start(:iex)
|
||||
Application.put_env(:iex, :started, true)
|
||||
|
||||
fn ->
|
||||
# The callback may actually configure IEx (for example,
|
||||
# if it is a Mix project), so we wrap the original callback
|
||||
# so we can normalize options afterwards.
|
||||
callback.()
|
||||
|
||||
colors = default_colors
|
||||
|> Keyword.merge(Application.get_env(:iex, :colors))
|
||||
|> Keyword.put_new(:enabled, IO.ANSI.terminal?)
|
||||
|
||||
inspect = default_inspect
|
||||
|> Keyword.merge(Application.get_env(:iex, :inspect))
|
||||
|
||||
Application.put_env(:iex, :colors, colors)
|
||||
Application.put_env(:iex, :inspect, inspect)
|
||||
end
|
||||
colors = [enabled: IO.ANSI.terminal?] ++
|
||||
Application.get_env(:iex, :colors)
|
||||
Application.put_env(:iex, :colors, colors)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -531,4 +498,24 @@ defmodule IEx do
|
||||
defp run_after_spawn do
|
||||
for fun <- Enum.reverse(after_spawn), do: fun.()
|
||||
end
|
||||
|
||||
# Used by default on evaluation cycle
|
||||
defp default_color(:eval_interrupt), do: "yellow"
|
||||
defp default_color(:eval_result), do: "yellow"
|
||||
defp default_color(:eval_error), do: "red"
|
||||
defp default_color(:eval_info), do: "normal"
|
||||
defp default_color(:stack_app), do: "red,bright"
|
||||
defp default_color(:stack_info), do: "red"
|
||||
|
||||
# Used by ls
|
||||
defp default_color(:ls_directory), do: "blue"
|
||||
defp default_color(:ls_device), do: "green"
|
||||
|
||||
# Used by ansi docs
|
||||
defp default_color(:doc_bold), do: "bright"
|
||||
defp default_color(:doc_code), do: "cyan,bright"
|
||||
defp default_color(:doc_headings), do: "yellow,bright"
|
||||
defp default_color(:doc_inline_code), do: "cyan"
|
||||
defp default_color(:doc_underline), do: "underline"
|
||||
defp default_color(:doc_title), do: "reverse,yellow,bright"
|
||||
end
|
||||
|
||||
@@ -92,7 +92,12 @@ defmodule IEx.CLI do
|
||||
end
|
||||
|
||||
def local_start do
|
||||
IEx.start(config(), fn -> :elixir.start_cli end)
|
||||
IEx.start(config(), {:elixir, :start_cli, []})
|
||||
end
|
||||
|
||||
def remote_start(parent, ref) do
|
||||
send parent, {:begin, ref, self}
|
||||
receive do: ({:done, ^ref} -> :ok)
|
||||
end
|
||||
|
||||
defp local_start_function do
|
||||
@@ -112,10 +117,7 @@ defmodule IEx.CLI do
|
||||
end
|
||||
|
||||
fn ->
|
||||
IEx.start(config, fn ->
|
||||
send parent, {:begin, ref, self}
|
||||
receive do: ({:done, ^ref} -> :ok)
|
||||
end)
|
||||
IEx.start(config, {__MODULE__, :remote_start, [parent, ref]})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -57,15 +57,16 @@ defmodule IEx.Evaluator do
|
||||
defp eval_dot_iex(config, path) do
|
||||
try do
|
||||
code = File.read!(path)
|
||||
env = :elixir.env_for_eval(config.env, file: path)
|
||||
env = :elixir.env_for_eval(config.env, file: path, line: 1)
|
||||
|
||||
# Evaluate the contents in the same environment server_loop will run in
|
||||
{_result, binding, env, _scope} =
|
||||
:elixir.eval(String.to_char_list(code), config.binding, env)
|
||||
|
||||
%{config | binding: binding, env: :elixir.env_for_eval(env, file: "iex")}
|
||||
%{config | binding: binding, env: :elixir.env_for_eval(env, file: "iex", line: 1)}
|
||||
catch
|
||||
kind, error ->
|
||||
io_result "Error while evaluating: #{path}"
|
||||
print_error(kind, error, System.stacktrace)
|
||||
System.halt(1)
|
||||
end
|
||||
@@ -112,7 +113,7 @@ defmodule IEx.Evaluator do
|
||||
{:ok, forms} ->
|
||||
{result, new_binding, env, scope} =
|
||||
:elixir.eval_forms(forms, config.binding, config.env, config.scope)
|
||||
unless result == IEx.dont_display_result, do: io_put result
|
||||
unless result == IEx.dont_display_result, do: io_inspect(result)
|
||||
update_history(line, code, result)
|
||||
%{config | env: env,
|
||||
cache: '',
|
||||
@@ -136,18 +137,18 @@ defmodule IEx.Evaluator do
|
||||
Application.get_env(:iex, :history_size))
|
||||
end
|
||||
|
||||
defp io_put(result) do
|
||||
IO.puts :stdio, IEx.color(:eval_result, inspect(result, inspect_opts))
|
||||
defp io_inspect(result) do
|
||||
io_result inspect(result, IEx.inspect_opts)
|
||||
end
|
||||
|
||||
defp io_result(result) do
|
||||
IO.puts :stdio, IEx.color(:eval_result, result)
|
||||
end
|
||||
|
||||
defp io_error(result) do
|
||||
IO.puts :stdio, IEx.color(:eval_error, result)
|
||||
end
|
||||
|
||||
defp inspect_opts do
|
||||
[width: IEx.width] ++ Application.get_env(:iex, :inspect)
|
||||
end
|
||||
|
||||
## Error handling
|
||||
|
||||
defp print_error(kind, reason, stacktrace) do
|
||||
|
||||
@@ -237,7 +237,7 @@ defmodule IEx.Helpers do
|
||||
their results.
|
||||
"""
|
||||
def v do
|
||||
inspect_opts = Application.get_env(:iex, :inspect)
|
||||
inspect_opts = IEx.inspect_opts
|
||||
IEx.History.each(&print_history_entry(&1, inspect_opts))
|
||||
end
|
||||
|
||||
@@ -300,8 +300,7 @@ defmodule IEx.Helpers do
|
||||
Flushes all messages sent to the shell and prints them out.
|
||||
"""
|
||||
def flush do
|
||||
inspect_opts = Application.get_env(:iex, :inspect)
|
||||
do_flush(inspect_opts)
|
||||
do_flush(IEx.inspect_opts)
|
||||
end
|
||||
|
||||
defp do_flush(inspect_opts) do
|
||||
|
||||
@@ -76,9 +76,9 @@ defmodule IEx.Server do
|
||||
we spawn a new server for it without waiting for its
|
||||
conclusion.
|
||||
"""
|
||||
@spec start(list, fun) :: :ok
|
||||
def start(opts, callback) do
|
||||
{pid, ref} = spawn_monitor(callback)
|
||||
@spec start(list, mfa) :: :ok
|
||||
def start(opts, {m, f, a}) do
|
||||
{pid, ref} = spawn_monitor(m, f, a)
|
||||
start_loop(opts, pid, ref)
|
||||
end
|
||||
|
||||
|
||||
+2
-2
@@ -10,8 +10,8 @@ defmodule IEx.Mixfile do
|
||||
def application do
|
||||
[env: [
|
||||
after_spawn: [],
|
||||
colors: IEx.default_colors,
|
||||
inspect: IEx.default_inspect,
|
||||
colors: [],
|
||||
inspect: [],
|
||||
history_size: 20,
|
||||
default_prompt: "%prefix(%counter)>",
|
||||
alive_prompt: "%prefix(%node)%counter>"]]
|
||||
|
||||
@@ -7,7 +7,7 @@ defmodule IEx.InteractionTest do
|
||||
|
||||
test "whole output" do
|
||||
assert capture_io("IO.puts \"Hello world\"", fn ->
|
||||
IEx.Server.start([dot_iex_path: ""], fn -> end)
|
||||
IEx.Server.start([dot_iex_path: ""], {IEx, :dont_display_result, []})
|
||||
end) =~ "Interactive Elixir (#{System.version}) - press Ctrl+C to exit (type h() ENTER for help)" <>
|
||||
"\niex(1)> Hello world\n:ok\niex(2)>"
|
||||
end
|
||||
|
||||
@@ -55,6 +55,7 @@ defmodule IEx.ServerTest do
|
||||
# Helpers
|
||||
|
||||
defp boot(opts, callback \\ fn -> end) do
|
||||
IEx.Server.start(Keyword.merge([dot_iex_path: ""], opts), callback)
|
||||
IEx.Server.start(Keyword.merge([dot_iex_path: ""], opts),
|
||||
{:erlang, :apply, [callback, []]})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -34,7 +34,9 @@ defmodule IEx.Case do
|
||||
setup do
|
||||
opts = IEx.configuration |>
|
||||
Keyword.take([:default_prompt, :alive_prompt, :inspect, :colors, :history_size])
|
||||
on_exit fn -> IEx.configure(opts) end
|
||||
on_exit fn ->
|
||||
Enum.each opts, fn {k, v} -> Application.put_env(:iex, k, v) end
|
||||
end
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -53,7 +55,7 @@ defmodule IEx.Case do
|
||||
|
||||
ExUnit.CaptureIO.capture_io([input: input, capture_prompt: capture_prompt], fn ->
|
||||
server_options = Keyword.put_new(server_options, :dot_iex_path, "")
|
||||
IEx.Server.start(server_options, fn -> end)
|
||||
IEx.Server.start(server_options, {IEx, :dont_display_result, []})
|
||||
end) |> strip_iex
|
||||
end
|
||||
|
||||
|
||||
@@ -70,13 +70,13 @@ defmodule Mix.Archive do
|
||||
|
||||
defp files_to_add(path, dir) do
|
||||
File.cd! path, fn ->
|
||||
ebin = :filelib.wildcard('ebin/*.{beam,app}')
|
||||
priv = :filelib.wildcard('priv/**/*')
|
||||
ebin = Path.wildcard("ebin/*.{beam,app}")
|
||||
priv = Path.wildcard("priv/**/*")
|
||||
|
||||
Enum.reduce ebin ++ priv, [], fn(f, acc) ->
|
||||
case File.read(f) do
|
||||
{:ok, bin} ->
|
||||
[{:filename.join(dir, f), bin}|acc]
|
||||
[{Path.join(dir, f) |> String.to_char_list, bin}|acc]
|
||||
{:error, _} ->
|
||||
acc
|
||||
end
|
||||
|
||||
+6
-13
@@ -77,18 +77,11 @@ defmodule Mix.CLI do
|
||||
exception ->
|
||||
stacktrace = System.stacktrace
|
||||
|
||||
cond do
|
||||
Map.get(exception, :mix_error, false) ->
|
||||
IO.write :stderr, "warning: setting mix_error: true in Mix exceptions is deprecated, " <>
|
||||
"please define a `:mix` field and use Mix.raise to raise them"
|
||||
Mix.shell.error "** (Mix) #{Exception.message(exception)}"
|
||||
|
||||
info = Map.get(exception, :mix) ->
|
||||
mod = exception.__struct__ |> Module.split() |> Enum.at(0, "Mix")
|
||||
Mix.shell.error "** (#{mod})#{show_mix_info(info)} #{Exception.message(exception)}"
|
||||
|
||||
true ->
|
||||
reraise exception, stacktrace
|
||||
if info = Map.get(exception, :mix) do
|
||||
mod = exception.__struct__ |> Module.split() |> Enum.at(0, "Mix")
|
||||
Mix.shell.error "** (#{mod})#{show_mix_info(info)} #{Exception.message(exception)}"
|
||||
else
|
||||
reraise exception, stacktrace
|
||||
end
|
||||
|
||||
exit(1)
|
||||
@@ -111,7 +104,7 @@ defmodule Mix.CLI do
|
||||
end
|
||||
|
||||
defp load_dot_config do
|
||||
path = Path.expand("~/.mix/config.exs")
|
||||
path = Path.join(Mix.Utils.mix_home, "config.exs")
|
||||
if File.regular?(path) do
|
||||
Mix.Task.run "loadconfig", [path]
|
||||
end
|
||||
|
||||
@@ -107,23 +107,17 @@ defmodule Mix.Compilers.Elixir do
|
||||
|> Enum.map(&Atom.to_string(&1))
|
||||
|> Enum.reject(&match?("elixir_" <> _, &1))
|
||||
|
||||
files = get_beam_files(binary, cwd)
|
||||
|> List.delete(source)
|
||||
|> Enum.filter(&(Path.type(&1) == :relative))
|
||||
files = for file <- get_external_resources(module),
|
||||
File.regular?(file),
|
||||
relative = Path.relative_to(file, cwd),
|
||||
Path.type(relative) == :relative,
|
||||
do: relative
|
||||
|
||||
Agent.cast pid, &:lists.keystore(beam, 1, &1, {beam, bin, source, deps, files, binary})
|
||||
end
|
||||
|
||||
defp get_beam_files(binary, cwd) do
|
||||
case :beam_lib.chunks(binary, [:abstract_code]) do
|
||||
{:ok, {_, [abstract_code: {:raw_abstract_v1, code}]}} ->
|
||||
for {:attribute, _, :file, {file, _}} <- code,
|
||||
File.exists?(file) do
|
||||
Path.relative_to(file, cwd)
|
||||
end
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
defp get_external_resources(module) do
|
||||
module.__info__(:attributes)[:external_resource] || []
|
||||
end
|
||||
|
||||
defp each_file(file) do
|
||||
|
||||
@@ -77,10 +77,10 @@ defmodule Mix.Dep.Fetcher do
|
||||
end
|
||||
|
||||
defp out_of_date?(%Mix.Dep{status: {:lockmismatch, _}}), do: true
|
||||
defp out_of_date?(%Mix.Dep{status: :lockoutdated}), do: true
|
||||
defp out_of_date?(%Mix.Dep{status: :nolock}), do: true
|
||||
defp out_of_date?(%Mix.Dep{status: :lockoutdated}), do: true
|
||||
defp out_of_date?(%Mix.Dep{status: :nolock}), do: true
|
||||
defp out_of_date?(%Mix.Dep{status: {:unavailable, _}}), do: true
|
||||
defp out_of_date?(%Mix.Dep{}), do: false
|
||||
defp out_of_date?(%Mix.Dep{}), do: false
|
||||
|
||||
defp do_finalize({all_deps, apps, new_lock}, old_lock, opts) do
|
||||
# Let's get the loaded versions of deps
|
||||
|
||||
@@ -165,17 +165,19 @@ defmodule Mix.Dep.Loader do
|
||||
defp ok?(_), do: false
|
||||
|
||||
defp mix?(dest) do
|
||||
File.regular?(Path.join(dest, "mix.exs"))
|
||||
any_of?(dest, ["mix.exs"])
|
||||
end
|
||||
|
||||
defp rebar?(dest) do
|
||||
Enum.any?(["rebar.config", "rebar.config.script"], fn file ->
|
||||
File.regular?(Path.join(dest, file))
|
||||
end) or File.regular?(Path.join(dest, "rebar"))
|
||||
any_of?(dest, ["rebar", "rebar.config", "rebar.config.script"])
|
||||
end
|
||||
|
||||
defp make?(dest) do
|
||||
File.regular? Path.join(dest, "Makefile")
|
||||
any_of?(dest, ["Makefile", "Makefile.win"])
|
||||
end
|
||||
|
||||
defp any_of?(dest, files) do
|
||||
Enum.any?(files, &File.regular?(Path.join(dest, &1)))
|
||||
end
|
||||
|
||||
defp invalid_dep_format(dep) do
|
||||
|
||||
@@ -36,14 +36,7 @@ defmodule Mix.Generator do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads the content from a file relative to the current
|
||||
file and not relative to the cwd. Useful when used with
|
||||
embed macros:
|
||||
|
||||
embed_template :lib, from_file("../templates/lib.eex")
|
||||
|
||||
"""
|
||||
@doc false
|
||||
defmacro from_file(path) do
|
||||
quote do
|
||||
File.read! Path.expand(unquote(path), __ENV__.file)
|
||||
@@ -63,9 +56,21 @@ defmodule Mix.Generator do
|
||||
For more information, check `EEx.SmartEngine`.
|
||||
"""
|
||||
defmacro embed_template(name, contents) do
|
||||
quote do
|
||||
quote bind_quoted: binding do
|
||||
contents =
|
||||
case contents do
|
||||
[from_file: file] ->
|
||||
@file file
|
||||
File.read!(file)
|
||||
c when is_binary(c) ->
|
||||
@file {__ENV__.file, __ENV__.line+1}
|
||||
c
|
||||
_ ->
|
||||
raise ArgumentError, "expected string or from_file: file"
|
||||
end
|
||||
|
||||
require EEx
|
||||
EEx.function_from_string :defp, :"#{unquote(name)}_template", "<% _ = assigns %>" <> unquote(contents), [:assigns]
|
||||
EEx.function_from_string :defp, :"#{name}_template", "<% _ = assigns %>" <> contents, [:assigns]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -76,7 +81,13 @@ defmodule Mix.Generator do
|
||||
`_text` that expects no argument.
|
||||
"""
|
||||
defmacro embed_text(name, contents) do
|
||||
quote bind_quoted: [name: name, contents: Macro.escape(contents)] do
|
||||
quote bind_quoted: binding do
|
||||
contents =
|
||||
case contents do
|
||||
[from_file: f] -> File.read!(f)
|
||||
c when is_binary(c) -> c
|
||||
_ -> raise ArgumentError, "expected string or from_file: file"
|
||||
end
|
||||
defp unquote(:"#{name}_text")(), do: unquote(contents)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,9 +3,18 @@ defmodule Mix.Local do
|
||||
|
||||
@doc """
|
||||
The path for local archives.
|
||||
|
||||
It checks the `MIX_ARCHIVES` variable or it uses the
|
||||
"archives" directory inside `Mix.Utils.mix_home/0`.
|
||||
|
||||
Since archives are specific to Elixir versions, it is
|
||||
expected from build tools to swap the `MIX_ARCHIVES`
|
||||
variable to different locations based on a particular
|
||||
Elixir installation.
|
||||
"""
|
||||
def archives_path do
|
||||
Path.join Mix.Utils.mix_home, "archives"
|
||||
System.get_env("MIX_ARCHIVES") ||
|
||||
Path.join(Mix.Utils.mix_home, "archives")
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
+29
-10
@@ -36,14 +36,13 @@ defmodule Mix.SCM.Git do
|
||||
end
|
||||
|
||||
def checked_out?(opts) do
|
||||
File.dir?(Path.join(opts[:dest], ".git")) &&
|
||||
File.cd!(opts[:dest], fn ->
|
||||
# Make sure git can read the dependency .git folder
|
||||
String.strip(System.cmd "git rev-parse --git-dir") == ".git"
|
||||
end)
|
||||
# Are we inside a git repository?
|
||||
File.regular?(Path.join(opts[:dest], ".git/HEAD"))
|
||||
end
|
||||
|
||||
def lock_status(opts) do
|
||||
assert_git
|
||||
|
||||
case opts[:lock] do
|
||||
{:git, lock_repo, lock_rev, lock_opts} ->
|
||||
File.cd!(opts[:dest], fn ->
|
||||
@@ -69,6 +68,8 @@ defmodule Mix.SCM.Git do
|
||||
end
|
||||
|
||||
def checkout(opts) do
|
||||
assert_git
|
||||
|
||||
path = opts[:dest]
|
||||
location = opts[:git]
|
||||
|
||||
@@ -80,12 +81,14 @@ defmodule Mix.SCM.Git do
|
||||
end
|
||||
|
||||
def update(opts) do
|
||||
assert_git
|
||||
|
||||
File.cd! opts[:dest], fn ->
|
||||
# Ensures origin is set the lock repo
|
||||
location = opts[:git]
|
||||
update_origin(location)
|
||||
|
||||
command = "git fetch --force"
|
||||
command = "git --git-dir=.git fetch --force"
|
||||
|
||||
if {1, 7, 1} <= git_version() do
|
||||
command = command <> " --progress"
|
||||
@@ -104,10 +107,10 @@ defmodule Mix.SCM.Git do
|
||||
|
||||
defp do_checkout(opts) do
|
||||
ref = get_lock_rev(opts[:lock]) || get_opts_rev(opts)
|
||||
run_cmd_or_raise "git checkout --quiet #{ref}"
|
||||
run_cmd_or_raise "git --git-dir=.git checkout --quiet #{ref}"
|
||||
|
||||
if opts[:submodules] do
|
||||
run_cmd_or_raise "git submodule update --init --recursive"
|
||||
run_cmd_or_raise "git --git-dir=.git submodule update --init --recursive"
|
||||
end
|
||||
|
||||
get_lock(opts)
|
||||
@@ -141,14 +144,14 @@ defmodule Mix.SCM.Git do
|
||||
|
||||
defp get_rev_info do
|
||||
destructure [origin, rev],
|
||||
System.cmd('git config remote.origin.url && git rev-parse --verify --quiet HEAD')
|
||||
System.cmd('git --git-dir=.git config remote.origin.url && git --git-dir=.git rev-parse --verify --quiet HEAD')
|
||||
|> IO.iodata_to_binary
|
||||
|> String.split("\n", trim: true)
|
||||
[ origin: origin, rev: rev ]
|
||||
end
|
||||
|
||||
defp update_origin(location) do
|
||||
System.cmd('git config remote.origin.url #{location}')
|
||||
System.cmd('git --git-dir=.git config remote.origin.url #{location}')
|
||||
end
|
||||
|
||||
defp run_cmd_or_raise(command) do
|
||||
@@ -158,6 +161,22 @@ defmodule Mix.SCM.Git do
|
||||
true
|
||||
end
|
||||
|
||||
defp assert_git do
|
||||
case Application.fetch_env(:mix, :git_available) do
|
||||
{:ok, true} ->
|
||||
:ok
|
||||
:error ->
|
||||
if :os.find_executable('git') == false do
|
||||
Mix.raise "Error fetching/updating Git repository: the `git` " <>
|
||||
"executable is not available in your PATH. Please install " <>
|
||||
"Git on this machine or pass --no-deps-check if you want to " <>
|
||||
"run a previously built application on a system without Git."
|
||||
else
|
||||
Application.put_env(:mix, :git_available, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp git_version do
|
||||
case Application.fetch_env(:mix, :git_version) do
|
||||
{:ok, version} ->
|
||||
|
||||
@@ -36,7 +36,7 @@ defmodule Mix.Tasks.Compile do
|
||||
end
|
||||
|
||||
max = Enum.reduce docs, 0, fn({task, _}, acc) ->
|
||||
max(size(task), acc)
|
||||
max(byte_size(task), acc)
|
||||
end
|
||||
|
||||
sorted = Enum.sort(docs)
|
||||
|
||||
@@ -9,17 +9,18 @@ defmodule Mix.Tasks.Deps.Compile do
|
||||
By default, compile all dependencies. A list of dependencies can
|
||||
be given to force the compilation of specific dependencies.
|
||||
|
||||
By default, attempt to detect if the project contains one of
|
||||
the following files:
|
||||
This task attempts to detect if the project contains one of
|
||||
the following files and act accordingly:
|
||||
|
||||
* `mix.exs` - if so, invokes `mix compile`
|
||||
* `rebar.config` - if so, invokes `rebar compile`
|
||||
* `Makefile` - if so, invokes `make`
|
||||
* `mix.exs` - invokes `mix compile`
|
||||
* `rebar.config` - invokes `rebar compile`
|
||||
* `Makefile.win` - invokes `nmake /F Makefile.win` (only on Windows)
|
||||
* `Makefile` - invokes `make` (except on Windows)
|
||||
|
||||
The compilation can be customized by passing a `compile` option
|
||||
in the dependency:
|
||||
|
||||
{:some_dependency, "0.1.0", git: "...", compile: "command to compile"}
|
||||
{:some_dependency, "0.1.0", compile: "command to compile"}
|
||||
|
||||
"""
|
||||
|
||||
@@ -123,7 +124,11 @@ defmodule Mix.Tasks.Deps.Compile do
|
||||
end
|
||||
|
||||
defp do_make(dep) do
|
||||
do_command(dep, "make")
|
||||
if match?({:win32, _}, :os.type) and File.regular?("Makefile.win") do
|
||||
do_command(dep, "nmake /F Makefile.win")
|
||||
else
|
||||
do_command(dep, "make")
|
||||
end
|
||||
end
|
||||
|
||||
defp do_compile(%Mix.Dep{app: app, opts: opts} = dep) do
|
||||
|
||||
@@ -42,13 +42,13 @@ defmodule Mix.Tasks.Escriptize do
|
||||
Defaults to `[]`.
|
||||
|
||||
* `:shebang` - shebang interpreter directive used to execute the escript.
|
||||
Defaults to "#! /usr/bin/env escript\n".
|
||||
Defaults to `"#! /usr/bin/env escript\n"`.
|
||||
|
||||
* `:comment` - comment line to follow shebang directive in the escript.
|
||||
Defaults to "%%\n".
|
||||
Defaults to `""`.
|
||||
|
||||
* `:emu_args` - emulator arguments to embed in the escript file.
|
||||
Defaults to "%%!\n".
|
||||
Defaults to `""`.
|
||||
|
||||
## Example
|
||||
|
||||
@@ -79,38 +79,18 @@ defmodule Mix.Tasks.Escriptize do
|
||||
escriptize(Mix.Project.config, opts[:force])
|
||||
end
|
||||
|
||||
|
||||
@deprecated_opts [
|
||||
:escript_main_module, :escript_name, :escript_path, :escript_app,
|
||||
:escript_embed_elixir, :escript_embed_extra_apps, :escript_shebang,
|
||||
:escript_comment, :escript_mu_args, ]
|
||||
|
||||
@prefix_len String.length("escript_")
|
||||
|
||||
defp collect_deprecated_opts(project) do
|
||||
Enum.reduce(@deprecated_opts, [], fn name, acc ->
|
||||
if Keyword.has_key?(project, name) do
|
||||
IO.puts :stderr, "Option #{inspect name} is deprecated. " <>
|
||||
"Use the new `:escript` option that takes a keyword list instead."
|
||||
new_name =
|
||||
Atom.to_string(name) |> String.slice(@prefix_len, 100) |> String.to_atom()
|
||||
[{new_name, project[name]}|acc]
|
||||
else
|
||||
acc
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp escriptize(project, force) do
|
||||
escript_opts = project[:escript] || collect_deprecated_opts(project)
|
||||
escript_opts = project[:escript] || []
|
||||
|
||||
script_name = escript_opts[:name] || project[:app]
|
||||
filename = escript_opts[:path] || Atom.to_string(script_name)
|
||||
script_name = to_string(escript_opts[:name] || project[:app])
|
||||
filename = escript_opts[:path] || script_name
|
||||
main = escript_opts[:main_module]
|
||||
embed = Keyword.get(escript_opts, :embed_elixir, true)
|
||||
app = Keyword.get(escript_opts, :app, project[:app])
|
||||
files = project_files()
|
||||
|
||||
escript_mod = String.to_atom(Atom.to_string(app) <> "-escript-main")
|
||||
|
||||
cond do
|
||||
!script_name ->
|
||||
Mix.raise "Could not generate escript, no name given, " <>
|
||||
@@ -121,7 +101,7 @@ defmodule Mix.Tasks.Escriptize do
|
||||
"in your project configuration (under `:escript` option) to a module that implements main/1"
|
||||
|
||||
force || Mix.Utils.stale?(files, [filename]) ->
|
||||
tuples = gen_main(script_name, main, app) ++ to_tuples(files)
|
||||
tuples = gen_main(escript_mod, main, app) ++ to_tuples(files)
|
||||
tuples = tuples ++ deps_tuples()
|
||||
|
||||
if embed do
|
||||
@@ -138,8 +118,8 @@ defmodule Mix.Tasks.Escriptize do
|
||||
case :zip.create 'mem', tuples, [:memory] do
|
||||
{:ok, {'mem', zip}} ->
|
||||
shebang = escript_opts[:shebang] || "#! /usr/bin/env escript\n"
|
||||
comment = escript_opts[:comment] || "%%\n"
|
||||
emu_args = escript_opts[:emu_args] || "%%!\n"
|
||||
comment = build_comment(escript_opts[:comment])
|
||||
emu_args = build_emu_args(escript_opts[:emu_args], escript_mod)
|
||||
|
||||
script = IO.iodata_to_binary([shebang, comment, emu_args, zip])
|
||||
|
||||
@@ -195,6 +175,26 @@ defmodule Mix.Tasks.Escriptize do
|
||||
end
|
||||
end
|
||||
|
||||
defp build_comment("%%" <> user_comment) do
|
||||
IO.puts :stderr, "Including the '%%' marker as part of :comment value " <>
|
||||
"is deprecated. Pass a string with just the comment."
|
||||
build_comment(user_comment)
|
||||
end
|
||||
|
||||
defp build_comment(user_comment) do
|
||||
"%% #{user_comment}\n"
|
||||
end
|
||||
|
||||
defp build_emu_args("%%!" <> user_args, escript_mod) do
|
||||
IO.puts :stderr, "Including the '%%!' marker as part of :emu_args value " <>
|
||||
"is deprecated. Pass a string with just arguments."
|
||||
build_emu_args(user_args, escript_mod)
|
||||
end
|
||||
|
||||
defp build_emu_args(user_args, escript_mod) do
|
||||
"%%! -escript main #{escript_mod} #{user_args}\n"
|
||||
end
|
||||
|
||||
defp gen_main(name, module, app) do
|
||||
{:module, ^name, binary, _} =
|
||||
defmodule name do
|
||||
|
||||
@@ -47,7 +47,7 @@ defmodule Mix.Tasks.Help do
|
||||
end
|
||||
|
||||
max = Enum.reduce docs, 0, fn({task, _}, acc) ->
|
||||
max(size(task), acc)
|
||||
max(byte_size(task), acc)
|
||||
end
|
||||
|
||||
display_default_task_doc(max)
|
||||
|
||||
@@ -17,7 +17,7 @@ defmodule Mix.Tasks.Local do
|
||||
end
|
||||
|
||||
max = Enum.reduce docs, 0, fn({task, _}, acc) ->
|
||||
max(size(task), acc)
|
||||
max(byte_size(task), acc)
|
||||
end
|
||||
|
||||
sorted = Enum.sort(docs)
|
||||
|
||||
@@ -2,7 +2,7 @@ defmodule Mix.Tasks.Local.Hex do
|
||||
use Mix.Task
|
||||
|
||||
@hex_url "https://hex.pm/installs/hex.ez"
|
||||
@hex_requirement ">= 0.2.5"
|
||||
@hex_requirement ">= 0.3.0"
|
||||
|
||||
@shortdoc "Install hex locally"
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user