Compare commits

...
77 Commits
Author SHA1 Message Date
José Valim 44b0412042 Release v0.14.2 2014-06-29 11:39:33 +02:00
José Valim d984a82587 Merge pull request #2463 from hamiltop/master
Update documentation on Kernel.var!/2
2014-06-29 11:13:40 +02:00
hamiltop e59d50a891 Update documentation on Kernel.var!/2
The documentation is no longer accurate since passing atoms to `Kernel.var!/2` has been deprecated and removed.
2014-06-28 22:00:29 -07:00
Eric Meadows-Jönsson 7bf568d0b3 Bump Hex version requirement 2014-06-28 16:47:30 +02:00
José Valim f95cafe5ca Fix regression when case is used inside cond head 2014-06-28 16:31:03 +02:00
José Valim f0e1443f21 Use iodata_to_binary in join/2 and map_join/3
Everything is previously converted to a string using
the String.Chars protocol, so we can just treat it
as iodata.
2014-06-28 14:33:20 +02:00
José Valim cf619585a6 No need to reverse a list when building an iolist 2014-06-28 12:21:17 +02:00
José Valim c639b01146 Update CHANGELOG 2014-06-28 12:02:58 +02:00
José Valim b4415f2a14 Change precedence of & to not special case / 2014-06-28 11:16:51 +02:00
José Valim 538b51c20a Remove usage of :filelib functions
This is to work around a regression in 17.1 where many
:filelib functions were broken. More information read:

http://erlang.org/pipermail/erlang-bugs/2014-June/004465.html
2014-06-27 23:00:18 +02:00
José Valim c8a0158791 Add @external_resource attribute, closes #2455 2014-06-27 22:16:34 +02:00
José Valim b088d4b90e Also filter out .ps1 files 2014-06-26 20:10:47 +02:00
José Valim 02a415facd Add MIX_ARCHIVES env variable 2014-06-26 19:48:32 +02:00
José Valim 5cee19067d Enum.join/2 and Enum.map_join/3 now uses iolists 2014-06-26 18:41:12 +02:00
José Valim 3496681236 Merge pull request #2457 from mururu/args-for-bodyless
Ensure we can only use variables and `\\` as arguments of bodyless clause
2014-06-26 17:07:00 +02:00
Yuki Ito a7645a2c8b Ensure we can only use variables and \\ as arguments of bodyless clause, closes #2374 2014-06-26 23:06:49 +09:00
José Valim d9423627f3 Document the role of MIX_HOME more explicitly 2014-06-26 14:48:44 +02:00
José Valim 5a8520fb0f Ensure we show a consistent error when we cannot evaluate .iex.exs 2014-06-26 14:41:23 +02:00
José Valim 4458ee1232 Remove --gen-debug option 2014-06-26 13:35:54 +02:00
José Valim a6b1cca91f Soft deprecate Mix.Generator.from_file/1 2014-06-26 13:35:54 +02:00
José Valim e2535731c8 Merge pull request #2453 from jwarwick/specialform_typo
Fixed typos in Kernel.SpecialForms docs
2014-06-25 22:01:35 +02:00
John Warwick 393ff69b1e Fixed typos in Kernel.SpecialForms docs 2014-06-25 15:49:16 -04:00
José Valim 033747ad63 Fix changelog message typo 2014-06-25 13:15:01 +02:00
José Valim 730d043631 Update CHANGELOG 2014-06-25 12:38:35 +02:00
José Valim c819bdbf74 Raise CondClauseError for cond clause mismatch 2014-06-25 01:46:56 +02:00
José Valim 64f8541ee6 Make cond a special form so it does not leak vars on head 2014-06-25 01:46:56 +02:00
Eric Meadows-Jönsson d1f4e2d49b Simplify .git directory check 2014-06-25 01:16:53 +02:00
Eric Meadows-Jönsson 5a13aecbae Error if git executable is not available 2014-06-25 00:15:11 +02:00
Eric Meadows-Jönsson 61904ee24c Remove git rev-parse check for valid .git directory
Instead pass `—git-dir=.git` to all git commands to ensure that the
command will only affect the local directory.
2014-06-25 00:14:35 +02:00
José Valim 5c7fdcc244 Soft deprecate EEx.TransformerEngine and EEx.AssignsEngine 2014-06-24 13:51:17 +02:00
José Valim f772877039 Update CHANGELOG 2014-06-24 13:28:41 +02:00
José Valim e571218917 Sigils no longer balance start and end tokens, closes #2284 2014-06-24 13:17:43 +02:00
José Valim aa01dd1fc1 Ensure derived protocols are defined with a file 2014-06-24 11:28:27 +02:00
José Valim 30971c9cb3 Merge pull request #2447 from jwarwick/task_docs
Fixing doc typos in Task
2014-06-23 20:16:09 +02:00
John Warwick abfc69a6e2 Fixing doc typos in Task 2014-06-23 14:02:31 -04:00
José Valim ac1fc484e4 Registered name returns an empty list, as in Erlang, to avoid ambiguity 2014-06-23 17:20:28 +02:00
José Valim 889bfebe4e Convert build_info to a map 2014-06-22 23:38:20 +02:00
José Valim e9899a5462 Fix local.install description 2014-06-22 18:00:47 +02:00
José Valim ec92bb118d Merge pull request #2444 from alco/kernel-cli-paths
Support paths inside archives in -pa and -pz options. Closes #2442
2014-06-22 14:38:55 +02:00
Alexei Sholik fd9ca435b7 Support paths inside archives in -pa and -pz options. Closes #2442 2014-06-22 15:26:36 +03:00
José Valim 3ec0d20621 Only set encoding when there is noshell, closes #2432 2014-06-22 14:16:40 +02:00
José Valim 16f3b928a1 Do not set -noshell if we are going to override it afterwards 2014-06-22 14:16:40 +02:00
José Valim fb20b6ddf6 Merge pull request #2443 from alco/doc-typo
Remove a stray XML tag from the path docs
2014-06-22 14:15:53 +02:00
Alexei Sholik d77a4d6c3f Remove a stray XML tag from the path docs 2014-06-22 14:44:58 +03:00
José Valim d7900239c2 Improve docs and defaults for File 2014-06-22 12:13:21 +02:00
José Valim babd98be3f Use mfa when starting remote nodes so it works with any elixir version 2014-06-21 20:43:16 +02:00
José Valim 9a85b1556b Merge pull request #2440 from tallakt/stream_unfold_typo
Fix unclear doc in Stream.unfold
2014-06-21 16:15:06 +02:00
Tallak Tveide d1da200af5 Fix unclear doc in Stream.unfold 2014-06-21 15:05:20 +02:00
José Valim e260b92f89 Merge pull request #2431 from alco/escript-name
Mention the fact that :escript_name is also used as a module name
2014-06-21 11:12:55 +02:00
José Valim e09e5c3f41 Merge pull request #2438 from jwarwick/helper_warning
Cleaning up IEx.Helpers.Flush warning
2014-06-21 11:12:01 +02:00
John Warwick a76a281309 Cleaning up IEx.Helpers.Flush warning 2014-06-20 22:09:28 -04:00
Alexei Sholik be42d00067 Update escript options: :name, :comment, :emu_args 2014-06-21 01:07:34 +03:00
José Valim f21a998f81 Merge pull request #2437 from lexmag/uri-parse
Let `URI.parse/1` work with `%URI{}`
2014-06-20 23:01:41 +02:00
Aleksei Magusev 89e0018d58 Let URI.parse/1 work with %URI{} 2014-06-21 00:37:16 +04:00
José Valim 21b691cd4e Do not expect options to have a certain format in the environment 2014-06-20 21:14:02 +02:00
José Valim a8a91c2282 Merge pull request #2435 from chyndman/makefile-win-epmd
workaround 'epmd -daemon' not working on Windows
2014-06-20 20:10:55 +02:00
José Valim 5275646966 Merge pull request #2433 from chyndman/contrib-win-update
Add extra Path directory, remove PowerShell warning
2014-06-20 20:10:34 +02:00
Chris Hyndman e60cd6a484 workaround 'epmd -daemon' not working on Windows 2014-06-20 10:53:23 -07:00
José Valim 552006bea3 Merge pull request #2434 from jwarwick/agent_doc
Fixed doc typos in Agent and File.Stream
2014-06-20 19:51:30 +02:00
John Warwick 2077b584ad Fixed doc typos in Agent and File.Stream 2014-06-20 13:22:25 -04:00
Chris Hyndman 29f05fda35 Add extra Path directory, remove PowerShell warning 2014-06-20 10:09:55 -07:00
Eric Meadows-Jönsson cbb7c545e0 Fix size/1 warnings 2014-06-20 14:24:36 +02:00
José Valim c909267077 Merge pull request #2429 from jwarwick/application_doc
Fixing typos in Application doc
2014-06-19 22:01:56 +02:00
John Warwick a9bbabfabc Fixing typos in Application doc 2014-06-19 15:49:31 -04:00
José Valim c4d666a4ba Fix typo on Access docs 2014-06-19 16:24:13 +02:00
José Valim 8e824d3da1 Merge pull request #2427 from fishcakez/task_init_call
Set :proc_lib initial call on task
2014-06-19 11:09:39 +02:00
Eric Meadows-Jönsson 41caa0d635 Merge pull request #2428 from knewter/feature/deriving_typofix
Fix typo in deriving docs
2014-06-19 01:39:28 +02:00
Josh Adams 3a0108edf8 Fix typo in deriving docs 2014-06-18 18:22:05 -05:00
James Fish 7123fb9d09 Set :proc_lib initial call on task
Sets the :proc_lib initial call to the {mod, fun, arity} of the task.

This allows :proc_lib.translate_initial_call(task.pid) to show the mfa
of a task process when debugging. Tools such as :observer and the erlang
shell make use of this function when getting information on a process.

This gives a useful initial call in the crash report sent to the error
logger when a task raises.
2014-06-18 22:58:20 +01:00
José Valim 6d8e118192 Check if Makefile.win is available before running nmake 2014-06-18 23:17:56 +02:00
José Valim 846703b31c Merge pull request #2426 from chyndman/mix-make-manager-win-nmake
make manager calls nmake /F Makefile.win on Windows
2014-06-18 23:07:52 +02:00
Chris Hyndman 46905b39a7 make manager calls nmake /F Makefile.win on Windows 2014-06-18 12:20:11 -07:00
Eric Meadows-Jönsson 0f43aca5bc Merge pull request #2424 from ericmj/delay-typespecs
Delay typespec compilation to after expansion
2014-06-18 20:01:17 +02:00
Eric Meadows-Jönsson 587c83cbff Delay typespec compilation to after expansion 2014-06-18 19:49:57 +02:00
José Valim e8194b1764 Fix wrong changelog entry 2014-06-18 16:57:20 +02:00
José Valim feb9cb645e Remove previously deprecated behaviour 2014-06-18 16:56:59 +02:00
José Valim 29f9a4fd67 Start v0.14.2-dev 2014-06-18 16:16:18 +02:00
108 changed files with 1756 additions and 1512 deletions
+34 -1
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -1 +1 @@
0.14.1
0.14.2
+2 -6
View File
@@ -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
View File
@@ -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 %*
)
-1
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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 """
+46 -1
View File
@@ -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.
+28 -43
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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.
+9 -9
View File
@@ -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).
+3 -3
View File
@@ -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
View File
@@ -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
+12
View File
@@ -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
View File
@@ -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 """
+74
View File
@@ -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
+96
View File
@@ -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
+2 -4
View File
@@ -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 """
+2 -2
View File
@@ -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
+5
View File
@@ -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
View File
@@ -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
+15 -6
View File
@@ -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
+27 -7
View File
@@ -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
+77 -71
View File
@@ -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
+21 -5
View File
@@ -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)
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+9 -7
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -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)
+17
View File
@@ -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)
+9 -6
View File
@@ -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
+2
View File
@@ -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
View File
@@ -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
+24 -17
View File
@@ -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};
+1
View File
@@ -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">>,
+54 -29
View File
@@ -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]).
+19 -14
View File
@@ -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.
+19 -58
View File
@@ -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.
+37 -2
View File
@@ -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).
+56 -36
View File
@@ -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),
+1 -1
View File
@@ -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}.
+1 -1
View File
@@ -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 ->
+1
View File
@@ -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;
+18 -38
View File
@@ -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.
+105 -43
View File
@@ -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}) ->
+12 -9
View File
@@ -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]}]};
+9 -11
View File
@@ -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;
+8 -3
View File
@@ -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);
+61 -34
View File
@@ -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}) ->
+14 -24
View 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]};
+26 -16
View File
@@ -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) ->
+1 -1
View File
@@ -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 ->
+14 -6
View File
@@ -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
+3 -2
View File
@@ -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
+224 -195
View File
@@ -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
"""
+1 -1
View File
@@ -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) ==
+5
View File
@@ -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{}, []}
+1
View File
@@ -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
+35 -7
View File
@@ -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
+4
View File
@@ -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,
+1 -31
View File
@@ -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.
-8
View File
@@ -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
+1 -21
View File
@@ -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
View File
@@ -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
+7 -5
View File
@@ -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
+10 -9
View File
@@ -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
+2 -3
View File
@@ -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
+3 -3
View File
@@ -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
View File
@@ -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>"]]
+1 -1
View File
@@ -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
+2 -1
View File
@@ -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
+4 -2
View File
@@ -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
+3 -3
View File
@@ -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
View File
@@ -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
+7 -13
View File
@@ -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
+3 -3
View File
@@ -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
+7 -5
View File
@@ -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
+22 -11
View File
@@ -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
+10 -1
View File
@@ -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
View File
@@ -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} ->
+1 -1
View File
@@ -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)
+12 -7
View File
@@ -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
+31 -31
View File
@@ -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
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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