Compare commits
155
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82535feb90 | ||
|
|
81f007bdd7 | ||
|
|
0b35996d5e | ||
|
|
fc5cf506ae | ||
|
|
c4582e0041 | ||
|
|
c80d215674 | ||
|
|
3ee9d5d0df | ||
|
|
d86ba0e1c7 | ||
|
|
c295ee50de | ||
|
|
54c25a55fe | ||
|
|
583a4ec5f6 | ||
|
|
69b78dad51 | ||
|
|
0685e0315a | ||
|
|
5e429001d9 | ||
|
|
707b810f1b | ||
|
|
e51cfc142d | ||
|
|
b259e793bb | ||
|
|
32738a97d9 | ||
|
|
45c6fbaced | ||
|
|
bba4f1bd1b | ||
|
|
ade2596e92 | ||
|
|
0e804a2b5c | ||
|
|
33ab1679f3 | ||
|
|
f2b12633f3 | ||
|
|
5a4b2769ee | ||
|
|
2439bae409 | ||
|
|
6ed7e40d03 | ||
|
|
2fc76fe1b0 | ||
|
|
e541a56130 | ||
|
|
6378750b12 | ||
|
|
fc25f60718 | ||
|
|
3b351e7650 | ||
|
|
999a535603 | ||
|
|
a726dd812b | ||
|
|
58d54afac6 | ||
|
|
001d776176 | ||
|
|
4bcd25fb4d | ||
|
|
f0fa9e66a6 | ||
|
|
12f9bf09b0 | ||
|
|
bf8f5a995f | ||
|
|
d3a3feace4 | ||
|
|
30331b2bea | ||
|
|
f5bf9d9659 | ||
|
|
2865bd8653 | ||
|
|
4513b6965f | ||
|
|
b47e8c61d7 | ||
|
|
40bf079566 | ||
|
|
2eaf854e79 | ||
|
|
7c12cf029c | ||
|
|
597d90b326 | ||
|
|
5dcad26b79 | ||
|
|
f8c2d5cb4f | ||
|
|
b0f432b2f8 | ||
|
|
651a4bad67 | ||
|
|
f52e350adc | ||
|
|
35e8019447 | ||
|
|
a6fab4a111 | ||
|
|
64d62d2472 | ||
|
|
10b322aa48 | ||
|
|
981955301d | ||
|
|
ac425f04ef | ||
|
|
1dda1563da | ||
|
|
0e71b1e711 | ||
|
|
841f6f2387 | ||
|
|
2eb7fb97de | ||
|
|
e781983e6b | ||
|
|
7d17a12783 | ||
|
|
d888057689 | ||
|
|
76d6e05399 | ||
|
|
1bd0ca6dea | ||
|
|
8e1f7fddca | ||
|
|
abda47903b | ||
|
|
030e72c9c6 | ||
|
|
88dbad9650 | ||
|
|
07263b8533 | ||
|
|
f67741b6dc | ||
|
|
4724ff12e2 | ||
|
|
f7c7ff46b4 | ||
|
|
c4868f8549 | ||
|
|
85951052ca | ||
|
|
28bd15b76c | ||
|
|
1fdb715967 | ||
|
|
0279547232 | ||
|
|
7fabf32d9e | ||
|
|
4cbf0eb2ac | ||
|
|
071b0830f8 | ||
|
|
39cd4a6594 | ||
|
|
0d2332fe5e | ||
|
|
c807fa6ec5 | ||
|
|
4a10806d9a | ||
|
|
57031f8cb0 | ||
|
|
b292486ac9 | ||
|
|
595f9bb0b1 | ||
|
|
ebe3397ad9 | ||
|
|
bc12b76bcc | ||
|
|
d8df85e99c | ||
|
|
80b9879d0a | ||
|
|
d54f9ec931 | ||
|
|
d2258acbb3 | ||
|
|
6c3a4bd019 | ||
|
|
7903211546 | ||
|
|
6b87fcf903 | ||
|
|
07d0d1b275 | ||
|
|
95361172a0 | ||
|
|
a0f3086167 | ||
|
|
65a59b41e5 | ||
|
|
78dc02fb81 | ||
|
|
dc577b8bfa | ||
|
|
ed9862ea91 | ||
|
|
f710c61179 | ||
|
|
d86cf14f0d | ||
|
|
bdfd5817dc | ||
|
|
3b2ece4ddc | ||
|
|
3452f364aa | ||
|
|
dad77a4d4b | ||
|
|
c8b925c581 | ||
|
|
24297c87e6 | ||
|
|
0c589eff04 | ||
|
|
3a3684ca78 | ||
|
|
6e39366c40 | ||
|
|
fd0ccb7741 | ||
|
|
81f151245e | ||
|
|
acfdd5b59e | ||
|
|
588800c495 | ||
|
|
cfd157ab7d | ||
|
|
377eb6126d | ||
|
|
2cad4c96bd | ||
|
|
2453501c83 | ||
|
|
dcc3c6290e | ||
|
|
54240e1b4f | ||
|
|
6639f91ba2 | ||
|
|
15bf309760 | ||
|
|
1de0302ecb | ||
|
|
871cbda3f8 | ||
|
|
c4ea85d7e5 | ||
|
|
a8f3125774 | ||
|
|
c564af180b | ||
|
|
3ebe1ed728 | ||
|
|
6b030b2978 | ||
|
|
9f2fa0f7af | ||
|
|
da06e35443 | ||
|
|
da4d047a37 | ||
|
|
782314abff | ||
|
|
94d53f052b | ||
|
|
9ceebf96db | ||
|
|
8611bb7322 | ||
|
|
ba2e1c95ef | ||
|
|
5979b5ca75 | ||
|
|
f144cae300 | ||
|
|
8bcf473b68 | ||
|
|
b10fdfc7b8 | ||
|
|
95eb1c0978 | ||
|
|
7a5cb8d374 | ||
|
|
f23b133d29 | ||
|
|
0bdee49f7f |
+1
-2
@@ -10,5 +10,4 @@
|
|||||||
/lib/elixir/test/ebin
|
/lib/elixir/test/ebin
|
||||||
/rel/elixir
|
/rel/elixir
|
||||||
erl_crash.dump
|
erl_crash.dump
|
||||||
.dialyzer_plt
|
.elixir.plt
|
||||||
.dialyzer.base_plt
|
|
||||||
|
|||||||
+48
-5
@@ -1,18 +1,61 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## v0.14.3-dev
|
## v0.14.3 (2014-07-12)
|
||||||
|
|
||||||
* Enhancements
|
* Enhancements
|
||||||
|
* [Access] Allow function access on `get_in/2` and `get_and_update_in/3`
|
||||||
|
* [Enum] Add `Enum.sort_by/3`
|
||||||
|
* [ExUnit] Match the line filter by proximity instead of exact match
|
||||||
|
* [Float] Support precision in `Float.ceil/1` and `Float.floor/1`
|
||||||
|
* [IO] Add `IO.(bin)read(device, :all)`
|
||||||
|
* [Kernel] Print a warning if a dangling `@doc` clause is found
|
||||||
|
* [Mix] Use absolute symbolic links on Windows for `_build` instead of copying
|
||||||
|
* [Mix] Add `Mix.compilers` that returns all default compilers used by mix tasks
|
||||||
|
* [Mix] Issue warning and reset mtime for source files from the future
|
||||||
|
* [Mix] Support task aliases in Mix
|
||||||
|
* [OptionParser] Add `OptionParser.split/1` that splits a string into argv
|
||||||
|
* [Record] Allow a record to be converted to a keyword list with `record(some_record)`
|
||||||
|
* [String] Improve performance of `String.split/1`
|
||||||
|
* [Typespec] Allow `%Struct{}` syntax to be used in typespecs
|
||||||
|
* [Typespec] Allow `record(:record, fields)` syntax to be used in typespecs
|
||||||
|
|
||||||
* Bug fixes
|
* Bug fixes
|
||||||
* [Kernel] `|>`, `<<<`, `>>>` and `^^^` were made left associative in operator table
|
* [IEx] Do not print ANSI sequences on `IEx.Helpers.clear/0` if ANSI sequences are not supported
|
||||||
* [Kernel] `<`, `>`, `<=`, `>=` were given higher precedence than comparison ones (`==`, `!=`, etc) in operator table
|
* [Inspect] Ensure `Inspect.Algebra.to_doc/2` doesn't go into a loop when there is a failure printing a struct
|
||||||
|
* [Kernel] `|>`, `<<<`, `>>>` and `^^^` made left associative in operator table
|
||||||
|
* [Kernel] `<`, `>`, `<=`, `>=` given higher precedence than comparison operators (`==`, `!=`, etc) in the operator table
|
||||||
|
* [Kernel] Run command line and escripts in a process that does not trap exits
|
||||||
|
* [Kernel] Fix a bug where Mix paths had higher priority than CLI ones, causing protocol consolidations to not be properly loaded
|
||||||
|
* [Kernel] Fix wording on error messages when a check/guard always passes or always fails
|
||||||
|
* [Kernel] Fix a bug where an unused function warning was printed even when the function was used via `defoverridable`
|
||||||
|
* [Kernel] Improve typespecs so they don't generate supertype dialyzer warnings
|
||||||
|
* [Macro] `to_string` correctly displays sigils
|
||||||
|
* [Mix] Ensure Mix dependencies are not compiled every second time when `mix deps.compile` is invoked
|
||||||
|
* [Mix] Fix a bug where `Mix.shell.error/1` and friends choked when printing a map
|
||||||
|
* [Mix] Ensure multiple `@external_resource` entries are read by Mix compilers
|
||||||
|
* [Mix] Fix a bug where tasks for umbrella projects were not properly reenabled
|
||||||
|
* [Stream] Fix bug when `flat_map` is used inside another `flat_map` with an Enumerable
|
||||||
|
* [Typespec] Fix a bug where the `list` typespec was incorrectly rendered as `[]`
|
||||||
|
|
||||||
* Soft deprecations (no warnings emitted)
|
* Soft deprecations (no warnings emitted)
|
||||||
|
* [Kernel] Using a list for bitstring modifiers is deprecated (as in `<<x :: [little, utf16]>>`), please use `-` as the separator instead (as in `<<x :: little-utf16>>`)
|
||||||
|
* [System] `System.cmd/1` is deprecated in favor of `System.cmd/3`
|
||||||
|
|
||||||
* Deprecations
|
* Deprecations
|
||||||
|
* [Mix] `mix escriptize` is deprecated in favor of `escript.build`
|
||||||
|
* [Mix] `mix local.install` and `mix local.uninstall` have been deprecated in favor of `mix archive.install` and `mix archive.uninstall` respectively
|
||||||
|
* [Mix] `:embed_extra_apps` for escripts is deprecated, instead list the dependencies inside `def application`
|
||||||
|
* [System] Giving a char list to `System.find_executable/1` is deprecated
|
||||||
|
|
||||||
* Backwards incompatible changes
|
* Backwards incompatible changes
|
||||||
|
* [Access] No longer fill in missing intermediate values with empty maps
|
||||||
|
* [Float] `Float.ceil/2` and `Float.floor/2` now always returns floats and no longer accept integers
|
||||||
|
* [Kernel] `defstruct` no longer automatically defines a type
|
||||||
|
* [Kernel] `exit(integer)` is no longer supported from scripts to configure the exit signal. Use `exit({:shutdown, integer})` instead
|
||||||
|
* [Kernel] Default argument values have to be defined in a function head if the function has multiple clauses
|
||||||
|
* [Mix] `mix archive.build` replaces the functionality of `mix archive`, `mix archive` now lists locally installed archives
|
||||||
|
* [Mix] `Mix.shell.info/1` no longer automatically escape ANSI sequences. Instead if has to be explicitly enabled with the `ansi: true` option
|
||||||
|
* [OptionParser] `--no-SWITCH` are only allowed for declared booleans switches
|
||||||
|
|
||||||
## v0.14.2 (2014-06-29)
|
## v0.14.2 (2014-06-29)
|
||||||
|
|
||||||
@@ -28,7 +71,7 @@
|
|||||||
|
|
||||||
* Bug fixes
|
* Bug fixes
|
||||||
* [CLI] Support paths inside archives in `-pa` and `-pz` options
|
* [CLI] Support paths inside archives in `-pa` and `-pz` options
|
||||||
* [IEx] Remove delay when printing data from the an application start callback
|
* [IEx] Remove delay when printing data from the application start callback
|
||||||
* [IEx] Ensure we show a consistent error when we cannot evaluate `.iex.exs`
|
* [IEx] Ensure we show a consistent error when we cannot evaluate `.iex.exs`
|
||||||
* [Kernel] Ensure derived protocols are defined with a file
|
* [Kernel] Ensure derived protocols are defined with a file
|
||||||
* [Kernel] Change precedence of `&` to not special case `/`
|
* [Kernel] Change precedence of `&` to not special case `/`
|
||||||
@@ -70,7 +113,7 @@
|
|||||||
* [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)
|
* [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
|
* Deprecations
|
||||||
* [Mix] `:escript_*` options were moved into a single `:escript` group
|
* [Mix] `:escript_*` options moved into a single `:escript` group
|
||||||
|
|
||||||
* Backwards incompatible changes
|
* Backwards incompatible changes
|
||||||
* [GenEvent] `GenEvent.stream/2` defaults to `:sync` mode
|
* [GenEvent] `GenEvent.stream/2` defaults to `:sync` mode
|
||||||
|
|||||||
@@ -279,7 +279,6 @@ We have saved some excellent pull requests we have received in the past in case
|
|||||||
you are looking for some examples:
|
you are looking for some examples:
|
||||||
|
|
||||||
* https://github.com/elixir-lang/elixir/pull/992
|
* https://github.com/elixir-lang/elixir/pull/992
|
||||||
* https://github.com/elixir-lang/elixir/pull/1041
|
|
||||||
* https://github.com/elixir-lang/elixir/pull/1058
|
* https://github.com/elixir-lang/elixir/pull/1058
|
||||||
* https://github.com/elixir-lang/elixir/pull/1059
|
* https://github.com/elixir-lang/elixir/pull/1059
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ INSTALL_DIR = $(INSTALL) -m755 -d
|
|||||||
INSTALL_DATA = $(INSTALL) -m644
|
INSTALL_DATA = $(INSTALL) -m644
|
||||||
INSTALL_PROGRAM = $(INSTALL) -m755
|
INSTALL_PROGRAM = $(INSTALL) -m755
|
||||||
|
|
||||||
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip check_erlang_release
|
.PHONY: install compile erlang elixir build_plt clean_plt dialyze test clean docs release_docs release_zip check_erlang_release
|
||||||
.NOTPARALLEL: compile
|
.NOTPARALLEL: compile
|
||||||
|
|
||||||
#==> Functions
|
#==> Functions
|
||||||
@@ -32,7 +32,7 @@ $(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
|
|||||||
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
|
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
|
||||||
$(Q) mkdir -p lib/$(1)/_build/shared/lib/$(1)
|
$(Q) mkdir -p lib/$(1)/_build/shared/lib/$(1)
|
||||||
$(Q) cp -R lib/$(1)/ebin lib/$(1)/_build/shared/lib/$(1)/
|
$(Q) cp -R lib/$(1)/ebin lib/$(1)/_build/shared/lib/$(1)/
|
||||||
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.start(:permanent, [])" -r mix.exs -e "Mix.Task.run('compile.app')"
|
$(Q) cd lib/$(1) && ../../bin/elixir -e 'Mix.start(:permanent, [])' -r mix.exs -e 'Mix.Task.run("compile.app")'
|
||||||
$(Q) cp lib/$(1)/_build/shared/lib/$(1)/ebin/$(1).app lib/$(1)/ebin/$(1).app
|
$(Q) cp lib/$(1)/_build/shared/lib/$(1)/ebin/$(1).app lib/$(1)/ebin/$(1).app
|
||||||
$(Q) rm -rf lib/$(1)/_build
|
$(Q) rm -rf lib/$(1)/_build
|
||||||
|
|
||||||
@@ -175,14 +175,20 @@ test_stdlib: compile
|
|||||||
$(Q) exec epmd & exit
|
$(Q) exec epmd & exit
|
||||||
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
|
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
|
||||||
|
|
||||||
.dialyzer.base_plt:
|
#==> Dialyzer tasks
|
||||||
@ echo "==> Adding Erlang/OTP basic applications to a new base PLT"
|
|
||||||
$(Q) dialyzer --output_plt .dialyzer.base_plt --build_plt --apps erts kernel stdlib compiler tools syntax_tools parsetools
|
|
||||||
|
|
||||||
dialyze: .dialyzer.base_plt
|
DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wrace_conditions -Wunderspecs
|
||||||
$(Q) rm -f .dialyzer_plt
|
PLT = .elixir.plt
|
||||||
$(Q) cp .dialyzer.base_plt .dialyzer_plt
|
|
||||||
@ echo "==> Adding Elixir to PLT..."
|
$(PLT):
|
||||||
$(Q) dialyzer --plt .dialyzer_plt --add_to_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin
|
@ echo "==> Building PLT with Elixir's dependencies..."
|
||||||
|
$(Q) dialyzer --output_plt $(PLT) --build_plt --apps erts kernel stdlib compiler syntax_tools parsetools tools ssl inets
|
||||||
|
|
||||||
|
clean_plt:
|
||||||
|
$(Q) rm -f $(PLT)
|
||||||
|
|
||||||
|
build_plt: clean_plt $(PLT)
|
||||||
|
|
||||||
|
dialyze: compile $(PLT)
|
||||||
@ echo "==> Dialyzing Elixir..."
|
@ echo "==> Dialyzing Elixir..."
|
||||||
$(Q) dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin
|
$(Q) dialyzer --plt $(PLT) $(DIALYZER_OPTS) lib/*/ebin
|
||||||
|
|||||||
+14
-14
@@ -60,22 +60,22 @@ if "%par%"=="""" (
|
|||||||
)
|
)
|
||||||
rem ******* EXECUTION OPTIONS **********************
|
rem ******* EXECUTION OPTIONS **********************
|
||||||
IF "%par%"==""+iex"" (Set useWerl=1)
|
IF "%par%"==""+iex"" (Set useWerl=1)
|
||||||
rem ******* ERLANG PARAMETERS **********************
|
|
||||||
IF NOT "%par%"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
|
|
||||||
IF NOT "%par%"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
|
|
||||||
IF NOT "%par%"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 && shift)
|
|
||||||
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)
|
|
||||||
rem ******* elixir parameters **********************
|
rem ******* elixir parameters **********************
|
||||||
rem Note: we don't have to do anything with options that don't take an argument
|
rem Note: we don't have to do anything with options that don't take an argument
|
||||||
IF NOT "%par%"=="%par:-e=%" (shift)
|
IF """"=="%par:-e=%" (shift)
|
||||||
IF NOT "%par%"=="%par:-r=%" (shift)
|
IF """"=="%par:-r=%" (shift)
|
||||||
IF NOT "%par%"=="%par:-pr=%" (shift)
|
IF """"=="%par:-pr=%" (shift)
|
||||||
IF NOT "%par%"=="%par:-pa=%" (shift)
|
IF """"=="%par:-pa=%" (shift)
|
||||||
IF NOT "%par%"=="%par:-pz=%" (shift)
|
IF """"=="%par:-pz=%" (shift)
|
||||||
IF NOT "%par%"=="%par:--app=%" (shift)
|
IF """"=="%par:--app=%" (shift)
|
||||||
IF NOT "%par%"=="%par:--remsh=%" (shift)
|
IF """"=="%par:--remsh=%" (shift)
|
||||||
|
rem ******* ERLANG PARAMETERS **********************
|
||||||
|
IF """"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
|
||||||
|
IF """"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
|
||||||
|
IF """"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 && shift)
|
||||||
|
IF """"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
|
||||||
|
IF """"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
|
||||||
|
IF """"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
|
||||||
goto:startloop
|
goto:startloop
|
||||||
|
|
||||||
rem ******* assume all pre-params are parsed ********************
|
rem ******* assume all pre-params are parsed ********************
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
@echo off
|
@echo off
|
||||||
call "%~dp0\elixir.bat" "%~dp0\mix" %*
|
call "%~dp0\elixir.bat" "%~dps0\mix" %*
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ defmodule EEx.Engine do
|
|||||||
end
|
end
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def handle_assign({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
|
def handle_assign({:@, meta, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
|
||||||
|
line = meta[:line] || 0
|
||||||
quote line: line, do: Dict.get(var!(assigns), unquote(name))
|
quote line: line, do: Dict.get(var!(assigns), unquote(name))
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -98,7 +99,10 @@ defmodule EEx.Engine do
|
|||||||
def handle_expr(buffer, "=", expr) do
|
def handle_expr(buffer, "=", expr) do
|
||||||
quote do
|
quote do
|
||||||
tmp = unquote(buffer)
|
tmp = unquote(buffer)
|
||||||
tmp <> to_string(unquote(expr))
|
tmp <> (case unquote(expr) do
|
||||||
|
bin when is_binary(bin) -> bin
|
||||||
|
oth -> String.Chars.to_string(oth)
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,16 @@ defmodule EEx.SmartEngineTest do
|
|||||||
assert_eval "1\n2\n3\n", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>"
|
assert_eval "1\n2\n3\n", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "compiled preserved line numbers" do
|
||||||
|
result = EEx.compile_string("<%= @hello %>", engine: EEx.SmartEngine)
|
||||||
|
Macro.prewalk(result, fn
|
||||||
|
{_left, meta, _right} ->
|
||||||
|
assert Keyword.get(meta, :line, 0) in [0, 1]
|
||||||
|
_ ->
|
||||||
|
:ok
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
defp assert_eval(expected, actual, binding \\ []) do
|
defp assert_eval(expected, actual, binding \\ []) do
|
||||||
result = EEx.eval_string(actual, binding, file: __ENV__.file)
|
result = EEx.eval_string(actual, binding, file: __ENV__.file)
|
||||||
assert result == expected
|
assert result == expected
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
-record(elixir_quote, {
|
-record(elixir_quote, {
|
||||||
line=false,
|
line=false,
|
||||||
keep=false,
|
file=nil,
|
||||||
context=nil,
|
context=nil,
|
||||||
vars_hygiene=true,
|
vars_hygiene=true,
|
||||||
aliases_hygiene=true,
|
aliases_hygiene=true,
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ defimpl Access, for: Map do
|
|||||||
|
|
||||||
def get_and_update!(other, key, _fun) do
|
def get_and_update!(other, key, _fun) do
|
||||||
raise ArgumentError,
|
raise ArgumentError,
|
||||||
"could not update key #{inspect key}. Expected map/struct, got: #{inspect other}"
|
"could not put/update key #{inspect key}. Expected map/struct, got: #{inspect other}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -132,8 +132,9 @@ defimpl Access, for: Atom do
|
|||||||
undefined(atom)
|
undefined(atom)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_and_update(nil, _, fun) do
|
def get_and_update(nil, key, _fun) do
|
||||||
fun.(nil)
|
raise ArgumentError,
|
||||||
|
"could not put/update key #{inspect key} on a nil value"
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_and_update(atom, _key, _fun) do
|
def get_and_update(atom, _key, _fun) do
|
||||||
|
|||||||
@@ -51,6 +51,23 @@ defmodule Code do
|
|||||||
:code.del_path(to_char_list(Path.expand path))
|
:code.del_path(to_char_list(Path.expand path))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Re-add paths given to the command line to keep their position
|
||||||
|
on the overall code path.
|
||||||
|
|
||||||
|
Some tools may change the code path by prepending new items but
|
||||||
|
still want the paths given by the user to have higher priority.
|
||||||
|
Calling this function guarantees the paths are re-added on
|
||||||
|
top of the user given ones.
|
||||||
|
"""
|
||||||
|
@spec readd_paths() :: :ok
|
||||||
|
def readd_paths() do
|
||||||
|
{pa, pz} = :elixir_code_server.call(:paths)
|
||||||
|
:code.add_pathsa(pa)
|
||||||
|
:code.add_pathsz(pz)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Evaluate the contents given by `string`.
|
Evaluate the contents given by `string`.
|
||||||
|
|
||||||
|
|||||||
@@ -85,8 +85,8 @@ defimpl Collectable, for: BitString do
|
|||||||
|
|
||||||
def into(original) do
|
def into(original) do
|
||||||
{original, fn
|
{original, fn
|
||||||
bitstring, {:cont, x} -> <<bitstring :: bits, x :: bits>>
|
acc, {:cont, x} when is_bitstring(x) -> [acc|x]
|
||||||
bitstring, :done -> bitstring
|
acc, :done -> IO.iodata_to_binary(acc)
|
||||||
_, :halt -> :ok
|
_, :halt -> :ok
|
||||||
end}
|
end}
|
||||||
end
|
end
|
||||||
|
|||||||
+40
-6
@@ -287,7 +287,6 @@ defmodule Enum do
|
|||||||
:none
|
:none
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec at(t, integer) :: element | nil
|
|
||||||
@spec at(t, integer, default) :: element | default
|
@spec at(t, integer, default) :: element | default
|
||||||
def at(collection, n, default \\ nil) do
|
def at(collection, n, default \\ nil) do
|
||||||
case fetch(collection, n) do
|
case fetch(collection, n) do
|
||||||
@@ -330,7 +329,6 @@ defmodule Enum do
|
|||||||
[[1, 2, 3], [4, 5, 6]]
|
[[1, 2, 3], [4, 5, 6]]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec chunk(t, non_neg_integer, non_neg_integer) :: [list]
|
|
||||||
@spec chunk(t, non_neg_integer, non_neg_integer, t | nil) :: [list]
|
@spec chunk(t, non_neg_integer, non_neg_integer, t | nil) :: [list]
|
||||||
def chunk(coll, n, step, pad \\ nil) when n > 0 and step > 0 do
|
def chunk(coll, n, step, pad \\ nil) when n > 0 and step > 0 do
|
||||||
limit = :erlang.max(n, step)
|
limit = :erlang.max(n, step)
|
||||||
@@ -689,7 +687,6 @@ defmodule Enum do
|
|||||||
3
|
3
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec find(t, (element -> any)) :: element | nil
|
|
||||||
@spec find(t, default, (element -> any)) :: element | default
|
@spec find(t, default, (element -> any)) :: element | default
|
||||||
def find(collection, ifnone \\ nil, fun)
|
def find(collection, ifnone \\ nil, fun)
|
||||||
|
|
||||||
@@ -716,7 +713,6 @@ defmodule Enum do
|
|||||||
true
|
true
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec find_value(t, (element -> any)) :: any | :nil
|
|
||||||
@spec find_value(t, any, (element -> any)) :: any | :nil
|
@spec find_value(t, any, (element -> any)) :: any | :nil
|
||||||
def find_value(collection, ifnone \\ nil, fun)
|
def find_value(collection, ifnone \\ nil, fun)
|
||||||
|
|
||||||
@@ -931,7 +927,6 @@ defmodule Enum do
|
|||||||
"1 = 2 = 3"
|
"1 = 2 = 3"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec join(t) :: String.t
|
|
||||||
@spec join(t, String.t) :: String.t
|
@spec join(t, String.t) :: String.t
|
||||||
def join(collection, joiner \\ "")
|
def join(collection, joiner \\ "")
|
||||||
|
|
||||||
@@ -990,7 +985,6 @@ defmodule Enum do
|
|||||||
"2 = 4 = 6"
|
"2 = 4 = 6"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec map_join(t, (element -> any)) :: String.t
|
|
||||||
@spec map_join(t, String.t, (element -> any)) :: String.t
|
@spec map_join(t, String.t, (element -> any)) :: String.t
|
||||||
def map_join(collection, joiner \\ "", mapper)
|
def map_join(collection, joiner \\ "", mapper)
|
||||||
|
|
||||||
@@ -1568,6 +1562,46 @@ defmodule Enum do
|
|||||||
reduce(collection, [], &sort_reducer(&1, &2, fun)) |> sort_terminator(fun)
|
reduce(collection, [], &sort_reducer(&1, &2, fun)) |> sort_terminator(fun)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Sorts the mapped results of the `collection` according to the `sorter` function.
|
||||||
|
|
||||||
|
This function maps each element of the collection using the `mapper`
|
||||||
|
function. The collection is then sorted by the mapped elements using the
|
||||||
|
`sorter` function, which defaults to `<=/2`
|
||||||
|
|
||||||
|
`sort_by/3` differs from `sort/2` in that it only calculates the comparison
|
||||||
|
value for each element in the collection once instead of once for each
|
||||||
|
element in each comparison. If the same function is being called on both
|
||||||
|
element, it's also more compact to use `sort_by/3`.
|
||||||
|
|
||||||
|
This technique is also known as a
|
||||||
|
[Schwartzian Transform](https://en.wikipedia.org/wiki/Schwartzian_transform),
|
||||||
|
or the Lisp decorate-sort-undecorate idiom as the `mapper` is decorating the
|
||||||
|
original `collection`, then `sorter` is sorting the decorations, and finally
|
||||||
|
the `collection` is being undecorated so only the original elements remain,
|
||||||
|
but now in sorted order.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Using the default `sorter` of `<=/2`:
|
||||||
|
|
||||||
|
iex> Enum.sort_by ["some", "kind", "of", "monster"], &byte_size/1
|
||||||
|
["of", "some", "kind", "monster"]
|
||||||
|
|
||||||
|
Using a custom `sorter` to override the order:
|
||||||
|
|
||||||
|
iex> Enum.sort_by ["some", "kind", "of", "monster"], &byte_size/1, &>=/2
|
||||||
|
["monster", "some", "kind", "of"]
|
||||||
|
|
||||||
|
"""
|
||||||
|
@spec sort_by(t, (element -> mapped_element), (mapped_element, mapped_element -> boolean)) :: list when mapped_element: element
|
||||||
|
def sort_by(collection, mapper, sorter \\ &<=/2) do
|
||||||
|
collection
|
||||||
|
|> map(&{&1, mapper.(&1)})
|
||||||
|
|> sort(&sorter.(elem(&1, 1), elem(&2, 1)))
|
||||||
|
|> map(&elem(&1, 0))
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Splits the enumerable into two collections, leaving `count`
|
Splits the enumerable into two collections, leaving `count`
|
||||||
elements in the first one. If `count` is a negative number,
|
elements in the first one. If `count` is a negative number,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ defmodule Exception do
|
|||||||
format exceptions as in the current Elixir version being used.
|
format exceptions as in the current Elixir version being used.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@typedoc "The exception type (as generated by defexception)"
|
@typedoc "The exception type"
|
||||||
@type t :: %{__struct__: module, __exception__: true}
|
@type t :: %{__struct__: module, __exception__: true}
|
||||||
|
|
||||||
@typedoc "The kind handled by formatting functions"
|
@typedoc "The kind handled by formatting functions"
|
||||||
@@ -482,6 +482,7 @@ end
|
|||||||
defmodule RuntimeError do
|
defmodule RuntimeError do
|
||||||
defexception message: "runtime error"
|
defexception message: "runtime error"
|
||||||
|
|
||||||
|
@spec exception(String.t) :: Exception.t
|
||||||
def exception(msg) when is_binary(msg) do
|
def exception(msg) when is_binary(msg) do
|
||||||
%RuntimeError{message: msg}
|
%RuntimeError{message: msg}
|
||||||
end
|
end
|
||||||
@@ -494,6 +495,7 @@ end
|
|||||||
defmodule ArgumentError do
|
defmodule ArgumentError do
|
||||||
defexception message: "argument error"
|
defexception message: "argument error"
|
||||||
|
|
||||||
|
@spec exception(String.t) :: Exception.t
|
||||||
def exception(msg) when is_binary(msg) do
|
def exception(msg) when is_binary(msg) do
|
||||||
%ArgumentError{message: msg}
|
%ArgumentError{message: msg}
|
||||||
end
|
end
|
||||||
|
|||||||
+21
-9
@@ -308,11 +308,15 @@ defmodule File do
|
|||||||
def touch(path, time \\ :calendar.local_time) do
|
def touch(path, time \\ :calendar.local_time) do
|
||||||
path = IO.chardata_to_string(path)
|
path = IO.chardata_to_string(path)
|
||||||
case F.change_time(path, time) do
|
case F.change_time(path, time) do
|
||||||
{:error, :enoent} ->
|
{:error, :enoent} -> touch_new(path, time)
|
||||||
write(path, "")
|
other -> other
|
||||||
F.change_time(path, time)
|
end
|
||||||
other ->
|
end
|
||||||
other
|
|
||||||
|
defp touch_new(path, time) do
|
||||||
|
case write(path, "", [:append]) do
|
||||||
|
:ok -> F.change_time(path, time)
|
||||||
|
{:error, _reason} = error -> error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -548,7 +552,8 @@ defmodule File do
|
|||||||
[dest|acc]
|
[dest|acc]
|
||||||
{:error, :eexist} ->
|
{:error, :eexist} ->
|
||||||
if callback.(src, dest) do
|
if callback.(src, dest) do
|
||||||
rm(dest)
|
# If rm/1 fails, copy/2 will fail
|
||||||
|
_ = rm(dest)
|
||||||
case copy(src, dest) do
|
case copy(src, dest) do
|
||||||
{:ok, _} ->
|
{:ok, _} ->
|
||||||
copy_file_mode!(src, dest)
|
copy_file_mode!(src, dest)
|
||||||
@@ -569,7 +574,8 @@ defmodule File do
|
|||||||
[dest|acc]
|
[dest|acc]
|
||||||
{:error, :eexist} ->
|
{:error, :eexist} ->
|
||||||
if callback.(src, dest) do
|
if callback.(src, dest) do
|
||||||
rm(dest)
|
# If rm/1 fails, iF.make_symlink/2 will fail
|
||||||
|
_ = rm(dest)
|
||||||
case F.make_symlink(link, dest) do
|
case F.make_symlink(link, dest) do
|
||||||
:ok -> [dest|acc]
|
:ok -> [dest|acc]
|
||||||
{:error, reason} -> {:error, reason, src}
|
{:error, reason} -> {:error, reason, src}
|
||||||
@@ -665,14 +671,20 @@ defmodule File do
|
|||||||
if match? {:win32, _}, :os.type do
|
if match? {:win32, _}, :os.type do
|
||||||
case F.read_file_info(IO.chardata_to_string(path)) do
|
case F.read_file_info(IO.chardata_to_string(path)) do
|
||||||
{:ok, file_info} when elem(file_info, 3) in [:read, :none] ->
|
{:ok, file_info} when elem(file_info, 3) in [:read, :none] ->
|
||||||
File.chmod(path, (elem(file_info, 7) + 0200))
|
change_mode_windows(path, file_info)
|
||||||
F.delete(path)
|
|
||||||
_ ->
|
_ ->
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp change_mode_windows(path, file_info) do
|
||||||
|
case File.chmod(path, (elem(file_info, 7) + 0200)) do
|
||||||
|
:ok -> F.delete(path)
|
||||||
|
{:error, _reason} = error -> error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Same as `rm/1`, but raises an exception in case of failure. Otherwise `:ok`.
|
Same as `rm/1`, but raises an exception in case of failure. Otherwise `:ok`.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ defmodule File.Stat do
|
|||||||
pairs = :lists.zip(keys, vals)
|
pairs = :lists.zip(keys, vals)
|
||||||
|
|
||||||
defstruct keys
|
defstruct keys
|
||||||
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Converts a `File.Stat` struct to a `:file_info` record.
|
Converts a `File.Stat` struct to a `:file_info` record.
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ defmodule File.Stream do
|
|||||||
|
|
||||||
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
|
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def __build__(path, modes, line_or_bytes) do
|
def __build__(path, modes, line_or_bytes) do
|
||||||
raw = :lists.keyfind(:encoding, 1, modes) == false
|
raw = :lists.keyfind(:encoding, 1, modes) == false
|
||||||
@@ -55,10 +57,14 @@ defmodule File.Stream do
|
|||||||
false -> IO.write(device, x)
|
false -> IO.write(device, x)
|
||||||
end
|
end
|
||||||
:ok, :done ->
|
:ok, :done ->
|
||||||
:file.close(device)
|
# If delayed_write option is used and the last write failed will
|
||||||
|
# MatchError here as {:error, _} is returned.
|
||||||
|
:ok = :file.close(device)
|
||||||
stream
|
stream
|
||||||
:ok, :halt ->
|
:ok, :halt ->
|
||||||
:file.close(device)
|
# If delayed_write option is used and the last write failed will
|
||||||
|
# MatchError here as {:error, _} is returned.
|
||||||
|
:ok = :file.close(device)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+51
-29
@@ -1,3 +1,5 @@
|
|||||||
|
import Kernel, except: [round: 1]
|
||||||
|
|
||||||
defmodule Float do
|
defmodule Float do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Functions for working with floating point numbers.
|
Functions for working with floating point numbers.
|
||||||
@@ -99,57 +101,71 @@ defmodule Float do
|
|||||||
@doc """
|
@doc """
|
||||||
Rounds a float to the largest integer less than or equal to `num`.
|
Rounds a float to the largest integer less than or equal to `num`.
|
||||||
|
|
||||||
|
Floor also accepts a precision to round a floating point value down
|
||||||
|
to an arbitrary number of fractional digits (between 0 and 15).
|
||||||
|
|
||||||
|
This function always returns floats. One may use `Kernel.trunc/1` to
|
||||||
|
truncate the result to an integer afterwards.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> Float.floor(34)
|
|
||||||
34
|
|
||||||
|
|
||||||
iex> Float.floor(34.25)
|
iex> Float.floor(34.25)
|
||||||
34
|
34.0
|
||||||
|
|
||||||
iex> Float.floor(-56.5)
|
iex> Float.floor(-56.5)
|
||||||
-57
|
-57.0
|
||||||
|
|
||||||
|
iex> Float.floor(34.253, 2)
|
||||||
|
34.25
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec floor(float | integer) :: integer
|
@spec floor(float, 0..15) :: float
|
||||||
def floor(num) when is_integer(num), do: num
|
def floor(number, precision \\ 0) when is_float(number) and precision in 0..15 do
|
||||||
def floor(num) when is_float(num) do
|
power = power_of_10(precision)
|
||||||
truncated = :erlang.trunc(num)
|
number = number * power
|
||||||
case :erlang.abs(num - truncated) do
|
truncated = trunc(number)
|
||||||
x when x > 0 and num < 0 -> truncated - 1
|
variance = if number - truncated < 0, do: -1.0, else: 0.0
|
||||||
_ -> truncated
|
(truncated + variance) / power
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Rounds a float to the largest integer greater than or equal to `num`.
|
Rounds a float to the largest integer greater than or equal to `num`.
|
||||||
|
|
||||||
|
Ceil also accepts a precision to round a floating point value down to
|
||||||
|
an arbitrary number of fractional digits (between 0 and 15).
|
||||||
|
|
||||||
|
This function always returns floats. One may use `Kernel.trunc/1` to
|
||||||
|
truncate the result to an integer afterwards.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> Float.ceil(34)
|
|
||||||
34
|
|
||||||
|
|
||||||
iex> Float.ceil(34.25)
|
iex> Float.ceil(34.25)
|
||||||
35
|
35.0
|
||||||
|
|
||||||
iex> Float.ceil(-56.5)
|
iex> Float.ceil(-56.5)
|
||||||
-56
|
-56.0
|
||||||
|
|
||||||
|
iex> Float.ceil(34.253, 2)
|
||||||
|
34.26
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec ceil(float | integer) :: integer
|
@spec ceil(float, 0..15) :: float
|
||||||
def ceil(num) when is_integer(num), do: num
|
def ceil(number, precision \\ 0) when is_float(number) and precision in 0..15 do
|
||||||
def ceil(num) when is_float(num) do
|
power = power_of_10(precision)
|
||||||
truncated = :erlang.trunc(num)
|
number = number * power
|
||||||
case :erlang.abs(num - truncated) do
|
truncated = trunc(number)
|
||||||
x when x > 0 and num > 0 -> truncated + 1
|
variance = if number - truncated > 0, do: 1.0, else: 0.0
|
||||||
_ -> truncated
|
(truncated + variance) / power
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Rounds a floating point value to an arbitrary number of fractional digits
|
Rounds a floating point value to an arbitrary number of fractional digits
|
||||||
(between 0 and 15).
|
(between 0 and 15).
|
||||||
|
|
||||||
|
This function only accepts floats and returns floats. Use `Kernel.round/1`
|
||||||
|
if you want a function that accepts both floats and integers and always
|
||||||
|
returns an integer.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> Float.round(5.5674, 3)
|
iex> Float.round(5.5674, 3)
|
||||||
@@ -165,9 +181,15 @@ defmodule Float do
|
|||||||
-5.568
|
-5.568
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec round(float, integer) :: float
|
@spec round(float, 0..15) :: float
|
||||||
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do
|
def round(number, precision \\ 0) when is_float(number) and precision in 0..15 do
|
||||||
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision)
|
power = power_of_10(precision)
|
||||||
|
Kernel.round(number * power) / power
|
||||||
|
end
|
||||||
|
|
||||||
|
Enum.reduce 0..15, 1, fn x, acc ->
|
||||||
|
defp power_of_10(unquote(x)), do: unquote(acc)
|
||||||
|
acc * 10
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ defmodule GenEvent do
|
|||||||
## Example
|
## Example
|
||||||
|
|
||||||
There are many use cases for event handlers. For example, a logging
|
There are many use cases for event handlers. For example, a logging
|
||||||
system can be built using event handlers where which log message is
|
system can be built using event handlers where each log message is
|
||||||
an event and different event handlers can be plugged to handle the
|
an event and different event handlers can be plugged to handle the
|
||||||
log messages. One handler may print error messages on the terminal,
|
log messages. One handler may print error messages on the terminal,
|
||||||
another can write it to a file, while a third one can keep the
|
another can write it to a file, while a third one can keep the
|
||||||
@@ -164,6 +164,16 @@ defmodule GenEvent do
|
|||||||
@typedoc "Supported values for new handlers"
|
@typedoc "Supported values for new handlers"
|
||||||
@type handler :: module | {module, term}
|
@type handler :: module | {module, term}
|
||||||
|
|
||||||
|
@typedoc "The subscription mode"
|
||||||
|
@type mode :: :sync | :async
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{
|
||||||
|
manager: manager,
|
||||||
|
id: term,
|
||||||
|
timeout: timeout,
|
||||||
|
duration: timeout,
|
||||||
|
mode: mode}
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Defines a `GenEvent` stream.
|
Defines a `GenEvent` stream.
|
||||||
|
|
||||||
@@ -387,7 +397,7 @@ defmodule GenEvent do
|
|||||||
def cancel_streams(%GenEvent{manager: manager, id: id}) do
|
def cancel_streams(%GenEvent{manager: manager, id: id}) do
|
||||||
handlers = :gen_event.which_handlers(manager)
|
handlers = :gen_event.which_handlers(manager)
|
||||||
|
|
||||||
for {Enumerable.GenEvent, {handler_id, _}} = ref <- handlers,
|
_ = for {Enumerable.GenEvent, {handler_id, _}} = ref <- handlers,
|
||||||
handler_id === id do
|
handler_id === id do
|
||||||
:gen_event.delete_handler(manager, ref, :remove_handler)
|
:gen_event.delete_handler(manager, ref, :remove_handler)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ defmodule HashDict do
|
|||||||
@node_size 8
|
@node_size 8
|
||||||
@node_template :erlang.make_tuple(@node_size, [])
|
@node_template :erlang.make_tuple(@node_size, [])
|
||||||
|
|
||||||
@opaque t :: map
|
@opaque t :: %__MODULE__{size: non_neg_integer, root: term}
|
||||||
@doc false
|
@doc false
|
||||||
defstruct size: 0, root: @node_template
|
defstruct size: 0, root: @node_template
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ defmodule HashSet do
|
|||||||
@node_size 8
|
@node_size 8
|
||||||
@node_template :erlang.make_tuple(@node_size, [])
|
@node_template :erlang.make_tuple(@node_size, [])
|
||||||
|
|
||||||
@opaque t :: map
|
@opaque t :: %__MODULE__{size: non_neg_integer, root: term}
|
||||||
@doc false
|
@doc false
|
||||||
defstruct size: 0, root: @node_template
|
defstruct size: 0, root: @node_template
|
||||||
|
|
||||||
|
|||||||
@@ -32,12 +32,20 @@ defmodule Inspect.Opts do
|
|||||||
* `:width` - defaults to the 80 characters.
|
* `:width` - defaults to the 80 characters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
defstruct structs: true :: boolean,
|
defstruct structs: true,
|
||||||
binaries: :infer :: :infer | :as_binaries | :as_strings,
|
binaries: :infer,
|
||||||
char_lists: :infer :: :infer | :as_lists | :as_char_lists,
|
char_lists: :infer,
|
||||||
limit: 50 :: pos_integer,
|
limit: 50,
|
||||||
width: 80 :: pos_integer | :infinity,
|
width: 80,
|
||||||
pretty: false :: boolean
|
pretty: false
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{
|
||||||
|
structs: boolean,
|
||||||
|
binaries: :infer | :as_binaries | :as_strings,
|
||||||
|
char_lists: :infer | :as_lists | :as_char_lists,
|
||||||
|
limit: pos_integer,
|
||||||
|
width: pos_integer | :infinity,
|
||||||
|
pretty: boolean}
|
||||||
end
|
end
|
||||||
|
|
||||||
defmodule Inspect.Algebra do
|
defmodule Inspect.Algebra do
|
||||||
@@ -170,10 +178,25 @@ defmodule Inspect.Algebra do
|
|||||||
Inspect.inspect(map, opts)
|
Inspect.inspect(map, opts)
|
||||||
rescue
|
rescue
|
||||||
e ->
|
e ->
|
||||||
res = Inspect.Map.inspect(map, opts)
|
# Because we try to raise a nice error message in case
|
||||||
raise ArgumentError,
|
# we can't inspect a struct, there is a chance the error
|
||||||
"Got #{inspect e.__struct__} with message " <>
|
# message itself relies on the struct being printed, so
|
||||||
"\"#{Exception.message(e)}\" while inspecting #{pretty(res, opts.width)}"
|
# we need to trap the inspected messages to guarantee
|
||||||
|
# we won't try to render any failed instruct when building
|
||||||
|
# the error message.
|
||||||
|
if Process.get(:inspect_trap) do
|
||||||
|
Inspect.Map.inspect(map, opts)
|
||||||
|
else
|
||||||
|
try do
|
||||||
|
Process.put(:inspect_trap, true)
|
||||||
|
res = Inspect.Map.inspect(map, opts)
|
||||||
|
raise ArgumentError,
|
||||||
|
"Got #{inspect e.__struct__} with message " <>
|
||||||
|
"\"#{Exception.message(e)}\" while inspecting #{pretty(res, opts.width)}"
|
||||||
|
after
|
||||||
|
Process.delete(:inspect_trap)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
Inspect.Map.inspect(map, opts)
|
Inspect.Map.inspect(map, opts)
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ defmodule Integer do
|
|||||||
'7'
|
'7'
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec to_char_list(integer) :: list
|
@spec to_char_list(integer) :: char_list
|
||||||
def to_char_list(number) do
|
def to_char_list(number) do
|
||||||
:erlang.integer_to_list(number)
|
:erlang.integer_to_list(number)
|
||||||
end
|
end
|
||||||
@@ -132,7 +132,7 @@ defmodule Integer do
|
|||||||
'3FF'
|
'3FF'
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec to_char_list(integer, pos_integer) :: list
|
@spec to_char_list(integer, pos_integer) :: char_list
|
||||||
def to_char_list(number, base) do
|
def to_char_list(number, base) do
|
||||||
:erlang.integer_to_list(number, base)
|
:erlang.integer_to_list(number, base)
|
||||||
end
|
end
|
||||||
|
|||||||
+49
-6
@@ -43,8 +43,10 @@ defmodule IO do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Reads `count` characters from the IO device or until
|
Reads `count` characters from the IO device, a whole
|
||||||
the end of the line if `:line` is given. It returns:
|
`:line` or the whole device with `:all`.
|
||||||
|
|
||||||
|
It returns:
|
||||||
|
|
||||||
* `data` - the input characters
|
* `data` - the input characters
|
||||||
|
|
||||||
@@ -53,10 +55,17 @@ defmodule IO do
|
|||||||
* `{:error, reason}` - other (rare) error condition;
|
* `{:error, reason}` - other (rare) error condition;
|
||||||
for instance, `{:error, :estale}` if reading from an
|
for instance, `{:error, :estale}` if reading from an
|
||||||
NFS volume
|
NFS volume
|
||||||
|
|
||||||
|
If `:all` is given, `:eof` is never returned, but an
|
||||||
|
empty string in case the device has reached EOF.
|
||||||
"""
|
"""
|
||||||
@spec read(device, :line | non_neg_integer) :: chardata | nodata
|
@spec read(device, :all | :line | non_neg_integer) :: chardata | nodata
|
||||||
def read(device \\ group_leader, chars_or_line)
|
def read(device \\ group_leader, chars_or_line)
|
||||||
|
|
||||||
|
def read(device, :all) do
|
||||||
|
do_read_all(map_dev(device), "")
|
||||||
|
end
|
||||||
|
|
||||||
def read(device, :line) do
|
def read(device, :line) do
|
||||||
:io.get_line(map_dev(device), '')
|
:io.get_line(map_dev(device), '')
|
||||||
end
|
end
|
||||||
@@ -65,9 +74,19 @@ defmodule IO do
|
|||||||
:io.get_chars(map_dev(device), '', count)
|
:io.get_chars(map_dev(device), '', count)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp do_read_all(mapped_dev, acc) do
|
||||||
|
case :io.get_line(mapped_dev, "") do
|
||||||
|
line when is_binary(line) -> do_read_all(mapped_dev, acc <> line)
|
||||||
|
:eof -> acc
|
||||||
|
other -> other
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Reads `count` bytes from the IO device or until
|
Reads `count` characters from the IO device, a whole
|
||||||
the end of the line if `:line` is given. It returns:
|
`:line` or the whole device with `:all`.
|
||||||
|
|
||||||
|
It returns:
|
||||||
|
|
||||||
* `data` - the input characters
|
* `data` - the input characters
|
||||||
|
|
||||||
@@ -76,10 +95,20 @@ defmodule IO do
|
|||||||
* `{:error, reason}` - other (rare) error condition;
|
* `{:error, reason}` - other (rare) error condition;
|
||||||
for instance, `{:error, :estale}` if reading from an
|
for instance, `{:error, :estale}` if reading from an
|
||||||
NFS volume
|
NFS volume
|
||||||
|
|
||||||
|
If `:all` is given, `:eof` is never returned, but an
|
||||||
|
empty string in case the device has reached EOF.
|
||||||
|
|
||||||
|
Note: do not use this function on IO devices in unicode mode
|
||||||
|
as it will return the wrong result.
|
||||||
"""
|
"""
|
||||||
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
|
@spec binread(device, :all | :line | non_neg_integer) :: iodata | nodata
|
||||||
def binread(device \\ group_leader, chars_or_line)
|
def binread(device \\ group_leader, chars_or_line)
|
||||||
|
|
||||||
|
def binread(device, :all) do
|
||||||
|
do_binread_all(map_dev(device), "")
|
||||||
|
end
|
||||||
|
|
||||||
def binread(device, :line) do
|
def binread(device, :line) do
|
||||||
case :file.read_line(map_dev(device)) do
|
case :file.read_line(map_dev(device)) do
|
||||||
{:ok, data} -> data
|
{:ok, data} -> data
|
||||||
@@ -94,6 +123,14 @@ defmodule IO do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp do_binread_all(mapped_dev, acc) do
|
||||||
|
case :file.read_line(mapped_dev) do
|
||||||
|
{:ok, data} -> do_binread_all(mapped_dev, acc <> data)
|
||||||
|
:eof -> acc
|
||||||
|
other -> other
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Writes the given argument to the given device.
|
Writes the given argument to the given device.
|
||||||
|
|
||||||
@@ -119,6 +156,9 @@ defmodule IO do
|
|||||||
as a binary, no unicode conversion happens.
|
as a binary, no unicode conversion happens.
|
||||||
|
|
||||||
Check `write/2` for more information.
|
Check `write/2` for more information.
|
||||||
|
|
||||||
|
Note: do not use this function on IO devices in unicode mode
|
||||||
|
as it will return the wrong result.
|
||||||
"""
|
"""
|
||||||
@spec binwrite(device, iodata) :: :ok | {:error, term}
|
@spec binwrite(device, iodata) :: :ok | {:error, term}
|
||||||
def binwrite(device \\ group_leader(), item) when is_iodata(item) do
|
def binwrite(device \\ group_leader(), item) when is_iodata(item) do
|
||||||
@@ -263,6 +303,9 @@ defmodule IO do
|
|||||||
|
|
||||||
Note that an IO stream has side effects and every time
|
Note that an IO stream has side effects and every time
|
||||||
you go over the stream you may get different results.
|
you go over the stream you may get different results.
|
||||||
|
|
||||||
|
Finally, do not use this function on IO devices in unicode
|
||||||
|
mode as it will return the wrong result.
|
||||||
"""
|
"""
|
||||||
@spec binstream(device, :line | pos_integer) :: Enumerable.t
|
@spec binstream(device, :line | pos_integer) :: Enumerable.t
|
||||||
def binstream(device, line_or_bytes) do
|
def binstream(device, line_or_bytes) do
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ defmodule IO.Stream do
|
|||||||
|
|
||||||
defstruct device: nil, raw: true, line_or_bytes: :line
|
defstruct device: nil, raw: true, line_or_bytes: :line
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def __build__(device, raw, line_or_bytes) do
|
def __build__(device, raw, line_or_bytes) do
|
||||||
%IO.Stream{device: device, raw: raw, line_or_bytes: line_or_bytes}
|
%IO.Stream{device: device, raw: raw, line_or_bytes: line_or_bytes}
|
||||||
|
|||||||
+205
-107
@@ -150,9 +150,9 @@ defmodule Kernel do
|
|||||||
3
|
3
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec byte_size(bitstring) :: non_neg_integer
|
@spec byte_size(binary) :: non_neg_integer
|
||||||
def byte_size(bitstring) do
|
def byte_size(binary) do
|
||||||
:erlang.byte_size(bitstring)
|
:erlang.byte_size(binary)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@@ -183,9 +183,56 @@ defmodule Kernel do
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
When a process reaches its end, by default it exits with
|
||||||
|
reason `:normal`. You can also call it explicitly if you
|
||||||
|
want to terminate a process but not signal any failure:
|
||||||
|
|
||||||
exit(:normal)
|
exit(:normal)
|
||||||
|
|
||||||
|
In case something goes wrong, you can also use `exit/1` with
|
||||||
|
a different reason:
|
||||||
|
|
||||||
exit(:seems_bad)
|
exit(:seems_bad)
|
||||||
|
|
||||||
|
If the reason is not `:normal`, all linked process to the
|
||||||
|
exited process will crash (unless they are trapping exits).
|
||||||
|
|
||||||
|
## OTP exits
|
||||||
|
|
||||||
|
Exits are used by OTP to determine if a process exited abnormally
|
||||||
|
or not. The following exits are considered "normal":
|
||||||
|
|
||||||
|
* `exit(:normal)`
|
||||||
|
* `exit(:shutdown)`
|
||||||
|
* `exit({:shutdown, term})`
|
||||||
|
|
||||||
|
Exiting with any other reason is considered abnormal and treated
|
||||||
|
as a crash. This means the default supervisor behaviour kicks in,
|
||||||
|
error reports are emitted, etc.
|
||||||
|
|
||||||
|
This behaviour is relied on in many different places. For example,
|
||||||
|
`ExUnit` uses `exit(:shutdown)` when exiting the test process to
|
||||||
|
signal linked processes, supervision trees and so on to politely
|
||||||
|
shutdown too.
|
||||||
|
|
||||||
|
## CLI exits
|
||||||
|
|
||||||
|
Building on top of the exit signals mentioned above, if the
|
||||||
|
process started by the command line exits with any of the three
|
||||||
|
reasons above, its exit is considered normal and the Operating
|
||||||
|
System process will exit with status 0.
|
||||||
|
|
||||||
|
It is, however, possible to customize the Operating System exit
|
||||||
|
signal by invoking:
|
||||||
|
|
||||||
|
exit({:shutdown, integer})
|
||||||
|
|
||||||
|
This will cause the OS process to exit with the status given by
|
||||||
|
`integer` while signaling all linked OTP processes to politely
|
||||||
|
shutdown.
|
||||||
|
|
||||||
|
Any other exit reason will cause the OS process to exit with
|
||||||
|
status `1` and linked OTP processes to crash.
|
||||||
"""
|
"""
|
||||||
@spec exit(term) :: no_return
|
@spec exit(term) :: no_return
|
||||||
def exit(reason) do
|
def exit(reason) do
|
||||||
@@ -1453,7 +1500,16 @@ defmodule Kernel do
|
|||||||
Gets a value from a nested structure.
|
Gets a value from a nested structure.
|
||||||
|
|
||||||
Uses the `Access` protocol to traverse the structures
|
Uses the `Access` protocol to traverse the structures
|
||||||
according to the given `keys`.
|
according to the given `keys`, unless the `key` is a
|
||||||
|
function.
|
||||||
|
|
||||||
|
If a key is a function, the function will be invoked
|
||||||
|
passing three arguments, the operation (`:get`), the
|
||||||
|
data to be accessed, and a function to be invoked next.
|
||||||
|
|
||||||
|
This means `get_in/2` can be extended to provide
|
||||||
|
custom lookups. The downside is that functions cannot be
|
||||||
|
stored as keys in the accessed data structures.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -1468,18 +1524,44 @@ defmodule Kernel do
|
|||||||
iex> get_in(users, ["unknown", :age])
|
iex> get_in(users, ["unknown", :age])
|
||||||
nil
|
nil
|
||||||
|
|
||||||
|
When one of the keys is a function, the function is invoked.
|
||||||
|
In the example below, we use a function to get all the maps
|
||||||
|
inside a list:
|
||||||
|
|
||||||
|
iex> users = [%{name: "josé", age: 27}, %{name: "eric", age: 23}]
|
||||||
|
iex> all = fn :get, data, next -> Enum.map(data, next) end
|
||||||
|
iex> get_in(users, [all, :age])
|
||||||
|
[27, 23]
|
||||||
|
|
||||||
|
If the previous value before invoking the function is nil,
|
||||||
|
the function *will* receive nil as a value and must handle it
|
||||||
|
accordingly.
|
||||||
"""
|
"""
|
||||||
@spec get_in(Access.t, nonempty_list(term)) :: term
|
@spec get_in(Access.t, nonempty_list(term)) :: term
|
||||||
def get_in(data, keys)
|
def get_in(data, keys)
|
||||||
def get_in(nil, list) when is_list(list), do: nil
|
|
||||||
def get_in(data, [h]), do: Access.get(data, h)
|
def get_in(data, [h]) when is_function(h),
|
||||||
def get_in(data, [h|t]), do: get_in(Access.get(data, h), t)
|
do: h.(:get, data, &(&1))
|
||||||
|
def get_in(data, [h|t]) when is_function(h),
|
||||||
|
do: h.(:get, data, &get_in(&1, t))
|
||||||
|
|
||||||
|
def get_in(nil, [_]),
|
||||||
|
do: nil
|
||||||
|
def get_in(nil, [_|t]),
|
||||||
|
do: get_in(nil, t)
|
||||||
|
|
||||||
|
def get_in(data, [h]),
|
||||||
|
do: Access.get(data, h)
|
||||||
|
def get_in(data, [h|t]),
|
||||||
|
do: get_in(Access.get(data, h), t)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Puts a value in a nested structure.
|
Puts a value in a nested structure.
|
||||||
|
|
||||||
Uses the `Access` protocol to traverse the structures
|
Uses the `Access` protocol to traverse the structures
|
||||||
according to the given `keys`.
|
according to the given `keys`, unless the `key` is a
|
||||||
|
function. If the key is a function, it will be invoked
|
||||||
|
as specified in `get_and_update_in/3`.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -1487,13 +1569,8 @@ defmodule Kernel do
|
|||||||
iex> put_in(users, ["josé", :age], 28)
|
iex> put_in(users, ["josé", :age], 28)
|
||||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||||
|
|
||||||
In case any of entries in the middle returns `nil`, a map is dynamically
|
In case any of entries in the middle returns `nil`,
|
||||||
created:
|
an error will be raised when trying to access it next.
|
||||||
|
|
||||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
|
||||||
iex> put_in(users, ["dave", :age], 13)
|
|
||||||
%{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 13}}
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec put_in(Access.t, nonempty_list(term), term) :: Access.t
|
@spec put_in(Access.t, nonempty_list(term), term) :: Access.t
|
||||||
def put_in(data, keys, value) do
|
def put_in(data, keys, value) do
|
||||||
@@ -1504,7 +1581,9 @@ defmodule Kernel do
|
|||||||
Updates a key in a nested structure.
|
Updates a key in a nested structure.
|
||||||
|
|
||||||
Uses the `Access` protocol to traverse the structures
|
Uses the `Access` protocol to traverse the structures
|
||||||
according to the given `keys`.
|
according to the given `keys`, unless the `key` is a
|
||||||
|
function. If the key is a function, it will be invoked
|
||||||
|
as specified in `get_and_update_in/3`.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -1512,13 +1591,8 @@ defmodule Kernel do
|
|||||||
iex> update_in(users, ["josé", :age], &(&1 + 1))
|
iex> update_in(users, ["josé", :age], &(&1 + 1))
|
||||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||||
|
|
||||||
In case any of entries in the middle returns `nil`, a map is dynamically
|
In case any of entries in the middle returns `nil`,
|
||||||
created:
|
an error will be raised when trying to access it next.
|
||||||
|
|
||||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
|
||||||
iex> update_in(users, ["dave", :age], &((&1 || 0) + 1))
|
|
||||||
%{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 1}}
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec update_in(Access.t, nonempty_list(term), (term -> term)) :: Access.t
|
@spec update_in(Access.t, nonempty_list(term), (term -> term)) :: Access.t
|
||||||
def update_in(data, keys, fun) do
|
def update_in(data, keys, fun) do
|
||||||
@@ -1528,9 +1602,20 @@ defmodule Kernel do
|
|||||||
@doc """
|
@doc """
|
||||||
Gets a value and updates a nested structure.
|
Gets a value and updates a nested structure.
|
||||||
|
|
||||||
It expects a tuple to be returned, containing the value retrieved
|
It expects a tuple to be returned, containing the value
|
||||||
and the update one. Uses the `Access` protocol to traverse the
|
retrieved and the update one.
|
||||||
structures according to the given `keys`.
|
|
||||||
|
Uses the `Access` protocol to traverse the structures
|
||||||
|
according to the given `keys`, unless the `key` is a
|
||||||
|
function.
|
||||||
|
|
||||||
|
If a key is a function, the function will be invoked
|
||||||
|
passing three arguments, the operation (`:get_and_update`),
|
||||||
|
the data to be accessed, and a function to be invoked next.
|
||||||
|
|
||||||
|
This means `get_and_update_in/3` can be extended to provide
|
||||||
|
custom lookups. The downside is that functions cannot be stored
|
||||||
|
as keys in the accessed data structures.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -1543,23 +1628,34 @@ defmodule Kernel do
|
|||||||
iex> get_and_update_in(users, ["josé", :age], &{&1, &1 + 1})
|
iex> get_and_update_in(users, ["josé", :age], &{&1, &1 + 1})
|
||||||
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}}
|
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}}
|
||||||
|
|
||||||
In case any of entries in the middle returns `nil`, a map is dynamically
|
When one of the keys is a function, the function is invoked.
|
||||||
created:
|
In the example below, we use a function to get and increment all
|
||||||
|
ages inside a list:
|
||||||
|
|
||||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
iex> users = [%{name: "josé", age: 27}, %{name: "eric", age: 23}]
|
||||||
iex> get_and_update_in(users, ["dave", :age], &{&1, 13})
|
iex> all = fn :get_and_update, data, next ->
|
||||||
{nil, %{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 13}}}
|
...> Enum.map(data, next) |> List.unzip() |> List.to_tuple()
|
||||||
|
...> end
|
||||||
|
iex> get_and_update_in(users, [all, :age], &{&1, &1 + 1})
|
||||||
|
{[27, 23], [%{name: "josé", age: 28}, %{name: "eric", age: 24}]}
|
||||||
|
|
||||||
|
If the previous value before invoking the function is nil,
|
||||||
|
the function *will* receive `nil` as a value and must handle it
|
||||||
|
accordingly (be it by failing or providing a sane default).
|
||||||
"""
|
"""
|
||||||
@spec get_and_update_in(Access.t, nonempty_list(term),
|
@spec get_and_update_in(Access.t, nonempty_list(term),
|
||||||
(term -> {get, term})) :: {get, Access.t} when get: var
|
(term -> {get, term})) :: {get, Access.t} when get: var
|
||||||
def get_and_update_in(data, keys, fun)
|
def get_and_update_in(data, keys, fun)
|
||||||
|
|
||||||
def get_and_update_in(nil, list, fun), do: get_and_update_in(%{}, list, fun)
|
def get_and_update_in(data, [h], fun) when is_function(h),
|
||||||
def get_and_update_in(data, [h], fun), do: Access.get_and_update(data, h, fun)
|
do: h.(:get_and_update, data, fun)
|
||||||
def get_and_update_in(data, [h|t], fun) do
|
def get_and_update_in(data, [h|t], fun) when is_function(h),
|
||||||
Access.get_and_update(data, h, &get_and_update_in(&1, t, fun))
|
do: h.(:get_and_update, data, &get_and_update_in(&1, t, fun))
|
||||||
end
|
|
||||||
|
def get_and_update_in(data, [h], fun),
|
||||||
|
do: Access.get_and_update(data, h, fun)
|
||||||
|
def get_and_update_in(data, [h|t], fun),
|
||||||
|
do: Access.get_and_update(data, h, &get_and_update_in(&1, t, fun))
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Puts a value in a nested structure via the given `path`.
|
Puts a value in a nested structure via the given `path`.
|
||||||
@@ -1645,7 +1741,7 @@ defmodule Kernel do
|
|||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||||
iex> get_and_update_in(users["josé"][:age], &{&1, &1 + 1})
|
iex> get_and_update_in(users["josé"].age, &{&1, &1 + 1})
|
||||||
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}}
|
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}}
|
||||||
|
|
||||||
## Paths
|
## Paths
|
||||||
@@ -1714,7 +1810,7 @@ defmodule Kernel do
|
|||||||
|
|
||||||
defp unnest(other, [], kind) do
|
defp unnest(other, [], kind) do
|
||||||
raise ArgumentError,
|
raise ArgumentError,
|
||||||
"expected expression given to #{kind} to access at least one field, got: #{Macro.to_string other}"
|
"expected expression given to #{kind} to access at least one element, got: #{Macro.to_string other}"
|
||||||
end
|
end
|
||||||
|
|
||||||
defp unnest(other, acc, kind) do
|
defp unnest(other, acc, kind) do
|
||||||
@@ -1723,7 +1819,7 @@ defmodule Kernel do
|
|||||||
false ->
|
false ->
|
||||||
raise ArgumentError,
|
raise ArgumentError,
|
||||||
"expression given to #{kind} must start with a variable, local or remote call " <>
|
"expression given to #{kind} must start with a variable, local or remote call " <>
|
||||||
"and be followed by field access, got: #{Macro.to_string other}"
|
"and be followed by an element access, got: #{Macro.to_string other}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1894,10 +1990,7 @@ defmodule Kernel do
|
|||||||
|
|
||||||
case is_list(args) and length(args) == 1 and typespec(name) do
|
case is_list(args) and length(args) == 1 and typespec(name) do
|
||||||
false ->
|
false ->
|
||||||
case name == :typedoc and not bootstraped?(Kernel.Typespec) do
|
do_at(args, name, function?, __CALLER__)
|
||||||
true -> nil
|
|
||||||
false -> do_at(args, name, function?, __CALLER__)
|
|
||||||
end
|
|
||||||
macro ->
|
macro ->
|
||||||
case bootstraped?(Kernel.Typespec) do
|
case bootstraped?(Kernel.Typespec) do
|
||||||
false -> nil
|
false -> nil
|
||||||
@@ -2561,7 +2654,7 @@ defmodule Kernel do
|
|||||||
|
|
||||||
# Generate the alias for this module definition
|
# Generate the alias for this module definition
|
||||||
{new, old} = module_nesting(env.module, full)
|
{new, old} = module_nesting(env.module, full)
|
||||||
meta = [defined: full, context: true] ++ alias_meta(alias)
|
meta = [defined: full, context: env.module] ++ alias_meta(alias)
|
||||||
|
|
||||||
{full, {:alias, meta, [old, [as: new, warn: false]]}}
|
{full, {:alias, meta, [old, [as: new, warn: false]]}}
|
||||||
false ->
|
false ->
|
||||||
@@ -2819,28 +2912,48 @@ defmodule Kernel do
|
|||||||
|
|
||||||
## Types
|
## Types
|
||||||
|
|
||||||
`defstruct` automatically generates a type `t` unless one exists.
|
It is recommended to define types for structs, by convention this type
|
||||||
The following definition:
|
is called `t`. To define a struct in a type the struct literal syntax
|
||||||
|
is used:
|
||||||
|
|
||||||
defmodule User do
|
defmodule User do
|
||||||
defstruct name: "José" :: String.t,
|
defstruct name: "José", age: 25
|
||||||
age: 25 :: integer
|
@type t :: %User{name: String.t, age: integer}
|
||||||
end
|
end
|
||||||
|
|
||||||
Generates a type as follows:
|
It is recommended to only use the struct syntax when defining the struct's
|
||||||
|
type. When referring to another struct use `User.t`, not `%User{}`. Fields
|
||||||
|
in the struct not included in the type defaults to `term`.
|
||||||
|
|
||||||
@type t :: %User{name: String.t, age: integer}
|
Private structs that are not used outside its module should use the private
|
||||||
|
type attribute `@typep`. Public structs whose internal structure is private
|
||||||
In case a struct does not declare a field type, it defaults to `term`.
|
to the local module (you are not allowed to pattern match it or directly
|
||||||
|
access fields) should use the `@opaque` attribute. Structs whose internal
|
||||||
|
structure is public should use `@type`.
|
||||||
"""
|
"""
|
||||||
defmacro defstruct(kv) do
|
defmacro defstruct(fields) do
|
||||||
{fields, types} = split_fields_and_types(kv)
|
{fields, types} = split_fields_and_types(fields)
|
||||||
|
|
||||||
|
types =
|
||||||
|
case types do
|
||||||
|
true ->
|
||||||
|
stacktrace = Exception.format_stacktrace(Macro.Env.stacktrace(__CALLER__))
|
||||||
|
IO.write :stderr, "warning: passing types to struct fields with :: is deprecated, " <>
|
||||||
|
"please define a type explicitly instead\n#{stacktrace}"
|
||||||
|
quote do
|
||||||
|
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
||||||
|
@type t :: %{__struct__: __MODULE__}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
false ->
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
fields =
|
fields =
|
||||||
quote bind_quoted: [fields: fields] do
|
quote bind_quoted: [fields: fields] do
|
||||||
fields = :lists.map(fn
|
fields = :lists.map(fn
|
||||||
{ key, _ } = pair when is_atom(key) -> pair
|
{key, _} = pair when is_atom(key) -> pair
|
||||||
key when is_atom(key) -> { key, nil }
|
key when is_atom(key) -> {key, nil}
|
||||||
other -> raise ArgumentError, "struct field names must be atoms, got: #{inspect other}"
|
other -> raise ArgumentError, "struct field names must be atoms, got: #{inspect other}"
|
||||||
end, fields)
|
end, fields)
|
||||||
|
|
||||||
@@ -2853,33 +2966,12 @@ defmodule Kernel do
|
|||||||
Protocol.__derive__(derive, __MODULE__, __ENV__)
|
Protocol.__derive__(derive, __MODULE__, __ENV__)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec __struct__() :: t
|
@spec __struct__() :: %__MODULE__{}
|
||||||
def __struct__() do
|
def __struct__() do
|
||||||
@struct
|
@struct
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
types =
|
|
||||||
case bootstraped?(Kernel.Typespec) do
|
|
||||||
true when types == [] ->
|
|
||||||
quote unquote: false do
|
|
||||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
|
||||||
types = :lists.map(fn {key, _} ->
|
|
||||||
{key, quote(do: term)}
|
|
||||||
end, fields)
|
|
||||||
@type t :: %{unquote_splicing(types), __struct__: __MODULE__}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
true ->
|
|
||||||
quote do
|
|
||||||
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
|
|
||||||
@type t :: %{unquote_splicing(types), __struct__: __MODULE__}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
false ->
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
quote do
|
quote do
|
||||||
unquote(fields)
|
unquote(fields)
|
||||||
unquote(types)
|
unquote(types)
|
||||||
@@ -2888,26 +2980,26 @@ defmodule Kernel do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp split_fields_and_types(kv) do
|
defp split_fields_and_types(kv) do
|
||||||
case Keyword.keyword?(kv) do
|
case Keyword.keyword?(kv) do
|
||||||
true -> split_fields_and_types(kv, [], [])
|
true -> split_fields_and_types(kv, [], false)
|
||||||
false -> {kv, []}
|
false -> {kv, false}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp split_fields_and_types([{field, {:::, _, [default, type]}}|t], fields, types) do
|
defp split_fields_and_types([{field, {:::, _, [default, _]}}|t], fields, _types) do
|
||||||
split_fields_and_types(t, [{field, default}|fields], [{field, type}|types])
|
split_fields_and_types(t, [{field, default}|fields], true)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp split_fields_and_types([{field, default}|t], fields, types) do
|
defp split_fields_and_types([{field, default}|t], fields, types) do
|
||||||
split_fields_and_types(t, [{field, default}|fields], [{field, quote(do: term)}|types])
|
split_fields_and_types(t, [{field, default}|fields], types)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp split_fields_and_types([field|t], fields, types) do
|
defp split_fields_and_types([field|t], fields, types) do
|
||||||
split_fields_and_types(t, [field|fields], [{field, quote(do: term)}|types])
|
split_fields_and_types(t, [field|fields], types)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp split_fields_and_types([], fields, types) do
|
defp split_fields_and_types([], fields, types) do
|
||||||
{:lists.reverse(fields), :lists.reverse(types)}
|
{:lists.reverse(fields), types}
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc ~S"""
|
@doc ~S"""
|
||||||
@@ -2970,7 +3062,7 @@ defmodule Kernel do
|
|||||||
@behaviour Exception
|
@behaviour Exception
|
||||||
fields = defstruct unquote(fields)
|
fields = defstruct unquote(fields)
|
||||||
|
|
||||||
@spec exception(term) :: t
|
@spec exception(Keyword.t) :: Exception.t
|
||||||
def exception(args) when is_list(args) do
|
def exception(args) when is_list(args) do
|
||||||
Kernel.struct(__struct__, args)
|
Kernel.struct(__struct__, args)
|
||||||
end
|
end
|
||||||
@@ -2978,7 +3070,7 @@ defmodule Kernel do
|
|||||||
defoverridable exception: 1
|
defoverridable exception: 1
|
||||||
|
|
||||||
if Keyword.has_key?(fields, :message) do
|
if Keyword.has_key?(fields, :message) do
|
||||||
@spec message(t) :: String.t
|
@spec message(Exception.t) :: String.t
|
||||||
def message(exception) do
|
def message(exception) do
|
||||||
exception.message
|
exception.message
|
||||||
end
|
end
|
||||||
@@ -3309,7 +3401,7 @@ defmodule Kernel do
|
|||||||
|
|
||||||
## Sigils
|
## Sigils
|
||||||
|
|
||||||
@doc """
|
@doc ~S"""
|
||||||
Handles the sigil ~S. It simply returns a string
|
Handles the sigil ~S. It simply returns a string
|
||||||
without escaping characters and without interpolations.
|
without escaping characters and without interpolations.
|
||||||
|
|
||||||
@@ -3318,15 +3410,15 @@ defmodule Kernel do
|
|||||||
iex> ~S(foo)
|
iex> ~S(foo)
|
||||||
"foo"
|
"foo"
|
||||||
|
|
||||||
iex> ~S(f\#{o}o)
|
iex> ~S(f#{o}o)
|
||||||
"f\\\#{o}o"
|
"f\#{o}o"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
defmacro sigil_S(string, []) do
|
defmacro sigil_S(string, []) do
|
||||||
string
|
string
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc ~S"""
|
||||||
Handles the sigil ~s. It returns a string as if it was double quoted
|
Handles the sigil ~s. It returns a string as if it was double quoted
|
||||||
string, unescaping characters and replacing interpolations.
|
string, unescaping characters and replacing interpolations.
|
||||||
|
|
||||||
@@ -3335,15 +3427,18 @@ defmodule Kernel do
|
|||||||
iex> ~s(foo)
|
iex> ~s(foo)
|
||||||
"foo"
|
"foo"
|
||||||
|
|
||||||
iex> ~s(f\#{:o}o)
|
iex> ~s(f#{:o}o)
|
||||||
"foo"
|
"foo"
|
||||||
|
|
||||||
|
iex> ~s(f\#{:o}o)
|
||||||
|
"f\#{:o}o"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
defmacro sigil_s({:<<>>, line, pieces}, []) do
|
defmacro sigil_s({:<<>>, line, pieces}, []) do
|
||||||
{:<<>>, line, Macro.unescape_tokens(pieces)}
|
{:<<>>, line, Macro.unescape_tokens(pieces)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc ~S"""
|
||||||
Handles the sigil ~C. It simply returns a char list
|
Handles the sigil ~C. It simply returns a char list
|
||||||
without escaping characters and without interpolations.
|
without escaping characters and without interpolations.
|
||||||
|
|
||||||
@@ -3352,15 +3447,15 @@ defmodule Kernel do
|
|||||||
iex> ~C(foo)
|
iex> ~C(foo)
|
||||||
'foo'
|
'foo'
|
||||||
|
|
||||||
iex> ~C(f\#{o}o)
|
iex> ~C(f#{o}o)
|
||||||
'f\\\#{o}o'
|
'f\#{o}o'
|
||||||
|
|
||||||
"""
|
"""
|
||||||
defmacro sigil_C({:<<>>, _line, [string]}, []) when is_binary(string) do
|
defmacro sigil_C({:<<>>, _line, [string]}, []) when is_binary(string) do
|
||||||
String.to_char_list(string)
|
String.to_char_list(string)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc ~S"""
|
||||||
Handles the sigil ~c. It returns a char list as if it were a single
|
Handles the sigil ~c. It returns a char list as if it were a single
|
||||||
quoted string, unescaping characters and replacing interpolations.
|
quoted string, unescaping characters and replacing interpolations.
|
||||||
|
|
||||||
@@ -3369,9 +3464,12 @@ defmodule Kernel do
|
|||||||
iex> ~c(foo)
|
iex> ~c(foo)
|
||||||
'foo'
|
'foo'
|
||||||
|
|
||||||
iex> ~c(f\#{:o}o)
|
iex> ~c(f#{:o}o)
|
||||||
'foo'
|
'foo'
|
||||||
|
|
||||||
|
iex> ~c(f\#{:o}o)
|
||||||
|
'f\#{:o}o'
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# We can skip the runtime conversion if we are
|
# We can skip the runtime conversion if we are
|
||||||
@@ -3405,13 +3503,13 @@ defmodule Kernel do
|
|||||||
quote do: Regex.compile!(unquote(binary), unquote(:binary.list_to_bin(options)))
|
quote do: Regex.compile!(unquote(binary), unquote(:binary.list_to_bin(options)))
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc ~S"""
|
||||||
Handles the sigil ~R. It returns a Regex pattern without escaping
|
Handles the sigil ~R. It returns a Regex pattern without escaping
|
||||||
nor interpreting interpolations.
|
nor interpreting interpolations.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> Regex.match?(~R(f\#{1,3}o), "f\#o")
|
iex> Regex.match?(~R(f#{1,3}o), "f#o")
|
||||||
true
|
true
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -3420,7 +3518,7 @@ defmodule Kernel do
|
|||||||
Macro.escape(regex)
|
Macro.escape(regex)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc ~S"""
|
||||||
Handles the sigil ~w. It returns a list of "words" split by whitespace.
|
Handles the sigil ~w. It returns a list of "words" split by whitespace.
|
||||||
|
|
||||||
## Modifiers
|
## Modifiers
|
||||||
@@ -3431,7 +3529,7 @@ defmodule Kernel do
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> ~w(foo \#{:bar} baz)
|
iex> ~w(foo #{:bar} baz)
|
||||||
["foo", "bar", "baz"]
|
["foo", "bar", "baz"]
|
||||||
|
|
||||||
iex> ~w(--source test/enum_test.exs)
|
iex> ~w(--source test/enum_test.exs)
|
||||||
@@ -3451,7 +3549,7 @@ defmodule Kernel do
|
|||||||
split_words(binary, modifiers)
|
split_words(binary, modifiers)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc ~S"""
|
||||||
Handles the sigil ~W. It returns a list of "words" split by whitespace
|
Handles the sigil ~W. It returns a list of "words" split by whitespace
|
||||||
without escaping nor interpreting interpolations.
|
without escaping nor interpreting interpolations.
|
||||||
|
|
||||||
@@ -3463,8 +3561,8 @@ defmodule Kernel do
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> ~W(foo \#{bar} baz)
|
iex> ~W(foo #{bar} baz)
|
||||||
["foo", "\\\#{bar}", "baz"]
|
["foo", "\#{bar}", "baz"]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
defmacro sigil_W({:<<>>, _line, [string]}, modifiers) when is_binary(string) do
|
defmacro sigil_W({:<<>>, _line, [string]}, modifiers) when is_binary(string) do
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ defmodule Kernel.CLI do
|
|||||||
|
|
||||||
@blank_config %{commands: [], output: ".", compile: [],
|
@blank_config %{commands: [], output: ".", compile: [],
|
||||||
halt: true, compiler_options: [], errors: [],
|
halt: true, compiler_options: [], errors: [],
|
||||||
verbose_compile: false}
|
pa: [], pz: [], verbose_compile: false}
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
This is the API invoked by Elixir boot process.
|
This is the API invoked by Elixir boot process.
|
||||||
@@ -12,9 +12,10 @@ defmodule Kernel.CLI do
|
|||||||
argv = for arg <- argv, do: IO.chardata_to_string(arg)
|
argv = for arg <- argv, do: IO.chardata_to_string(arg)
|
||||||
|
|
||||||
{config, argv} = parse_argv(argv)
|
{config, argv} = parse_argv(argv)
|
||||||
|
:elixir_code_server.cast({:paths, config.pa, config.pz})
|
||||||
System.argv(argv)
|
System.argv(argv)
|
||||||
|
|
||||||
run fn ->
|
run fn _ ->
|
||||||
errors = process_commands(config)
|
errors = process_commands(config)
|
||||||
|
|
||||||
if errors != [] do
|
if errors != [] do
|
||||||
@@ -33,37 +34,19 @@ defmodule Kernel.CLI do
|
|||||||
by escripts generated by Elixir.
|
by escripts generated by Elixir.
|
||||||
"""
|
"""
|
||||||
def run(fun, halt \\ true) do
|
def run(fun, halt \\ true) do
|
||||||
try do
|
res = exec_fun(fun, {:ok, 0})
|
||||||
fun.()
|
if elem(res, 0) == :shutdown or halt do
|
||||||
if halt do
|
{_, int} = at_exit(res)
|
||||||
at_exit(0)
|
System.halt(int)
|
||||||
System.halt(0)
|
|
||||||
end
|
|
||||||
catch
|
|
||||||
:exit, reason when is_integer(reason) ->
|
|
||||||
at_exit(reason)
|
|
||||||
System.halt(reason)
|
|
||||||
:exit, :normal ->
|
|
||||||
at_exit(0)
|
|
||||||
System.halt(0)
|
|
||||||
kind, reason ->
|
|
||||||
at_exit(1)
|
|
||||||
print_error(kind, reason, System.stacktrace)
|
|
||||||
System.halt(1)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc false
|
||||||
Parses ARGV returning the CLI config and trailing args.
|
|
||||||
"""
|
|
||||||
def parse_argv(argv) do
|
def parse_argv(argv) do
|
||||||
parse_argv(argv, @blank_config)
|
parse_argv(argv, @blank_config)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc false
|
||||||
Process commands according to the parsed config from `parse_argv/1`.
|
|
||||||
Returns all errors.
|
|
||||||
"""
|
|
||||||
def process_commands(config) do
|
def process_commands(config) do
|
||||||
results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
|
results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
|
||||||
errors = for {:error, msg} <- results, do: msg
|
errors = for {:error, msg} <- results, do: msg
|
||||||
@@ -72,21 +55,48 @@ defmodule Kernel.CLI do
|
|||||||
|
|
||||||
## Helpers
|
## Helpers
|
||||||
|
|
||||||
defp at_exit(status) do
|
defp at_exit(res) do
|
||||||
hooks = :elixir_code_server.call(:flush_at_exit)
|
hooks = :elixir_code_server.call(:flush_at_exit)
|
||||||
|
res = Enum.reduce(hooks, res, &exec_fun/2)
|
||||||
|
if hooks == [], do: res, else: at_exit(res)
|
||||||
|
end
|
||||||
|
|
||||||
for hook <- hooks do
|
defp exec_fun(fun, res) when is_function(fun, 1) and is_tuple(res) do
|
||||||
try do
|
parent = self()
|
||||||
hook.(status)
|
|
||||||
catch
|
{pid, ref} =
|
||||||
kind, reason ->
|
spawn_monitor fn ->
|
||||||
print_error(kind, reason, System.stacktrace)
|
try do
|
||||||
|
fun.(elem(res, 1))
|
||||||
|
catch
|
||||||
|
:exit, {:shutdown, int} when is_integer(int) ->
|
||||||
|
send parent, {self, {:shutdown, int}}
|
||||||
|
exit({:shutdown, int})
|
||||||
|
:exit, reason
|
||||||
|
when reason == :normal
|
||||||
|
when reason == :shutdown
|
||||||
|
when tuple_size(reason) == 2 and elem(reason, 0) == :shutdown ->
|
||||||
|
send parent, {self, {:shutdown, 0}}
|
||||||
|
exit(reason)
|
||||||
|
kind, reason ->
|
||||||
|
stack = System.stacktrace
|
||||||
|
print_error(kind, reason, stack)
|
||||||
|
send parent, {self, {:shutdown, 1}}
|
||||||
|
:erlang.raise(kind, reason, stack)
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
send parent, {self, res}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
# If an at_exit callback adds a
|
receive do
|
||||||
# new hook we need to invoke it.
|
{^pid, res} ->
|
||||||
unless hooks == [], do: at_exit(status)
|
:erlang.demonitor(ref, [:flush])
|
||||||
|
res
|
||||||
|
{:DOWN, ^ref, _, _, other} ->
|
||||||
|
print_error({:EXIT, pid}, other, [])
|
||||||
|
{:shutdown, 1}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp shared_option?(list, config, callback) do
|
defp shared_option?(list, config, callback) do
|
||||||
@@ -129,13 +139,15 @@ defmodule Kernel.CLI do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp parse_shared(["-pa", h|t], config) do
|
defp parse_shared(["-pa", h|t], config) do
|
||||||
add_code_path(h, &Code.prepend_path/1)
|
paths = expand_code_path(h)
|
||||||
parse_shared t, config
|
Enum.each(paths, &:code.add_patha/1)
|
||||||
|
parse_shared t, %{config | pa: config.pa ++ paths}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp parse_shared(["-pz", h|t], config) do
|
defp parse_shared(["-pz", h|t], config) do
|
||||||
add_code_path(h, &Code.append_path/1)
|
paths = expand_code_path(h)
|
||||||
parse_shared t, config
|
Enum.each(paths, &:code.add_pathz/1)
|
||||||
|
parse_shared t, %{config | pz: config.pz ++ paths}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp parse_shared(["--app", h|t], config) do
|
defp parse_shared(["--app", h|t], config) do
|
||||||
@@ -170,12 +182,11 @@ defmodule Kernel.CLI do
|
|||||||
{list, config}
|
{list, config}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp expand_code_path(path) do
|
||||||
defp add_code_path(path, fun) do
|
|
||||||
path = Path.expand(path)
|
path = Path.expand(path)
|
||||||
case Path.wildcard(path) do
|
case Path.wildcard(path) do
|
||||||
[] -> fun.(path)
|
[] -> [to_char_list(path)]
|
||||||
list -> Enum.each(list, fun)
|
list -> Enum.map(list, &to_char_list/1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -352,7 +363,8 @@ defmodule Kernel.CLI do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp process_command({:compile, patterns}, config) do
|
defp process_command({:compile, patterns}, config) do
|
||||||
:filelib.ensure_dir(:filename.join(config.output, "."))
|
# If ensuring the dir returns an error no files will be found.
|
||||||
|
_ = :filelib.ensure_dir(:filename.join(config.output, "."))
|
||||||
|
|
||||||
case filter_multiple_patterns(patterns) do
|
case filter_multiple_patterns(patterns) do
|
||||||
{:ok, []} ->
|
{:ok, []} ->
|
||||||
@@ -407,8 +419,8 @@ defmodule Kernel.CLI do
|
|||||||
# the actual Elixir executable.
|
# the actual Elixir executable.
|
||||||
case :os.type() do
|
case :os.type() do
|
||||||
{:win32, _} ->
|
{:win32, _} ->
|
||||||
exec = Path.rootname(exec)
|
base = Path.rootname(exec)
|
||||||
if File.regular?(exec), do: exec
|
if File.regular?(base), do: base, else: exec
|
||||||
_ ->
|
_ ->
|
||||||
exec
|
exec
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ defmodule Kernel.ErrorHandler do
|
|||||||
|
|
||||||
defp ensure_loaded(module) do
|
defp ensure_loaded(module) do
|
||||||
case Code.ensure_loaded(module) do
|
case Code.ensure_loaded(module) do
|
||||||
{:module, _} -> []
|
{:module, _} -> :ok
|
||||||
{:error, _} ->
|
{:error, _} ->
|
||||||
parent = :erlang.get(:elixir_compiler_pid)
|
parent = :erlang.get(:elixir_compiler_pid)
|
||||||
ref = :erlang.make_ref
|
ref = :erlang.make_ref
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ defmodule Kernel.ParallelCompiler do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp spawn_compilers(files, path, callbacks) do
|
defp spawn_compilers(files, path, callbacks) do
|
||||||
Code.ensure_loaded(Kernel.ErrorHandler)
|
true = Code.ensure_loaded?(Kernel.ErrorHandler)
|
||||||
compiler_pid = self()
|
compiler_pid = self()
|
||||||
:elixir_code_server.cast({:reset_warnings, compiler_pid})
|
:elixir_code_server.cast({:reset_warnings, compiler_pid})
|
||||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||||
@@ -54,7 +54,7 @@ defmodule Kernel.ParallelCompiler do
|
|||||||
# compilation status will be set to error and we fail with CompileError
|
# compilation status will be set to error and we fail with CompileError
|
||||||
case :elixir_code_server.call({:compilation_status, compiler_pid}) do
|
case :elixir_code_server.call({:compilation_status, compiler_pid}) do
|
||||||
:ok -> result
|
:ok -> result
|
||||||
:error -> exit(1)
|
:error -> exit({:shutdown, 1})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ defmodule Kernel.ParallelCompiler do
|
|||||||
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
|
||||||
|
|
||||||
exit(try do
|
exit(try do
|
||||||
if output do
|
_ = if output do
|
||||||
:elixir_compiler.file_to_path(h, output)
|
:elixir_compiler.file_to_path(h, output)
|
||||||
else
|
else
|
||||||
:elixir_compiler.file(h)
|
:elixir_compiler.file(h)
|
||||||
@@ -186,7 +186,7 @@ defmodule Kernel.ParallelCompiler do
|
|||||||
if all_missing?(entries, waiting, queued) do
|
if all_missing?(entries, waiting, queued) do
|
||||||
collect_failures(queued, length(queued) - 1)
|
collect_failures(queued, length(queued) - 1)
|
||||||
end
|
end
|
||||||
exit(1)
|
exit({:shutdown, 1})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -242,14 +242,14 @@ defmodule Kernel.SpecialForms do
|
|||||||
Here, `val` is interpreted as unsigned.
|
Here, `val` is interpreted as unsigned.
|
||||||
|
|
||||||
The endianness of a segment can be big, little or native (the
|
The endianness of a segment can be big, little or native (the
|
||||||
latter meaning it will be resolved at VM load time). Passing
|
latter meaning it will be resolved at VM load time). Many options
|
||||||
many options can be done by giving a list:
|
can be given by using `-` as separator:
|
||||||
|
|
||||||
<<102 :: [integer, native], rest :: binary>>
|
<<102 :: integer-native, rest :: binary>>
|
||||||
|
|
||||||
Or:
|
Or:
|
||||||
|
|
||||||
<<102 :: [unsigned, big, integer], rest :: binary>>
|
<<102 :: unsigned-big-integer, rest :: binary>>
|
||||||
|
|
||||||
And so on.
|
And so on.
|
||||||
|
|
||||||
@@ -271,7 +271,7 @@ defmodule Kernel.SpecialForms do
|
|||||||
|
|
||||||
We can also match by specifying size and unit explicitly:
|
We can also match by specifying size and unit explicitly:
|
||||||
|
|
||||||
iex> <<102, _rest :: [size(2), unit(8)]>> = "foo"
|
iex> <<102, _rest :: size(2)-unit(8)>> = "foo"
|
||||||
"foo"
|
"foo"
|
||||||
|
|
||||||
However, if we expect a size of 32, it won't match:
|
However, if we expect a size of 32, it won't match:
|
||||||
@@ -286,12 +286,15 @@ defmodule Kernel.SpecialForms do
|
|||||||
in a binary match can use the default size (all others must
|
in a binary match can use the default size (all others must
|
||||||
have their size specified explicitly).
|
have their size specified explicitly).
|
||||||
|
|
||||||
Size can also be specified using a syntax shortcut. Instead of
|
Size and unit can also be specified using a syntax shortcut
|
||||||
writing `size(8)`, one can write just `8` and it will be interpreted
|
when passing integer values:
|
||||||
as `size(8)`
|
|
||||||
|
|
||||||
iex> << 1 :: 3 >> == << 1 :: size(3) >>
|
<< x :: 8 >> == << x :: size(8) >>
|
||||||
true
|
<< x :: 8 * 4 >> == << x :: size(8)-unit(4) >>
|
||||||
|
<< x :: _ * 4 >> == << x :: unit(4) >>
|
||||||
|
|
||||||
|
This syntax reflects the fact the effective size is given by
|
||||||
|
multiplying the size by the unit.
|
||||||
|
|
||||||
For floats, `size * unit` must result in 32 or 64, corresponding
|
For floats, `size * unit` must result in 32 or 64, corresponding
|
||||||
to binary32 and binary64, respectively.
|
to binary32 and binary64, respectively.
|
||||||
@@ -1513,9 +1516,9 @@ defmodule Kernel.SpecialForms do
|
|||||||
The catch clause can be used to catch throws values and exits.
|
The catch clause can be used to catch throws values and exits.
|
||||||
|
|
||||||
try do
|
try do
|
||||||
exit(1)
|
exit(:shutdown)
|
||||||
catch
|
catch
|
||||||
:exit, 1 -> IO.puts "Exited with 1"
|
:exit, :shutdown -> IO.puts "Exited with shutdown reason"
|
||||||
end
|
end
|
||||||
|
|
||||||
try do
|
try do
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ defmodule Kernel.Typespec do
|
|||||||
| Fun
|
| Fun
|
||||||
| Integer
|
| Integer
|
||||||
| List
|
| List
|
||||||
|
| Map
|
||||||
| Tuple
|
| Tuple
|
||||||
| Union
|
| Union
|
||||||
| UserDefined # Described in section "Defining a type"
|
| UserDefined # Described in section "Defining a type"
|
||||||
@@ -65,16 +66,33 @@ defmodule Kernel.Typespec do
|
|||||||
| nonempty_list(Type) # proper non-empty list
|
| nonempty_list(Type) # proper non-empty list
|
||||||
| [] # empty list
|
| [] # empty list
|
||||||
| [Type] # shorthand for list(Type)
|
| [Type] # shorthand for list(Type)
|
||||||
|
| [...] # shorthand for nonempty_list()
|
||||||
| [Type, ...] # shorthand for nonempty_list(Type)
|
| [Type, ...] # shorthand for nonempty_list(Type)
|
||||||
|
| [Keyword]
|
||||||
|
|
||||||
Tuple :: tuple # a tuple of any size
|
Map :: map() # map of any size
|
||||||
| {} # empty tuple
|
| %{} # map of any size
|
||||||
|
| %Struct{} # struct (see defstruct/1)
|
||||||
|
| %Struct{Keyword}
|
||||||
|
| %{Keyword}
|
||||||
|
| %{Pairs}
|
||||||
|
|
||||||
|
Tuple :: tuple # a tuple of any size
|
||||||
|
| {} # empty tuple
|
||||||
| {TList}
|
| {TList}
|
||||||
|
| record(Atom) # record (see Record)
|
||||||
|
| record(Atom, Keyword)
|
||||||
|
|
||||||
|
Keyword :: ElixirAtom: Type
|
||||||
|
| ElixirAtom: Type, Keyword
|
||||||
|
|
||||||
|
Pairs :: Type => Type
|
||||||
|
| Type => Type, Pairs
|
||||||
|
|
||||||
TList :: Type
|
TList :: Type
|
||||||
| Type, TList
|
| Type, TList
|
||||||
|
|
||||||
Union :: Type1 | Type2
|
Union :: Type | Type
|
||||||
|
|
||||||
### Bit strings
|
### Bit strings
|
||||||
|
|
||||||
@@ -111,14 +129,15 @@ defmodule Kernel.Typespec do
|
|||||||
`bitstring` | `<< _ :: _ * 1 >>`
|
`bitstring` | `<< _ :: _ * 1 >>`
|
||||||
`boolean` | `false` | `true`
|
`boolean` | `false` | `true`
|
||||||
`byte` | `0..255`
|
`byte` | `0..255`
|
||||||
`char` | `0..0xffff`
|
`char` | `0..0x10ffff`
|
||||||
`number` | `integer` | `float`
|
`number` | `integer` | `float`
|
||||||
|
`char_list` | [char]
|
||||||
`list` | `[any]`
|
`list` | `[any]`
|
||||||
`maybe_improper_list` | `maybe_improper_list(any, any)`
|
`maybe_improper_list` | `maybe_improper_list(any, any)`
|
||||||
`nonempty_list` | `nonempty_list(any)`
|
`nonempty_list` | `nonempty_list(any)`
|
||||||
`iodata` | `iolist` | `binary`
|
`iodata` | `iolist` | `binary`
|
||||||
`iolist` | `maybe_improper_list(byte` | `binary` | `iolist, binary` | `[])`
|
`iolist` | `maybe_improper_list(byte` | `binary` | `iolist, binary` | `[])`
|
||||||
`module` | `atom`
|
`module` | `atom` | `tuple`
|
||||||
`mfa` | `{atom, atom, arity}`
|
`mfa` | `{atom, atom, arity}`
|
||||||
`arity` | `0..255`
|
`arity` | `0..255`
|
||||||
`node` | `atom`
|
`node` | `atom`
|
||||||
@@ -536,7 +555,7 @@ defmodule Kernel.Typespec do
|
|||||||
|
|
||||||
if not export and doc do
|
if not export and doc do
|
||||||
:elixir_errors.warn(caller.line, caller.file, "type #{name}/#{arity} is private, " <>
|
:elixir_errors.warn(caller.line, caller.file, "type #{name}/#{arity} is private, " <>
|
||||||
"@typedoc's are always discarded for private types\n")
|
"@typedoc's are always discarded for private types")
|
||||||
end
|
end
|
||||||
|
|
||||||
{{kind, {name, arity}, type}, caller.line, export, doc}
|
{{kind, {name, arity}, type}, caller.line, export, doc}
|
||||||
@@ -640,8 +659,20 @@ defmodule Kernel.Typespec do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp typespec_to_ast({:type, _line, :list, args}) do
|
defp typespec_to_ast({:type, line, :list, []}) do
|
||||||
for arg <- args, do: typespec_to_ast(arg)
|
{:list, [line: line], []}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp typespec_to_ast({:type, _line, :list, [arg]}) do
|
||||||
|
[typespec_to_ast(arg)]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp typespec_to_ast({:type, line, :nonempty_list, []}) do
|
||||||
|
[{:..., [line: line], nil}]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp typespec_to_ast({:type, line, :nonempty_list, [arg]}) do
|
||||||
|
[typespec_to_ast(arg), {:..., [line: line], nil}]
|
||||||
end
|
end
|
||||||
|
|
||||||
defp typespec_to_ast({:type, line, :map, fields}) do
|
defp typespec_to_ast({:type, line, :map, fields}) do
|
||||||
@@ -690,7 +721,11 @@ defmodule Kernel.Typespec do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp typespec_to_ast({:type, line, :range, [left, right]}) do
|
defp typespec_to_ast({:type, line, :range, [left, right]}) do
|
||||||
{:"..", [line: line], [typespec_to_ast(left), typespec_to_ast(right)]}
|
{:.., [line: line], [typespec_to_ast(left), typespec_to_ast(right)]}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp typespec_to_ast({:type, _line, nil, []}) do
|
||||||
|
[]
|
||||||
end
|
end
|
||||||
|
|
||||||
defp typespec_to_ast({:type, line, name, args}) do
|
defp typespec_to_ast({:type, line, name, args}) do
|
||||||
@@ -792,7 +827,58 @@ defmodule Kernel.Typespec do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp typespec({:%, _, [name, {:%{}, meta, fields}]}, vars, caller) do
|
defp typespec({:%, _, [name, {:%{}, meta, fields}]}, vars, caller) do
|
||||||
typespec({:%{}, meta, [{:__struct__, name}|fields]}, vars, caller)
|
module = Macro.expand(name, caller)
|
||||||
|
|
||||||
|
struct =
|
||||||
|
if module == caller.module do
|
||||||
|
Module.get_attribute(module, :struct) ||
|
||||||
|
compile_error(caller, "struct is not defined for #{Macro.to_string(name)}")
|
||||||
|
else
|
||||||
|
module.__struct__
|
||||||
|
end
|
||||||
|
|
||||||
|
struct =
|
||||||
|
:lists.map(fn {field, _} ->
|
||||||
|
{field, quote do: term()}
|
||||||
|
end, Map.to_list(struct))
|
||||||
|
|
||||||
|
|
||||||
|
:lists.foreach(fn {field, _} ->
|
||||||
|
unless Keyword.has_key?(struct, field) do
|
||||||
|
compile_error(caller, "undefined field #{field} on struct #{Macro.to_string(name)}")
|
||||||
|
end
|
||||||
|
end, fields)
|
||||||
|
|
||||||
|
fields = Keyword.merge(struct, [__struct__: module] ++ fields)
|
||||||
|
typespec({:%{}, meta, fields}, vars, caller)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Handle records
|
||||||
|
defp typespec({:record, meta, [atom]}, vars, caller) do
|
||||||
|
typespec({:record, meta, [atom, []]}, vars, caller)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp typespec({:record, meta, [atom, fields]}, vars, caller) do
|
||||||
|
case Macro.expand({atom, [], [{atom, [], []}]}, caller) do
|
||||||
|
keyword when is_list(keyword) ->
|
||||||
|
keyword =
|
||||||
|
:lists.map(fn {field, _} ->
|
||||||
|
{field, quote do: term()}
|
||||||
|
end, keyword)
|
||||||
|
|
||||||
|
:lists.foreach(fn {field, _} ->
|
||||||
|
unless Keyword.has_key?(keyword, field) do
|
||||||
|
compile_error(caller, "undefined field #{field} on record #{inspect atom}")
|
||||||
|
end
|
||||||
|
end, fields)
|
||||||
|
|
||||||
|
fields = Keyword.merge(keyword, fields)
|
||||||
|
types = Keyword.values(fields)
|
||||||
|
|
||||||
|
typespec({:{}, meta, [atom|types]}, vars, caller)
|
||||||
|
_ ->
|
||||||
|
compile_error(caller, "unknown record #{inspect atom}")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Handle ranges
|
# Handle ranges
|
||||||
@@ -866,11 +952,11 @@ defmodule Kernel.Typespec do
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Handle local calls
|
# Handle local calls
|
||||||
defp typespec({:string, meta, arguments}, vars, caller) do
|
defp typespec({type, meta, arguments}, vars, caller) when type in [:string, :nonempty_string] do
|
||||||
:elixir_errors.warn caller.line, caller.file, "string() type use is discouraged. For character lists, use " <>
|
:elixir_errors.warn caller.line, caller.file, "#{type}() type use is discouraged. For character lists, use " <>
|
||||||
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(Macro.Env.stacktrace(caller))}"
|
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(Macro.Env.stacktrace(caller))}"
|
||||||
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
|
||||||
{:type, line(meta), :string, arguments}
|
{:type, line(meta), type, arguments}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp typespec({:char_list, _meta, []}, vars, caller) do
|
defp typespec({:char_list, _meta, []}, vars, caller) do
|
||||||
@@ -899,14 +985,18 @@ defmodule Kernel.Typespec do
|
|||||||
typespec({nil, [], []}, vars, caller)
|
typespec({nil, [], []}, vars, caller)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp typespec([spec], vars, caller) do
|
defp typespec([{:..., _, atom}], vars, caller) when is_atom(atom) do
|
||||||
typespec({:list, [], [spec]}, vars, caller)
|
typespec({:nonempty_list, [], []}, vars, caller)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp typespec([spec, {:"...", _, quoted}], vars, caller) when is_atom(quoted) do
|
defp typespec([spec, {:..., _, atom}], vars, caller) when is_atom(atom) do
|
||||||
typespec({:nonempty_list, [], [spec]}, vars, caller)
|
typespec({:nonempty_list, [], [spec]}, vars, caller)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp typespec([spec], vars, caller) do
|
||||||
|
typespec({:list, [], [spec]}, vars, caller)
|
||||||
|
end
|
||||||
|
|
||||||
defp typespec(list, vars, caller) do
|
defp typespec(list, vars, caller) do
|
||||||
[h|t] = :lists.reverse(list)
|
[h|t] = :lists.reverse(list)
|
||||||
union = :lists.foldl(fn(x, acc) ->
|
union = :lists.foldl(fn(x, acc) ->
|
||||||
@@ -941,7 +1031,7 @@ defmodule Kernel.Typespec do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp fn_args(meta, [{:"...", _, _}], _vars, _caller) do
|
defp fn_args(meta, [{:..., _, _}], _vars, _caller) do
|
||||||
{:type, line(meta), :any}
|
{:type, line(meta), :any}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -307,6 +307,8 @@ defmodule List do
|
|||||||
@doc """
|
@doc """
|
||||||
Zips corresponding elements from each list in `list_of_lists`.
|
Zips corresponding elements from each list in `list_of_lists`.
|
||||||
|
|
||||||
|
The zipping finishes as soon as any list terminates.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> List.zip([[1, 2], [3, 4], [5, 6]])
|
iex> List.zip([[1, 2], [3, 4], [5, 6]])
|
||||||
@@ -323,8 +325,10 @@ defmodule List do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Unzips the given list of lists or tuples into separate lists and returns a
|
Unzips the given list of tuples into a list of lists.
|
||||||
list of lists.
|
|
||||||
|
The number of unzipped elements is equal to the size
|
||||||
|
of the smallest tuple in the list.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -566,7 +570,7 @@ defmodule List do
|
|||||||
"abc"
|
"abc"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec to_string(:unicode.char_list) :: String.t
|
@spec to_string(:unicode.charlist) :: String.t
|
||||||
def to_string(list) when is_list(list) do
|
def to_string(list) when is_list(list) do
|
||||||
case :unicode.characters_to_binary(list) do
|
case :unicode.characters_to_binary(list) do
|
||||||
result when is_binary(result) ->
|
result when is_binary(result) ->
|
||||||
|
|||||||
+37
-8
@@ -74,7 +74,7 @@ defmodule Macro do
|
|||||||
def pipe(expr, call_args, position)
|
def pipe(expr, call_args, position)
|
||||||
|
|
||||||
def pipe(expr, {:&, _, _} = call_args, _integer) do
|
def pipe(expr, {:&, _, _} = call_args, _integer) do
|
||||||
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}"
|
bad_pipe(expr, call_args)
|
||||||
end
|
end
|
||||||
|
|
||||||
def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do
|
def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do
|
||||||
@@ -86,7 +86,12 @@ defmodule Macro do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def pipe(expr, call_args, _integer) do
|
def pipe(expr, call_args, _integer) do
|
||||||
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}"
|
bad_pipe(expr, call_args)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp bad_pipe(expr, call_args) do
|
||||||
|
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}, " <>
|
||||||
|
"can only pipe into local calls foo(), remote calls Foo.bar() or anonymous functions calls foo.()"
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@@ -525,11 +530,15 @@ defmodule Macro do
|
|||||||
|
|
||||||
# All other calls
|
# All other calls
|
||||||
def to_string({target, _, args} = ast, fun) when is_list(args) do
|
def to_string({target, _, args} = ast, fun) when is_list(args) do
|
||||||
{list, last} = :elixir_utils.split_last(args)
|
if sigil = sigil_call(ast, fun) do
|
||||||
fun.(ast, case kw_blocks?(last) do
|
sigil
|
||||||
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
|
else
|
||||||
false -> call_to_string_with_args(target, args, fun)
|
{list, last} = :elixir_utils.split_last(args)
|
||||||
end)
|
fun.(ast, case kw_blocks?(last) do
|
||||||
|
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
|
||||||
|
false -> call_to_string_with_args(target, args, fun)
|
||||||
|
end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Two-item tuples
|
# Two-item tuples
|
||||||
@@ -565,6 +574,26 @@ defmodule Macro do
|
|||||||
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, [])
|
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, [])
|
||||||
defp module_to_string(other, fun), do: call_to_string(other, fun)
|
defp module_to_string(other, fun), do: call_to_string(other, fun)
|
||||||
|
|
||||||
|
defp sigil_call({func, _, [{:<<>>, _, [string]}, args]} = ast, fun) when is_list(args) do
|
||||||
|
sigil =
|
||||||
|
case Atom.to_string(func) do
|
||||||
|
<<"sigil_", name>> ->
|
||||||
|
"~" <> <<name>> <>
|
||||||
|
fun.(string, inspect(string, [])) <>
|
||||||
|
sigil_args(args, fun)
|
||||||
|
_ ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
fun.(ast, sigil)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp sigil_call(_other, _fun) do
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
defp sigil_args([], _fun), do: ""
|
||||||
|
defp sigil_args(args, fun), do: fun.(args, List.to_string(args))
|
||||||
|
|
||||||
defp call_to_string(atom, _fun) when is_atom(atom), do: Atom.to_string(atom)
|
defp call_to_string(atom, _fun) when is_atom(atom), do: Atom.to_string(atom)
|
||||||
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
|
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
|
||||||
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
|
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
|
||||||
@@ -838,7 +867,7 @@ defmodule Macro do
|
|||||||
end
|
end
|
||||||
|
|
||||||
expand = :elixir_dispatch.expand_import(meta, {atom, length(args)}, args,
|
expand = :elixir_dispatch.expand_import(meta, {atom, length(args)}, args,
|
||||||
env, extra)
|
env, extra, true)
|
||||||
|
|
||||||
case expand do
|
case expand do
|
||||||
{:ok, receiver, quoted} ->
|
{:ok, receiver, quoted} ->
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ defmodule Macro.Env do
|
|||||||
* `local` - the module to expand local functions to
|
* `local` - the module to expand local functions to
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@type name_arity :: {atom, non_neg_integer}
|
@type name_arity :: {atom, arity}
|
||||||
@type file :: binary
|
@type file :: binary
|
||||||
@type line :: non_neg_integer
|
@type line :: non_neg_integer
|
||||||
@type aliases :: [{module, module}]
|
@type aliases :: [{module, module}]
|
||||||
@@ -42,10 +42,10 @@ defmodule Macro.Env do
|
|||||||
@type vars :: [{atom, atom | non_neg_integer}]
|
@type vars :: [{atom, atom | non_neg_integer}]
|
||||||
@type export_vars :: vars | nil
|
@type export_vars :: vars | nil
|
||||||
@type lexical_tracker :: pid
|
@type lexical_tracker :: pid
|
||||||
@type local :: module | nil
|
@type local :: atom | nil
|
||||||
|
|
||||||
@type t :: %{__struct__: __MODULE__,
|
@type t :: %{__struct__: __MODULE__,
|
||||||
module: module,
|
module: atom,
|
||||||
file: file,
|
file: file,
|
||||||
line: line,
|
line: line,
|
||||||
function: name_arity | nil,
|
function: name_arity | nil,
|
||||||
|
|||||||
+44
-41
@@ -766,15 +766,9 @@ defmodule Module do
|
|||||||
|
|
||||||
@foo
|
@foo
|
||||||
|
|
||||||
Expands to:
|
Expands close to:
|
||||||
|
|
||||||
Module.get_attribute(__MODULE__, :foo, true)
|
Module.get_attribute(__MODULE__, :foo)
|
||||||
|
|
||||||
Notice the third argument may be given to indicate a stacktrace
|
|
||||||
to be emitted when the attribute was not previously defined.
|
|
||||||
The default value for `warn` is nil for direct calls but the `@foo`
|
|
||||||
macro sets it to the proper stacktrace automatically, warning
|
|
||||||
every time `@foo` is used but not set previously.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -788,37 +782,9 @@ defmodule Module do
|
|||||||
end
|
end
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec get_attribute(module, atom, warn :: nil | [tuple]) :: term
|
@spec get_attribute(atom, atom) :: term
|
||||||
def get_attribute(module, key, warn \\ nil) when
|
def get_attribute(module, key) do
|
||||||
is_atom(key) and (is_list(warn) or nil?(warn)) do
|
get_attribute(module, key, nil)
|
||||||
assert_not_compiled!(:get_attribute, module)
|
|
||||||
table = data_table_for(module)
|
|
||||||
|
|
||||||
case :ets.lookup(table, key) do
|
|
||||||
[{^key, val}] -> val
|
|
||||||
[] ->
|
|
||||||
acc = :ets.lookup_element(table, :__acc_attributes, 2)
|
|
||||||
|
|
||||||
cond do
|
|
||||||
:lists.member(key, acc) ->
|
|
||||||
[]
|
|
||||||
is_list(warn) ->
|
|
||||||
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
|
|
||||||
"please remove access to @#{key} or explicitly set it to nil before access\n"
|
|
||||||
nil
|
|
||||||
true ->
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp warn_info([entry|_]) do
|
|
||||||
opts = elem(entry, tuple_size(entry) - 1)
|
|
||||||
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
|
|
||||||
end
|
|
||||||
|
|
||||||
defp warn_info([]) do
|
|
||||||
""
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@@ -832,10 +798,12 @@ defmodule Module do
|
|||||||
end
|
end
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@spec delete_attribute(atom, atom) :: :ok
|
||||||
def delete_attribute(module, key) when is_atom(key) do
|
def delete_attribute(module, key) when is_atom(key) do
|
||||||
assert_not_compiled!(:delete_attribute, module)
|
assert_not_compiled!(:delete_attribute, module)
|
||||||
table = data_table_for(module)
|
table = data_table_for(module)
|
||||||
:ets.delete(table, key)
|
:ets.delete(table, key)
|
||||||
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@@ -911,7 +879,9 @@ defmodule Module do
|
|||||||
:ok ->
|
:ok ->
|
||||||
:ok
|
:ok
|
||||||
{:error, :private_doc} ->
|
{:error, :private_doc} ->
|
||||||
:elixir_errors.warn line, env.file, "function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
|
:elixir_errors.warn line, env.file,
|
||||||
|
"function #{name}/#{arity} is private, " <>
|
||||||
|
"@doc's are always discarded for private functions"
|
||||||
end
|
end
|
||||||
|
|
||||||
delete_attribute(module, :doc)
|
delete_attribute(module, :doc)
|
||||||
@@ -933,6 +903,38 @@ defmodule Module do
|
|||||||
:ets.insert(table, {key, new})
|
:ets.insert(table, {key, new})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def get_attribute(module, key, warn) when is_atom(key) and (is_list(warn) or nil?(warn)) do
|
||||||
|
assert_not_compiled!(:get_attribute, module)
|
||||||
|
table = data_table_for(module)
|
||||||
|
|
||||||
|
case :ets.lookup(table, key) do
|
||||||
|
[{^key, val}] -> val
|
||||||
|
[] ->
|
||||||
|
acc = :ets.lookup_element(table, :__acc_attributes, 2)
|
||||||
|
|
||||||
|
cond do
|
||||||
|
:lists.member(key, acc) ->
|
||||||
|
[]
|
||||||
|
is_list(warn) ->
|
||||||
|
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
|
||||||
|
"please remove access to @#{key} or explicitly set it to nil before access"
|
||||||
|
nil
|
||||||
|
true ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp warn_info([entry|_]) do
|
||||||
|
opts = elem(entry, tuple_size(entry) - 1)
|
||||||
|
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
|
||||||
|
end
|
||||||
|
|
||||||
|
defp warn_info([]) do
|
||||||
|
""
|
||||||
|
end
|
||||||
|
|
||||||
## Helpers
|
## Helpers
|
||||||
|
|
||||||
defp normalize_attribute(:on_load, atom) when is_atom(atom) do
|
defp normalize_attribute(:on_load, atom) when is_atom(atom) do
|
||||||
@@ -940,7 +942,8 @@ defmodule Module do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp normalize_attribute(:behaviour, atom) when is_atom(atom) do
|
defp normalize_attribute(:behaviour, atom) when is_atom(atom) do
|
||||||
Code.ensure_compiled(atom)
|
# Attempt to compile behaviour but ignore failure (will warn later)
|
||||||
|
_ = Code.ensure_compiled(atom)
|
||||||
atom
|
atom
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -65,16 +65,19 @@ defmodule Module.LocalsTracker do
|
|||||||
"""
|
"""
|
||||||
@spec reachable(ref) :: [local]
|
@spec reachable(ref) :: [local]
|
||||||
def reachable(ref) do
|
def reachable(ref) do
|
||||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
reachable_from(:gen_server.call(to_pid(ref), :digraph, @timeout), :local)
|
||||||
reduce_reachable(d, :local, [])
|
end
|
||||||
|
|
||||||
|
defp reachable_from(d, starting) do
|
||||||
|
:sets.to_list(reduce_reachable(d, starting, :sets.new))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp reduce_reachable(d, vertex, vertices) do
|
defp reduce_reachable(d, vertex, vertices) do
|
||||||
neighbours = :digraph.out_neighbours(d, vertex)
|
neighbours = :digraph.out_neighbours(d, vertex)
|
||||||
neighbours = (for {_, _} = t <- neighbours, do: t) |> :ordsets.from_list
|
neighbours = (for {_, _} = t <- neighbours, do: t) |> :sets.from_list
|
||||||
remaining = :ordsets.subtract(neighbours, vertices)
|
remaining = :sets.subtract(neighbours, vertices)
|
||||||
vertices = :ordsets.union(neighbours, vertices)
|
vertices = :sets.union(neighbours, vertices)
|
||||||
:lists.foldl(&reduce_reachable(d, &1, &2), vertices, remaining)
|
:sets.fold(&reduce_reachable(d, &1, &2), vertices, remaining)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp to_pid(pid) when is_pid(pid), do: pid
|
defp to_pid(pid) when is_pid(pid), do: pid
|
||||||
@@ -134,9 +137,7 @@ defmodule Module.LocalsTracker do
|
|||||||
# Reattach a previously yanked node
|
# Reattach a previously yanked node
|
||||||
@doc false
|
@doc false
|
||||||
def reattach(pid, kind, tuple, neighbours) do
|
def reattach(pid, kind, tuple, neighbours) do
|
||||||
pid = to_pid(pid)
|
:gen_server.cast(to_pid(pid), {:reattach, kind, tuple, neighbours})
|
||||||
add_definition(pid, kind, tuple)
|
|
||||||
:gen_server.cast(pid, {:reattach, tuple, neighbours})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Collecting all conflicting imports with the given functions
|
# Collecting all conflicting imports with the given functions
|
||||||
@@ -156,12 +157,42 @@ defmodule Module.LocalsTracker do
|
|||||||
# given also accounting the expected amount of default
|
# given also accounting the expected amount of default
|
||||||
# clauses a private function have.
|
# clauses a private function have.
|
||||||
@doc false
|
@doc false
|
||||||
def collect_unused_locals(pid, private) do
|
def collect_unused_locals(ref, private) do
|
||||||
reachable = reachable(pid)
|
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||||
:lists.foldl(&collect_unused_locals(&1, &2, reachable), [], private)
|
{unreachable(d, private), collect_warnings(d, private)}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp collect_unused_locals({tuple, kind, 0}, acc, reachable) do
|
defp unreachable(d, private) do
|
||||||
|
unreachable = for {tuple, _, _} <- private, do: tuple
|
||||||
|
|
||||||
|
private =
|
||||||
|
for {tuple, :defp, _} <- private do
|
||||||
|
neighbours = :digraph.in_neighbours(d, tuple)
|
||||||
|
neighbours = for {_, _} = t <- neighbours, do: t
|
||||||
|
{tuple, :sets.from_list(neighbours)}
|
||||||
|
end
|
||||||
|
|
||||||
|
reduce_unreachable(private, [], :sets.from_list(unreachable))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp reduce_unreachable([{vertex, callers}|t], acc, unreachable) do
|
||||||
|
if :sets.is_subset(callers, unreachable) do
|
||||||
|
reduce_unreachable(t, [{vertex, callers}|acc], unreachable)
|
||||||
|
else
|
||||||
|
reduce_unreachable(acc ++ t, [], :sets.del_element(vertex, unreachable))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp reduce_unreachable([], _acc, unreachable) do
|
||||||
|
:sets.to_list(unreachable)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp collect_warnings(d, private) do
|
||||||
|
reachable = reachable_from(d, :local)
|
||||||
|
:lists.foldl(&collect_warnings(&1, &2, reachable), [], private)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp collect_warnings({tuple, kind, 0}, acc, reachable) do
|
||||||
if :lists.member(tuple, reachable) do
|
if :lists.member(tuple, reachable) do
|
||||||
acc
|
acc
|
||||||
else
|
else
|
||||||
@@ -169,7 +200,7 @@ defmodule Module.LocalsTracker do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp collect_unused_locals({tuple, kind, default}, acc, reachable) when default > 0 do
|
defp collect_warnings({tuple, kind, default}, acc, reachable) when default > 0 do
|
||||||
{name, arity} = tuple
|
{name, arity} = tuple
|
||||||
min = arity - default
|
min = arity - default
|
||||||
max = arity
|
max = arity
|
||||||
@@ -268,9 +299,19 @@ defmodule Module.LocalsTracker do
|
|||||||
{:noreply, state}
|
{:noreply, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_cast({:reattach, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
|
def handle_cast({:reattach, kind, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
|
||||||
for from <- in_neigh, do: replace_edge(d, from, tuple)
|
handle_add_definition(d, kind, tuple)
|
||||||
for to <- out_neigh, do: replace_edge(d, tuple, to)
|
|
||||||
|
for from <- in_neigh do
|
||||||
|
:digraph.add_vertex(d, from)
|
||||||
|
replace_edge!(d, from, tuple)
|
||||||
|
end
|
||||||
|
|
||||||
|
for to <- out_neigh do
|
||||||
|
:digraph.add_vertex(d, to)
|
||||||
|
replace_edge!(d, tuple, to)
|
||||||
|
end
|
||||||
|
|
||||||
{:noreply, state}
|
{:noreply, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -300,6 +341,8 @@ defmodule Module.LocalsTracker do
|
|||||||
if function != nil do
|
if function != nil do
|
||||||
replace_edge!(d, function, tuple)
|
replace_edge!(d, function, tuple)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
defp handle_add_local(d, from, to) do
|
defp handle_add_local(d, from, to) do
|
||||||
@@ -320,11 +363,6 @@ defmodule Module.LocalsTracker do
|
|||||||
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
|
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
|
||||||
[:"$e"|_] = :digraph.add_edge(d, from, to)
|
[:"$e"|_] = :digraph.add_edge(d, from, to)
|
||||||
end
|
end
|
||||||
end
|
:ok
|
||||||
|
|
||||||
defp replace_edge(d, from, to) do
|
|
||||||
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
|
|
||||||
:digraph.add_edge(d, from, to)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ defmodule OptionParser do
|
|||||||
* `:strict` - the switches are strict. Any switch that does not
|
* `:strict` - the switches are strict. Any switch that does not
|
||||||
exist in the switch list is treated as an error.
|
exist in the switch list is treated as an error.
|
||||||
|
|
||||||
* `:switches` - configure some switches. Switches that does not
|
* `:switches` - defines some switches. Switches that does not
|
||||||
exist in the switch list are still attempted to be parsed.
|
exist in the switch list are still attempted to be parsed.
|
||||||
|
|
||||||
Note only `:strict` or `:switches` may be given at once.
|
Note only `:strict` or `:switches` may be given at once.
|
||||||
@@ -59,7 +59,7 @@ defmodule OptionParser do
|
|||||||
* `:float` - parses the switch as a float.
|
* `:float` - parses the switch as a float.
|
||||||
* `:string` - returns the switch as a string.
|
* `:string` - returns the switch as a string.
|
||||||
|
|
||||||
If a switch can't be parsed or is not specfied in the strict case,
|
If a switch can't be parsed or is not specified in the strict case,
|
||||||
the option is returned in the invalid options list (third element
|
the option is returned in the invalid options list (third element
|
||||||
of the returned tuple).
|
of the returned tuple).
|
||||||
|
|
||||||
@@ -91,14 +91,8 @@ defmodule OptionParser do
|
|||||||
|
|
||||||
## Negation switches
|
## Negation switches
|
||||||
|
|
||||||
All switches starting with `--no-` are considered to be booleans and never
|
In case a switch is declared as boolean, it may be passed as `--no-SWITCH`
|
||||||
parse the next value:
|
which will set the option to false:
|
||||||
|
|
||||||
iex> OptionParser.parse(["--no-op", "path/to/file"])
|
|
||||||
{[no_op: true], ["path/to/file"], []}
|
|
||||||
|
|
||||||
However, in case the base switch exists, it sets that particular switch to
|
|
||||||
false:
|
|
||||||
|
|
||||||
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
|
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
|
||||||
{[op: false], ["path/to/file"], []}
|
{[op: false], ["path/to/file"], []}
|
||||||
@@ -113,8 +107,7 @@ defmodule OptionParser do
|
|||||||
"""
|
"""
|
||||||
@spec parse(argv, options) :: {parsed, argv, errors}
|
@spec parse(argv, options) :: {parsed, argv, errors}
|
||||||
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||||
config = compile_config(opts, true)
|
do_parse(argv, compile_config(opts), [], [], [], true)
|
||||||
do_parse(argv, config, [], [], [])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@@ -134,37 +127,36 @@ defmodule OptionParser do
|
|||||||
"""
|
"""
|
||||||
@spec parse_head(argv, options) :: {parsed, argv, errors}
|
@spec parse_head(argv, options) :: {parsed, argv, errors}
|
||||||
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||||
config = compile_config(opts, false)
|
do_parse(argv, compile_config(opts), [], [], [], false)
|
||||||
do_parse(argv, config, [], [], [])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_parse([], _config, opts, args, invalid) do
|
defp do_parse([], _config, opts, args, invalid, _all?) do
|
||||||
{Enum.reverse(opts), Enum.reverse(args), Enum.reverse(invalid)}
|
{Enum.reverse(opts), Enum.reverse(args), Enum.reverse(invalid)}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_parse(argv, {aliases, switches, strict, all}=config, opts, args, invalid) do
|
defp do_parse(argv, {aliases, switches, strict}=config, opts, args, invalid, all?) do
|
||||||
case next(argv, aliases, switches, strict) do
|
case next(argv, aliases, switches, strict) do
|
||||||
{:ok, option, value, rest} ->
|
{:ok, option, value, rest} ->
|
||||||
# the option exist and it was successfully parsed
|
# the option exist and it was successfully parsed
|
||||||
kinds = List.wrap Keyword.get(switches, option)
|
kinds = List.wrap Keyword.get(switches, option)
|
||||||
new_opts = do_store_option(opts, option, value, kinds)
|
new_opts = do_store_option(opts, option, value, kinds)
|
||||||
do_parse(rest, config, new_opts, args, invalid)
|
do_parse(rest, config, new_opts, args, invalid, all?)
|
||||||
|
|
||||||
{:invalid, option, value, rest} ->
|
{:invalid, option, value, rest} ->
|
||||||
# the option exist but it has wrong value
|
# the option exist but it has wrong value
|
||||||
do_parse(rest, config, opts, args, [{option, value}|invalid])
|
do_parse(rest, config, opts, args, [{option, value}|invalid], all?)
|
||||||
|
|
||||||
{:undefined, option, _value, rest} ->
|
{:undefined, option, _value, rest} ->
|
||||||
# the option does not exist (for strict cases)
|
# the option does not exist (for strict cases)
|
||||||
do_parse(rest, config, opts, args, [{option, nil}|invalid])
|
do_parse(rest, config, opts, args, [{option, nil}|invalid], all?)
|
||||||
|
|
||||||
{:error, ["--"|rest]} ->
|
{:error, ["--"|rest]} ->
|
||||||
{Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
|
{Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
|
||||||
|
|
||||||
{:error, [arg|rest]=remaining_args} ->
|
{:error, [arg|rest]=remaining_args} ->
|
||||||
# there is no option
|
# there is no option
|
||||||
if all do
|
if all? do
|
||||||
do_parse(rest, config, opts, [arg|args], invalid)
|
do_parse(rest, config, opts, [arg|args], invalid, all?)
|
||||||
else
|
else
|
||||||
{Enum.reverse(opts), Enum.reverse(args, remaining_args), Enum.reverse(invalid)}
|
{Enum.reverse(opts), Enum.reverse(args, remaining_args), Enum.reverse(invalid)}
|
||||||
end
|
end
|
||||||
@@ -193,12 +185,12 @@ defmodule OptionParser do
|
|||||||
|
|
||||||
@spec next(argv, options) ::
|
@spec next(argv, options) ::
|
||||||
{:ok, key :: atom, value :: term, argv} |
|
{:ok, key :: atom, value :: term, argv} |
|
||||||
{:invalid, key :: atom, value :: term, argv} |
|
{:invalid, String.t, String.t | nil, argv} |
|
||||||
{:undefined, key :: atom, value :: term, argv} |
|
{:undefined, String.t, String.t | nil, argv} |
|
||||||
{:error, argv}
|
{:error, argv}
|
||||||
|
|
||||||
def next(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
def next(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||||
{aliases, switches, strict, _} = compile_config(opts, true)
|
{aliases, switches, strict} = compile_config(opts)
|
||||||
next(argv, aliases, switches, strict)
|
next(argv, aliases, switches, strict)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -221,7 +213,7 @@ defmodule OptionParser do
|
|||||||
defp next(["-" <> option|rest], aliases, switches, strict) do
|
defp next(["-" <> option|rest], aliases, switches, strict) do
|
||||||
{option, value} = split_option(option)
|
{option, value} = split_option(option)
|
||||||
opt_name_bin = "-" <> option
|
opt_name_bin = "-" <> option
|
||||||
tagged = tag_option(option, value, switches, aliases)
|
tagged = tag_option(option, switches, aliases)
|
||||||
|
|
||||||
if strict and not option_defined?(tagged, switches) do
|
if strict and not option_defined?(tagged, switches) do
|
||||||
{:undefined, opt_name_bin, value, rest}
|
{:undefined, opt_name_bin, value, rest}
|
||||||
@@ -239,9 +231,65 @@ defmodule OptionParser do
|
|||||||
{:error, argv}
|
{:error, argv}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc ~S"""
|
||||||
|
Splits a string into argv chunks.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> OptionParser.split("foo bar")
|
||||||
|
["foo", "bar"]
|
||||||
|
|
||||||
|
iex> OptionParser.split("foo \"bar baz\"")
|
||||||
|
["foo", "bar baz"]
|
||||||
|
"""
|
||||||
|
@spec split(String.t) :: argv
|
||||||
|
def split(string) do
|
||||||
|
do_split(strip_leading_spaces(string), "", [], nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
# If we have a escaped quote, simply remove the escape
|
||||||
|
defp do_split(<<?\\, quote, t :: binary>>, buffer, acc, quote),
|
||||||
|
do: do_split(t, <<buffer::binary, quote>>, acc, quote)
|
||||||
|
|
||||||
|
# If we have a quote and we were not in a quote, start one
|
||||||
|
defp do_split(<<quote, t :: binary>>, buffer, acc, nil) when quote in [?", ?'],
|
||||||
|
do: do_split(t, buffer, acc, quote)
|
||||||
|
|
||||||
|
# If we have a quote and we were inside it, close it
|
||||||
|
defp do_split(<<quote, t :: binary>>, buffer, acc, quote),
|
||||||
|
do: do_split(t, buffer, acc, nil)
|
||||||
|
|
||||||
|
# If we have a escaped quote/space, simply remove the escape as long as we are not inside a quote
|
||||||
|
defp do_split(<<?\\, h, t :: binary>>, buffer, acc, nil) when h in [?\s, ?', ?"],
|
||||||
|
do: do_split(t, <<buffer::binary, h>>, acc, nil)
|
||||||
|
|
||||||
|
# If we have space and we are outside of a quote, start new segment
|
||||||
|
defp do_split(<<?\s, t :: binary>>, buffer, acc, nil),
|
||||||
|
do: do_split(strip_leading_spaces(t), "", [buffer|acc], nil)
|
||||||
|
|
||||||
|
# All other characters are moved to buffer
|
||||||
|
defp do_split(<<h, t::binary>>, buffer, acc, quote) do
|
||||||
|
do_split(t, <<buffer::binary, h>>, acc, quote)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Finish the string expecting a nil marker
|
||||||
|
defp do_split(<<>>, "", acc, nil),
|
||||||
|
do: Enum.reverse(acc)
|
||||||
|
|
||||||
|
defp do_split(<<>>, buffer, acc, nil),
|
||||||
|
do: Enum.reverse([buffer|acc])
|
||||||
|
|
||||||
|
# Otherwise raise
|
||||||
|
defp do_split(<<>>, _, _acc, marker) do
|
||||||
|
raise "argv string did not terminate properly, a #{<<marker>>} was opened but never closed"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp strip_leading_spaces(" " <> t), do: strip_leading_spaces(t)
|
||||||
|
defp strip_leading_spaces(t), do: t
|
||||||
|
|
||||||
## Helpers
|
## Helpers
|
||||||
|
|
||||||
defp compile_config(opts, all) do
|
defp compile_config(opts) do
|
||||||
aliases = opts[:aliases] || []
|
aliases = opts[:aliases] || []
|
||||||
|
|
||||||
{switches, strict} = cond do
|
{switches, strict} = cond do
|
||||||
@@ -253,7 +301,7 @@ defmodule OptionParser do
|
|||||||
{[], false}
|
{[], false}
|
||||||
end
|
end
|
||||||
|
|
||||||
{aliases, switches, strict, all}
|
{aliases, switches, strict}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp validate_option(value, kinds) do
|
defp validate_option(value, kinds) do
|
||||||
@@ -296,11 +344,11 @@ defmodule OptionParser do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp tag_option(<<?-, option :: binary>>, value, switches, _aliases) do
|
defp tag_option(<<?-, option :: binary>>, switches, _aliases) do
|
||||||
get_negated(option, value, switches)
|
get_negated(option, switches)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp tag_option(option, _value, _switches, aliases) when is_binary(option) do
|
defp tag_option(option, _switches, aliases) when is_binary(option) do
|
||||||
opt = get_option(option)
|
opt = get_option(option)
|
||||||
if alias = aliases[opt] do
|
if alias = aliases[opt] do
|
||||||
{:default, alias}
|
{:default, alias}
|
||||||
@@ -325,23 +373,14 @@ defmodule OptionParser do
|
|||||||
{nil, [:invalid], value}
|
{nil, [:invalid], value}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp normalize_option({:negated, option}, nil, switches) do
|
defp normalize_option({:negated, option}, value, switches) do
|
||||||
kinds = List.wrap(switches[option])
|
if value do
|
||||||
|
{option, [:invalid], value}
|
||||||
cond do
|
else
|
||||||
:boolean in kinds ->
|
{option, List.wrap(switches[option]), false}
|
||||||
{option, kinds, false}
|
|
||||||
kinds == [] ->
|
|
||||||
{option, kinds, true}
|
|
||||||
true ->
|
|
||||||
{reverse_negated(option), [:invalid], nil}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp normalize_option({:negated, option}, value, _switches) do
|
|
||||||
{option, [:invalid], value}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp normalize_option({:default, option}, value, switches) do
|
defp normalize_option({:default, option}, value, switches) do
|
||||||
{option, List.wrap(switches[option]), value}
|
{option, List.wrap(switches[option]), value}
|
||||||
end
|
end
|
||||||
@@ -396,24 +435,18 @@ defmodule OptionParser do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp reverse_negated(negated) do
|
defp get_negated("no-" <> rest = original, switches) do
|
||||||
String.to_atom("no_" <> Atom.to_string(negated))
|
cond do
|
||||||
end
|
(negated = get_option(rest)) && :boolean in List.wrap(switches[negated]) ->
|
||||||
|
{:negated, negated}
|
||||||
defp get_negated("no-" <> rest = option, value, switches) do
|
option = get_option(original) ->
|
||||||
if negated = get_option(rest) do
|
{:default, option}
|
||||||
option = if Keyword.has_key?(switches, negated) and value == nil do
|
true ->
|
||||||
negated
|
:unknown
|
||||||
else
|
|
||||||
get_option(option)
|
|
||||||
end
|
|
||||||
{:negated, option}
|
|
||||||
else
|
|
||||||
:unknown
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_negated(rest, _value, _switches) do
|
defp get_negated(rest, _switches) do
|
||||||
if option = get_option(rest) do
|
if option = get_option(rest) do
|
||||||
{:default, option}
|
{:default, option}
|
||||||
else
|
else
|
||||||
|
|||||||
+14
-4
@@ -159,7 +159,7 @@ defmodule Path do
|
|||||||
def type(name) when is_list(name) or is_binary(name) do
|
def type(name) when is_list(name) or is_binary(name) do
|
||||||
case :os.type() do
|
case :os.type() do
|
||||||
{:win32, _} -> win32_pathtype(name)
|
{:win32, _} -> win32_pathtype(name)
|
||||||
_ -> unix_pathtype(name)
|
_ -> unix_pathtype(name)
|
||||||
end |> elem(0)
|
end |> elem(0)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -458,9 +458,14 @@ defmodule Path do
|
|||||||
defp maybe_remove_dirsep(name, _), do:
|
defp maybe_remove_dirsep(name, _), do:
|
||||||
:lists.reverse(name)
|
:lists.reverse(name)
|
||||||
|
|
||||||
@doc """
|
@doc ~S"""
|
||||||
Returns a list with the path split by the path separator.
|
Splits the path into a list at the path separator.
|
||||||
If an empty string is given, returns the root path.
|
|
||||||
|
If an empty string is given, returns an empty list.
|
||||||
|
|
||||||
|
On Windows, path is split on both "\" and "/" separators
|
||||||
|
and the driver letter, if there is one, is always returned
|
||||||
|
in lowercase.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -486,6 +491,11 @@ defmodule Path do
|
|||||||
defmodule Wildcard do
|
defmodule Wildcard do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
|
def read_link_info(file) do
|
||||||
|
call({:read_link_info, file})
|
||||||
|
end
|
||||||
|
|
||||||
|
# For compatibility with buggy Erlang 17.1.
|
||||||
def read_file_info(file) do
|
def read_file_info(file) do
|
||||||
call({:read_link_info, file})
|
call({:read_link_info, file})
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ defmodule Port do
|
|||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
See http://www.erlang.org/doc/man/erlang.html#open_port-2.
|
See http://www.erlang.org/doc/man/erlang.html#open_port-2.
|
||||||
|
|
||||||
|
Inlined by the compiler.
|
||||||
"""
|
"""
|
||||||
def open(name, settings) do
|
def open(name, settings) do
|
||||||
:erlang.open_port(name, settings)
|
:erlang.open_port(name, settings)
|
||||||
@@ -12,6 +14,8 @@ defmodule Port do
|
|||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
See http://www.erlang.org/doc/man/erlang.html#port_close-1.
|
See http://www.erlang.org/doc/man/erlang.html#port_close-1.
|
||||||
|
|
||||||
|
Inlined by the compiler.
|
||||||
"""
|
"""
|
||||||
def close(port) do
|
def close(port) do
|
||||||
:erlang.port_close(port)
|
:erlang.port_close(port)
|
||||||
@@ -19,6 +23,8 @@ defmodule Port do
|
|||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
See http://www.erlang.org/doc/man/erlang.html#port_command-2.
|
See http://www.erlang.org/doc/man/erlang.html#port_command-2.
|
||||||
|
|
||||||
|
Inlined by the compiler.
|
||||||
"""
|
"""
|
||||||
def command(port, data, options \\ []) do
|
def command(port, data, options \\ []) do
|
||||||
:erlang.port_command(port, data, options)
|
:erlang.port_command(port, data, options)
|
||||||
@@ -26,6 +32,8 @@ defmodule Port do
|
|||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
See http://www.erlang.org/doc/man/erlang.html#port_connect-2.
|
See http://www.erlang.org/doc/man/erlang.html#port_connect-2.
|
||||||
|
|
||||||
|
Inlined by the compiler.
|
||||||
"""
|
"""
|
||||||
def connect(port, pid) do
|
def connect(port, pid) do
|
||||||
:erlang.port_connect(port, pid)
|
:erlang.port_connect(port, pid)
|
||||||
@@ -33,6 +41,8 @@ defmodule Port do
|
|||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
See http://www.erlang.org/doc/man/erlang.html#port_control-3.
|
See http://www.erlang.org/doc/man/erlang.html#port_control-3.
|
||||||
|
|
||||||
|
Inlined by the compiler.
|
||||||
"""
|
"""
|
||||||
def control(port, operation, data) do
|
def control(port, operation, data) do
|
||||||
:erlang.port_control(port, operation, data)
|
:erlang.port_control(port, operation, data)
|
||||||
@@ -40,6 +50,8 @@ defmodule Port do
|
|||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
See http://www.erlang.org/doc/man/erlang.html#port_call-3.
|
See http://www.erlang.org/doc/man/erlang.html#port_call-3.
|
||||||
|
|
||||||
|
Inlined by the compiler.
|
||||||
"""
|
"""
|
||||||
def call(port, operation, data) do
|
def call(port, operation, data) do
|
||||||
:erlang.port_call(port, operation, data)
|
:erlang.port_call(port, operation, data)
|
||||||
@@ -47,6 +59,8 @@ defmodule Port do
|
|||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
See http://www.erlang.org/doc/man/erlang.html#port_info-1.
|
See http://www.erlang.org/doc/man/erlang.html#port_info-1.
|
||||||
|
|
||||||
|
Inlined by the compiler.
|
||||||
"""
|
"""
|
||||||
def info(port) do
|
def info(port) do
|
||||||
:erlang.port_info(port)
|
:erlang.port_info(port)
|
||||||
@@ -54,6 +68,8 @@ defmodule Port do
|
|||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
See http://www.erlang.org/doc/man/erlang.html#port_info-2.
|
See http://www.erlang.org/doc/man/erlang.html#port_info-2.
|
||||||
|
|
||||||
|
Inlined by the compiler.
|
||||||
"""
|
"""
|
||||||
def info(port, item) do
|
def info(port, item) do
|
||||||
:erlang.port_info(port, item)
|
:erlang.port_info(port, item)
|
||||||
@@ -61,6 +77,8 @@ defmodule Port do
|
|||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
See http://www.erlang.org/doc/man/erlang.html#ports-0.
|
See http://www.erlang.org/doc/man/erlang.html#ports-0.
|
||||||
|
|
||||||
|
Inlined by the compiler.
|
||||||
"""
|
"""
|
||||||
def list do
|
def list do
|
||||||
:erlang.ports
|
:erlang.ports
|
||||||
|
|||||||
+23
-23
@@ -34,7 +34,7 @@ defmodule Process do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns the value for the given key.
|
Returns the value for the given `key`.
|
||||||
"""
|
"""
|
||||||
@spec get(term) :: term
|
@spec get(term) :: term
|
||||||
@spec get(term, default :: term) :: term
|
@spec get(term, default :: term) :: term
|
||||||
@@ -64,7 +64,7 @@ defmodule Process do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Deletes the given key from the dictionary.
|
Deletes the given `key` from the dictionary.
|
||||||
"""
|
"""
|
||||||
@spec delete(term) :: term | nil
|
@spec delete(term) :: term | nil
|
||||||
def delete(key) do
|
def delete(key) do
|
||||||
@@ -79,11 +79,11 @@ defmodule Process do
|
|||||||
1. If pid is not trapping exits, pid will exit with the given reason.
|
1. If pid is not trapping exits, pid will exit with the given reason.
|
||||||
|
|
||||||
2. If pid is trapping exits, the exit signal is transformed into a message
|
2. If pid is trapping exits, the exit signal is transformed into a message
|
||||||
{:EXIT, from, reason} and delivered to the message queue of pid.
|
`{:EXIT, from, reason}` and delivered to the message queue of pid.
|
||||||
|
|
||||||
3. If reason is the atom `:normal`, pid will not exit. If it is trapping
|
3. If reason is the atom `:normal`, pid will not exit. If it is trapping
|
||||||
exits, the exit signal is transformed into a message {:EXIT, from,
|
exits, the exit signal is transformed into a message `{:EXIT, from,
|
||||||
:normal} and delivered to its message queue.
|
:normal}` and delivered to its message queue.
|
||||||
|
|
||||||
4. If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called,
|
4. If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called,
|
||||||
an untrappable exit signal is sent to pid which will unconditionally
|
an untrappable exit signal is sent to pid which will unconditionally
|
||||||
@@ -129,10 +129,10 @@ defmodule Process do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Sends `msg` to `dest` after `time` millisecons.
|
Sends `msg` to `dest` after `time` milliseconds.
|
||||||
|
|
||||||
If `dest` is a pid, it has to be a pid of a local process, dead or alive.
|
If `dest` is a pid, it must be the pid of a local process, dead or alive.
|
||||||
If `dest` is an atom, it is supposed to be the name of a registered process
|
If `dest` is an atom, it must be the name of a registered process
|
||||||
which is looked up at the time of delivery. No error is given if the name does
|
which is looked up at the time of delivery. No error is given if the name does
|
||||||
not refer to a process.
|
not refer to a process.
|
||||||
|
|
||||||
@@ -208,8 +208,8 @@ defmodule Process do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
If monitor_ref is a reference which the calling process
|
If `monitor_ref` is a reference which the calling process
|
||||||
obtained by calling monitor/1, this monitoring is turned off.
|
obtained by calling `monitor/1`, this monitoring is turned off.
|
||||||
If the monitoring is already turned off, nothing happens.
|
If the monitoring is already turned off, nothing happens.
|
||||||
|
|
||||||
See http://www.erlang.org/doc/man/erlang.html#demonitor-2 for more info.
|
See http://www.erlang.org/doc/man/erlang.html#demonitor-2 for more info.
|
||||||
@@ -227,7 +227,7 @@ defmodule Process do
|
|||||||
processes currently existing on the local node.
|
processes currently existing on the local node.
|
||||||
|
|
||||||
Note that a process that is exiting, exists but is not alive, i.e.,
|
Note that a process that is exiting, exists but is not alive, i.e.,
|
||||||
alive?/1 will return false for a process that is exiting,
|
`alive?/1` will return `false` for a process that is exiting,
|
||||||
but its process identifier will be part of the result returned.
|
but its process identifier will be part of the result returned.
|
||||||
|
|
||||||
See http://www.erlang.org/doc/man/erlang.html#processes-0 for more info.
|
See http://www.erlang.org/doc/man/erlang.html#processes-0 for more info.
|
||||||
@@ -252,7 +252,7 @@ defmodule Process do
|
|||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Removes the link, if there is one, between the calling process and
|
Removes the link, if there is one, between the calling process and
|
||||||
the process or port referred to by `pid`. Returns true and does not
|
the process or port referred to by `pid`. Returns `true` and does not
|
||||||
fail, even if there is no link or `id` does not exist
|
fail, even if there is no link or `id` does not exist
|
||||||
|
|
||||||
See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info.
|
See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info.
|
||||||
@@ -265,13 +265,13 @@ defmodule Process do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Associates the name with a pid or a port identifier. name, which must
|
Associates the name with a pid or a port identifier. `name`, which must
|
||||||
be an atom, can be used instead of the pid / port identifier with the
|
be an atom, can be used instead of the pid / port identifier with the
|
||||||
`Kernel.send/2` function.
|
`Kernel.send/2` function.
|
||||||
|
|
||||||
`Process.register/2` will fail with `ArgumentError` if the pid supplied
|
`Process.register/2` will fail with `ArgumentError` if the pid supplied
|
||||||
is no longer alive, (check with `alive?/1`) or if the name is
|
is no longer alive, (check with `alive?/1`) or if the name is
|
||||||
already registered (check with `registered?/1`).
|
already registered (check with `whereis/1`).
|
||||||
"""
|
"""
|
||||||
@spec register(pid | port, atom) :: true
|
@spec register(pid | port, atom) :: true
|
||||||
def register(pid, name) when not name in [nil, false, true] do
|
def register(pid, name) when not name in [nil, false, true] do
|
||||||
@@ -290,7 +290,7 @@ defmodule Process do
|
|||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns the pid or port identifier with the registered name.
|
Returns the pid or port identifier with the registered name.
|
||||||
Returns nil if the name is not registered.
|
Returns `nil` if the name is not registered.
|
||||||
|
|
||||||
See http://www.erlang.org/doc/man/erlang.html#whereis-1 for more info.
|
See http://www.erlang.org/doc/man/erlang.html#whereis-1 for more info.
|
||||||
"""
|
"""
|
||||||
@@ -309,7 +309,7 @@ defmodule Process do
|
|||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Sets the group leader of `pid` to `leader`. Typically, this is used when a processes
|
Sets the group leader of `pid` to `leader`. Typically, this is used when a processes
|
||||||
started from a certain shell should have another group leader than `:init`.
|
started from a certain shell should have a group leader other than `:init`.
|
||||||
"""
|
"""
|
||||||
@spec group_leader(pid, leader :: pid) :: true
|
@spec group_leader(pid, leader :: pid) :: true
|
||||||
def group_leader(pid, leader) do
|
def group_leader(pid, leader) do
|
||||||
@@ -317,7 +317,7 @@ defmodule Process do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns a list of names which have been registered using register/2.
|
Returns a list of names which have been registered using `register/2`.
|
||||||
"""
|
"""
|
||||||
@spec registered :: [atom]
|
@spec registered :: [atom]
|
||||||
def registered do
|
def registered do
|
||||||
@@ -339,9 +339,9 @@ defmodule Process do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Sets certain flags for the process Pid, in the same manner as flag/2.
|
Sets certain flags for the process `pid`, in the same manner as `flag/2`.
|
||||||
Returns the old value of the flag. The allowed values for Flag are
|
Returns the old value of the flag. The allowed values for `flag` are
|
||||||
only a subset of those allowed in flag/2, namely: save_calls.
|
only a subset of those allowed in `flag/2`, namely: `save_calls`.
|
||||||
|
|
||||||
See http://www.erlang.org/doc/man/erlang.html#process_flag-3 for more info.
|
See http://www.erlang.org/doc/man/erlang.html#process_flag-3 for more info.
|
||||||
"""
|
"""
|
||||||
@@ -351,7 +351,7 @@ defmodule Process do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns information about the process identified by pid or nil if the process
|
Returns information about the process identified by `pid` or `nil` if the process
|
||||||
is not alive.
|
is not alive.
|
||||||
Use this only for debugging information.
|
Use this only for debugging information.
|
||||||
|
|
||||||
@@ -363,8 +363,8 @@ defmodule Process do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns information about the process identified by pid
|
Returns information about the process identified by `pid`
|
||||||
or nil if the process is not alive.
|
or `nil` if the process is not alive.
|
||||||
|
|
||||||
See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info.
|
See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info.
|
||||||
"""
|
"""
|
||||||
|
|||||||
+33
-14
@@ -426,7 +426,7 @@ defmodule Protocol do
|
|||||||
|
|
||||||
defp after_defprotocol do
|
defp after_defprotocol do
|
||||||
quote bind_quoted: [builtin: builtin] do
|
quote bind_quoted: [builtin: builtin] do
|
||||||
@spec impl_for(term) :: module | nil
|
@spec impl_for(term) :: atom() | nil
|
||||||
Kernel.def impl_for(data)
|
Kernel.def impl_for(data)
|
||||||
|
|
||||||
# Define the implementation for structs.
|
# Define the implementation for structs.
|
||||||
@@ -449,7 +449,7 @@ defmodule Protocol do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec impl_for!(term) :: module | no_return
|
@spec impl_for!(term) :: atom() | no_return()
|
||||||
Kernel.def impl_for!(data) do
|
Kernel.def impl_for!(data) do
|
||||||
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
|
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
|
||||||
end
|
end
|
||||||
@@ -494,12 +494,19 @@ defmodule Protocol do
|
|||||||
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
|
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
@spec __protocol__(atom) :: term
|
@spec __protocol__(:name) :: __MODULE__
|
||||||
Kernel.def __protocol__(:name), do: __MODULE__
|
@spec __protocol__(:functions) :: unquote(Protocol.__functions_spec__(@functions))
|
||||||
|
Kernel.def __protocol__(:name), do: __MODULE__
|
||||||
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
|
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def __functions_spec__([]),
|
||||||
|
do: []
|
||||||
|
def __functions_spec__([h|t]),
|
||||||
|
do: [:lists.foldl(&{:|, [], [&1, &2]}, h, t), quote(do: ...)]
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def __impl__(protocol, opts) do
|
def __impl__(protocol, opts) do
|
||||||
do_defimpl(protocol, :lists.keysort(1, opts))
|
do_defimpl(protocol, :lists.keysort(1, opts))
|
||||||
@@ -510,6 +517,20 @@ defmodule Protocol do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp do_defimpl(protocol, [do: block, for: for]) do
|
defp do_defimpl(protocol, [do: block, for: for]) do
|
||||||
|
# Unquote the implementation just later
|
||||||
|
# when all variables will already be injected
|
||||||
|
# into the module body.
|
||||||
|
__impl__ =
|
||||||
|
quote unquote: false do
|
||||||
|
@doc false
|
||||||
|
@spec __impl__(:for) :: unquote(for)
|
||||||
|
@spec __impl__(:target) :: __MODULE__
|
||||||
|
@spec __impl__(:protocol) :: unquote(protocol)
|
||||||
|
def __impl__(:for), do: unquote(for)
|
||||||
|
def __impl__(:target), do: __MODULE__
|
||||||
|
def __impl__(:protocol), do: unquote(protocol)
|
||||||
|
end
|
||||||
|
|
||||||
quote do
|
quote do
|
||||||
protocol = unquote(protocol)
|
protocol = unquote(protocol)
|
||||||
for = unquote(for)
|
for = unquote(for)
|
||||||
@@ -527,11 +548,7 @@ defmodule Protocol do
|
|||||||
Module.register_attribute(__MODULE__, :impl, persist: true)
|
Module.register_attribute(__MODULE__, :impl, persist: true)
|
||||||
@impl [protocol: @protocol, for: @for]
|
@impl [protocol: @protocol, for: @for]
|
||||||
|
|
||||||
@doc false
|
unquote(__impl__)
|
||||||
@spec __impl__(atom) :: term
|
|
||||||
def __impl__(:target), do: __MODULE__
|
|
||||||
def __impl__(:protocol), do: @protocol
|
|
||||||
def __impl__(:for), do: @for
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -576,7 +593,9 @@ defmodule Protocol do
|
|||||||
@impl [protocol: unquote(protocol), for: unquote(for)]
|
@impl [protocol: unquote(protocol), for: unquote(for)]
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
@spec __impl__(atom) :: term
|
@spec __impl__(:target) :: unquote(impl)
|
||||||
|
@spec __impl__(:protocol) :: unquote(protocol)
|
||||||
|
@spec __impl__(:for) :: unquote(for)
|
||||||
def __impl__(:target), do: unquote(impl)
|
def __impl__(:target), do: unquote(impl)
|
||||||
def __impl__(:protocol), do: unquote(protocol)
|
def __impl__(:protocol), do: unquote(protocol)
|
||||||
def __impl__(:for), do: unquote(for)
|
def __impl__(:for), do: unquote(for)
|
||||||
@@ -592,10 +611,10 @@ defmodule Protocol do
|
|||||||
|
|
||||||
found =
|
found =
|
||||||
for {:spec, expr, caller} <- specs,
|
for {:spec, expr, caller} <- specs,
|
||||||
Kernel.Typespec.spec_to_signature(expr) == signature do
|
Kernel.Typespec.spec_to_signature(expr) == signature do
|
||||||
Kernel.Typespec.define_spec(:callback, expr, caller)
|
Kernel.Typespec.define_spec(:callback, expr, caller)
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
found != []
|
found != []
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ defmodule Range do
|
|||||||
@moduledoc """
|
@moduledoc """
|
||||||
Defines a Range.
|
Defines a Range.
|
||||||
|
|
||||||
A Range are represented internally as a struct. However,
|
A Range is represented internally as a struct. However,
|
||||||
the most common form of creating and matching on ranges
|
the most common form of creating and matching on ranges
|
||||||
is via the `../2` macro, auto-imported from Kernel:
|
is via the `../2` macro, auto-imported from Kernel:
|
||||||
|
|
||||||
@@ -18,7 +18,8 @@ defmodule Range do
|
|||||||
|
|
||||||
defstruct first: nil, last: nil
|
defstruct first: nil, last: nil
|
||||||
|
|
||||||
@type t(first, last) :: %{__struct__: Range, first: first, last: last}
|
@type t :: %Range{}
|
||||||
|
@type t(first, last) :: %Range{first: first, last: last}
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Creates a new range.
|
Creates a new range.
|
||||||
|
|||||||
@@ -20,6 +20,20 @@ defmodule Record do
|
|||||||
The macros `defrecord/3` and `defrecordp/3` can be used to create
|
The macros `defrecord/3` and `defrecordp/3` can be used to create
|
||||||
records while `extract/2` can be used to extract records from Erlang
|
records while `extract/2` can be used to extract records from Erlang
|
||||||
files.
|
files.
|
||||||
|
|
||||||
|
## Types
|
||||||
|
|
||||||
|
Types can be defined for tuples with the `record/2` macro (only available
|
||||||
|
in typespecs). Like with the generated record macros it will expand to
|
||||||
|
a tuple.
|
||||||
|
|
||||||
|
defmodule MyModule do
|
||||||
|
require Record
|
||||||
|
Record.defrecord :user name: "José", age: 25
|
||||||
|
|
||||||
|
@type user :: record(:user, name: String.t, age: integer)
|
||||||
|
# expands to: `@type user :: {:user, String.t, integer}`
|
||||||
|
end
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@@ -111,6 +125,7 @@ defmodule Record do
|
|||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
defmodule User do
|
defmodule User do
|
||||||
|
require Record
|
||||||
Record.defrecord :user, [name: "José", age: "25"]
|
Record.defrecord :user, [name: "José", age: "25"]
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -118,8 +133,8 @@ defmodule Record do
|
|||||||
arities will be defined to manipulate the underlying record:
|
arities will be defined to manipulate the underlying record:
|
||||||
|
|
||||||
# To create records
|
# To create records
|
||||||
user() #=> {:user, "José", 25}
|
record = user() #=> {:user, "José", 25}
|
||||||
user(age: 26) #=> {:user, "José", 26}
|
record = user(age: 26) #=> {:user, "José", 26}
|
||||||
|
|
||||||
# To get a field from the record
|
# To get a field from the record
|
||||||
user(record, :name) #=> "José"
|
user(record, :name) #=> "José"
|
||||||
@@ -127,10 +142,14 @@ defmodule Record do
|
|||||||
# To update the record
|
# To update the record
|
||||||
user(record, age: 26) #=> {:user, "José", 26}
|
user(record, age: 26) #=> {:user, "José", 26}
|
||||||
|
|
||||||
|
# Convert a record to a keyword list
|
||||||
|
user(record) #=> [name: "José", age: 26]
|
||||||
|
|
||||||
By default, Elixir uses the record name as the first element of
|
By default, Elixir uses the record name as the first element of
|
||||||
the tuple (the tag). But it can be changed to something else:
|
the tuple (the tag). But it can be changed to something else:
|
||||||
|
|
||||||
defmodule User do
|
defmodule User do
|
||||||
|
require Record
|
||||||
Record.defrecord :user, User, name: nil
|
Record.defrecord :user, User, name: nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -190,8 +209,15 @@ defmodule Record do
|
|||||||
Keyword.keyword?(args) ->
|
Keyword.keyword?(args) ->
|
||||||
create(atom, fields, args, caller)
|
create(atom, fields, args, caller)
|
||||||
true ->
|
true ->
|
||||||
msg = "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
|
case Macro.expand(args, caller) do
|
||||||
raise ArgumentError, msg
|
{:{}, _, [^atom|list]} when length(list) == length(fields) ->
|
||||||
|
record = List.to_tuple([atom|list])
|
||||||
|
Macro.escape(Record.__keyword__(atom, fields, record))
|
||||||
|
{^atom, arg} when length(fields) == 1 ->
|
||||||
|
Macro.escape(Record.__keyword__(atom, fields, {atom, arg}))
|
||||||
|
_ ->
|
||||||
|
quote do: Record.__keyword__(unquote(atom), unquote(fields), unquote(args))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -279,4 +305,21 @@ defmodule Record do
|
|||||||
defp find_index([{k, _}|_], k, i), do: i + 2
|
defp find_index([{k, _}|_], k, i), do: i + 2
|
||||||
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
|
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
|
||||||
defp find_index([], _k, _i), do: nil
|
defp find_index([], _k, _i), do: nil
|
||||||
|
|
||||||
|
# Returns a keyword list of the record
|
||||||
|
@doc false
|
||||||
|
def __keyword__(atom, fields, record) do
|
||||||
|
if record?(record, atom) do
|
||||||
|
[_tag|values] = Tuple.to_list(record)
|
||||||
|
join_keyword(fields, values, [])
|
||||||
|
else
|
||||||
|
msg = "expected argument to be a literal atom, literal keyword or a #{inspect atom} record, got runtime: #{inspect record}"
|
||||||
|
raise ArgumentError, msg
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp join_keyword([{field, _default}|fields], [value|values], acc),
|
||||||
|
do: join_keyword(fields, values, [{field, value}| acc])
|
||||||
|
defp join_keyword([], [], acc),
|
||||||
|
do: :lists.reverse(acc)
|
||||||
end
|
end
|
||||||
|
|||||||
+19
-21
@@ -22,8 +22,9 @@ defmodule Regex do
|
|||||||
|
|
||||||
The modifiers available when creating a Regex are:
|
The modifiers available when creating a Regex are:
|
||||||
|
|
||||||
* `unicode` (u) - enables unicode specific patterns like `\p`; it expects
|
* `unicode` (u) - enables unicode specific patterns like `\p` and changes
|
||||||
valid unicode strings to be given on match
|
modifiers like `\w`, `\W`, `\s` and friends to also match on unicode.
|
||||||
|
It expects valid unicode strings to be given on match
|
||||||
|
|
||||||
* `caseless` (i) - add case insensitivity
|
* `caseless` (i) - add case insensitivity
|
||||||
|
|
||||||
@@ -75,7 +76,9 @@ defmodule Regex do
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
defstruct re_pattern: nil :: term, source: "" :: binary, opts: "" :: binary
|
defstruct re_pattern: nil, source: "", opts: ""
|
||||||
|
|
||||||
|
@opaque t :: %__MODULE__{re_pattern: term, source: binary, opts: binary}
|
||||||
|
|
||||||
defmodule CompileError do
|
defmodule CompileError do
|
||||||
defexception message: "regex could not be compiled"
|
defexception message: "regex could not be compiled"
|
||||||
@@ -104,7 +107,7 @@ defmodule Regex do
|
|||||||
def compile(source, options \\ "")
|
def compile(source, options \\ "")
|
||||||
|
|
||||||
def compile(source, options) when is_binary(options) do
|
def compile(source, options) when is_binary(options) do
|
||||||
case translate_options(options) do
|
case translate_options(options, []) do
|
||||||
{:error, rest} ->
|
{:error, rest} ->
|
||||||
{:error, {:invalid_option, rest}}
|
{:error, {:invalid_option, rest}}
|
||||||
|
|
||||||
@@ -471,12 +474,12 @@ defmodule Regex do
|
|||||||
|
|
||||||
defp apply_list(whole, string, pos, replacement, [[{mpos, _} | _] | _] = list) when mpos > pos do
|
defp apply_list(whole, string, pos, replacement, [[{mpos, _} | _] | _] = list) when mpos > pos do
|
||||||
length = mpos - pos
|
length = mpos - pos
|
||||||
<<untouched :: [size(length), binary], rest :: binary>> = string
|
<<untouched :: binary-size(length), rest :: binary>> = string
|
||||||
[untouched | apply_list(whole, rest, mpos, replacement, list)]
|
[untouched | apply_list(whole, rest, mpos, replacement, list)]
|
||||||
end
|
end
|
||||||
|
|
||||||
defp apply_list(whole, string, pos, replacement, [[{mpos, length} | _] = head | tail]) when mpos == pos do
|
defp apply_list(whole, string, pos, replacement, [[{mpos, length} | _] = head | tail]) when mpos == pos do
|
||||||
<<_ :: [size(length), binary], rest :: binary>> = string
|
<<_ :: size(length)-binary, rest :: binary>> = string
|
||||||
new_data = apply_replace(whole, replacement, head)
|
new_data = apply_replace(whole, replacement, head)
|
||||||
[new_data | apply_list(whole, rest, pos + length, replacement, tail)]
|
[new_data | apply_list(whole, rest, pos + length, replacement, tail)]
|
||||||
end
|
end
|
||||||
@@ -509,7 +512,7 @@ defmodule Regex do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp get_index(string, {pos, len}) do
|
defp get_index(string, {pos, len}) do
|
||||||
<<_ :: [size(pos), binary], res :: [size(len), binary], _ :: binary>> = string
|
<<_ :: size(pos)-binary, res :: size(len)-binary, _ :: binary>> = string
|
||||||
res
|
res
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -559,18 +562,13 @@ defmodule Regex do
|
|||||||
|
|
||||||
# Private Helpers
|
# Private Helpers
|
||||||
|
|
||||||
defp translate_options(<<?g, t :: binary>>) do
|
defp translate_options(<<?u, t :: binary>>, acc), do: translate_options(t, [:unicode, :ucp|acc])
|
||||||
IO.write :stderr, "The /g flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
|
defp translate_options(<<?i, t :: binary>>, acc), do: translate_options(t, [:caseless|acc])
|
||||||
translate_options(t)
|
defp translate_options(<<?x, t :: binary>>, acc), do: translate_options(t, [:extended|acc])
|
||||||
end
|
defp translate_options(<<?f, t :: binary>>, acc), do: translate_options(t, [:firstline|acc])
|
||||||
|
defp translate_options(<<?r, t :: binary>>, acc), do: translate_options(t, [:ungreedy|acc])
|
||||||
defp translate_options(<<?u, t :: binary>>), do: [:unicode|translate_options(t)]
|
defp translate_options(<<?s, t :: binary>>, acc), do: translate_options(t, [:dotall, {:newline, :anycrlf}|acc])
|
||||||
defp translate_options(<<?i, t :: binary>>), do: [:caseless|translate_options(t)]
|
defp translate_options(<<?m, t :: binary>>, acc), do: translate_options(t, [:multiline|acc])
|
||||||
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)]
|
defp translate_options(<<>>, acc), do: acc
|
||||||
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)]
|
defp translate_options(rest, _acc), do: {:error, rest}
|
||||||
defp translate_options(<<?r, t :: binary>>), do: [:ungreedy|translate_options(t)]
|
|
||||||
defp translate_options(<<?s, t :: binary>>), do: [:dotall, {:newline, :anycrlf}|translate_options(t)]
|
|
||||||
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
|
|
||||||
defp translate_options(<<>>), do: []
|
|
||||||
defp translate_options(rest), do: {:error, rest}
|
|
||||||
end
|
end
|
||||||
|
|||||||
+23
-17
@@ -91,10 +91,11 @@ defmodule Stream do
|
|||||||
|
|
||||||
defstruct enum: nil, funs: [], accs: [], done: nil
|
defstruct enum: nil, funs: [], accs: [], done: nil
|
||||||
|
|
||||||
@type acc :: any
|
@type acc :: any
|
||||||
@type element :: any
|
@type element :: any
|
||||||
@type index :: non_neg_integer
|
@type index :: non_neg_integer
|
||||||
@type default :: any
|
@type default :: any
|
||||||
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
# Require Stream.Reducers and its callbacks
|
# Require Stream.Reducers and its callbacks
|
||||||
require Stream.Reducers, as: R
|
require Stream.Reducers, as: R
|
||||||
@@ -427,7 +428,7 @@ defmodule Stream do
|
|||||||
"""
|
"""
|
||||||
@spec run(Enumerable.t) :: :ok
|
@spec run(Enumerable.t) :: :ok
|
||||||
def run(stream) do
|
def run(stream) do
|
||||||
Enumerable.reduce(stream, {:cont, nil}, fn(_, _) -> {:cont, nil} end)
|
_ = Enumerable.reduce(stream, {:cont, nil}, fn(_, _) -> {:cont, nil} end)
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -616,12 +617,14 @@ defmodule Stream do
|
|||||||
{[], user_acc} ->
|
{[], user_acc} ->
|
||||||
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
|
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
|
||||||
{list, user_acc} when is_list(list) ->
|
{list, user_acc} when is_list(list) ->
|
||||||
do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.List.reduce(list, &1, fun))
|
do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner,
|
||||||
|
&Enumerable.List.reduce(list, &1, fun))
|
||||||
{:halt, _user_acc} ->
|
{:halt, _user_acc} ->
|
||||||
next.({:halt, next_acc})
|
next.({:halt, next_acc})
|
||||||
{:halted, elem(inner_acc, 1)}
|
{:halted, elem(inner_acc, 1)}
|
||||||
{other, user_acc} ->
|
{other, user_acc} ->
|
||||||
do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.reduce(other, &1, inner))
|
do_enum_transform(user_acc, user, fun, next_acc, next, inner_acc, inner,
|
||||||
|
&Enumerable.reduce(other, &1, inner))
|
||||||
end
|
end
|
||||||
{reason, _} ->
|
{reason, _} ->
|
||||||
{reason, elem(inner_acc, 1)}
|
{reason, elem(inner_acc, 1)}
|
||||||
@@ -633,8 +636,9 @@ defmodule Stream do
|
|||||||
reduce.(inner_acc)
|
reduce.(inner_acc)
|
||||||
catch
|
catch
|
||||||
kind, reason ->
|
kind, reason ->
|
||||||
|
stacktrace = System.stacktrace
|
||||||
next.({:halt, next_acc})
|
next.({:halt, next_acc})
|
||||||
:erlang.raise(kind, reason, :erlang.get_stacktrace)
|
:erlang.raise(kind, reason, stacktrace)
|
||||||
else
|
else
|
||||||
{:done, acc} ->
|
{:done, acc} ->
|
||||||
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
|
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
|
||||||
@@ -646,29 +650,31 @@ defmodule Stream do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, reduce) do
|
defp do_enum_transform(user_acc, user, fun, next_acc, next, {op, inner_acc}, inner, reduce) do
|
||||||
try do
|
try do
|
||||||
reduce.(inner_acc)
|
reduce.({op, [:outer|inner_acc]})
|
||||||
catch
|
catch
|
||||||
{:stream_transform, h} ->
|
|
||||||
next.({:halt, next_acc})
|
|
||||||
{:halted, h}
|
|
||||||
kind, reason ->
|
kind, reason ->
|
||||||
stacktrace = System.stacktrace
|
stacktrace = System.stacktrace
|
||||||
next.({:halt, next_acc})
|
next.({:halt, next_acc})
|
||||||
:erlang.raise(kind, reason, stacktrace)
|
:erlang.raise(kind, reason, stacktrace)
|
||||||
else
|
else
|
||||||
{_, acc} ->
|
{:halted, [:outer|acc]} ->
|
||||||
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
|
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
|
||||||
{:suspended, acc, c} ->
|
{:halted, [:inner|acc]} ->
|
||||||
{:suspended, acc, &do_other_transform(user_acc, user, fun, next_acc, next, &1, inner, c)}
|
next.({:halt, next_acc})
|
||||||
|
{:halted, acc}
|
||||||
|
{:done, [_|acc]} ->
|
||||||
|
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
|
||||||
|
{:suspended, [_|acc], c} ->
|
||||||
|
{:suspended, acc, &do_enum_transform(user_acc, user, fun, next_acc, next, &1, inner, c)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_transform_each(x, acc, f) do
|
defp do_transform_each(x, [:outer|acc], f) do
|
||||||
case f.(x, acc) do
|
case f.(x, acc) do
|
||||||
{:halt, h} -> throw({:stream_transform, h})
|
{:halt, res} -> {:halt, [:inner|res]}
|
||||||
{_, _} = o -> o
|
{op, res} -> {op, [:outer|res]}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -231,7 +231,14 @@ defmodule String do
|
|||||||
|
|
||||||
def split("", _pattern, _options), do: [""]
|
def split("", _pattern, _options), do: [""]
|
||||||
|
|
||||||
def split(binary, "", options), do: split(binary, ~r""u, options)
|
def split(binary, "", options) do
|
||||||
|
index =
|
||||||
|
case Keyword.get(options, :parts, :infinity) do
|
||||||
|
num when is_number(num) and num > 0 -> num
|
||||||
|
_ -> 0
|
||||||
|
end
|
||||||
|
split_codepoints(binary, index - 1, Keyword.get(options, :trim, false))
|
||||||
|
end
|
||||||
|
|
||||||
def split(binary, pattern, options) do
|
def split(binary, pattern, options) do
|
||||||
if Regex.regex?(pattern) do
|
if Regex.regex?(pattern) do
|
||||||
@@ -253,6 +260,14 @@ defmodule String do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp split_codepoints(binary, 0, _trim), do: [binary]
|
||||||
|
defp split_codepoints(<<h :: utf8, t :: binary>>, count, trim),
|
||||||
|
do: [<<h :: utf8>>|split_codepoints(t, count - 1, trim)]
|
||||||
|
defp split_codepoints(<<h, t :: binary>>, count, trim),
|
||||||
|
do: [<<h>>|split_codepoints(t, count - 1, trim)]
|
||||||
|
defp split_codepoints(<<>>, _, true), do: []
|
||||||
|
defp split_codepoints(<<>>, _, false), do: [""]
|
||||||
|
|
||||||
defp split_parts("", _pattern, _num), do: [""]
|
defp split_parts("", _pattern, _num), do: [""]
|
||||||
defp split_parts(binary, pattern, num), do: split_parts(binary, pattern, num, [])
|
defp split_parts(binary, pattern, num), do: split_parts(binary, pattern, num, [])
|
||||||
defp split_parts("", _pattern, _num, parts), do: Enum.reverse([""|parts])
|
defp split_parts("", _pattern, _num, parts), do: Enum.reverse([""|parts])
|
||||||
@@ -974,8 +989,13 @@ defmodule String do
|
|||||||
@doc """
|
@doc """
|
||||||
Returns a substring starting at the offset given by the first, and
|
Returns a substring starting at the offset given by the first, and
|
||||||
a length given by the second.
|
a length given by the second.
|
||||||
|
|
||||||
If the offset is greater than string length, then it returns `""`.
|
If the offset is greater than string length, then it returns `""`.
|
||||||
|
|
||||||
|
Remember this function works with unicode codepoints and consider
|
||||||
|
the slices to represent codepoints offsets. If you want to split
|
||||||
|
on raw bytes, check `Kernel.binary_part/3` instead.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> String.slice("elixir", 1, 3)
|
iex> String.slice("elixir", 1, 3)
|
||||||
@@ -1028,6 +1048,14 @@ defmodule String do
|
|||||||
If the start of the range is not a valid offset for the given
|
If the start of the range is not a valid offset for the given
|
||||||
string or if the range is in reverse order, returns `""`.
|
string or if the range is in reverse order, returns `""`.
|
||||||
|
|
||||||
|
If the start or end of the range are negative, the whole string
|
||||||
|
is traversed first in order to convert the negative indexes into
|
||||||
|
positive ones.
|
||||||
|
|
||||||
|
Remember this function works with unicode codepoints and consider
|
||||||
|
the slices to represent codepoints offsets. If you want to split
|
||||||
|
on raw bytes, check `Kernel.binary_part/3` instead.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> String.slice("elixir", 1..3)
|
iex> String.slice("elixir", 1..3)
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ defmodule StringIO do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp do_get_chars(input, :latin1, n) do
|
defp do_get_chars(input, :latin1, n) do
|
||||||
<<chars :: [binary, size(n)], rest :: binary>> = input
|
<<chars :: binary-size(n), rest :: binary>> = input
|
||||||
{chars, rest}
|
{chars, rest}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -209,7 +209,7 @@ defmodule StringIO do
|
|||||||
{buf_count, split_pos} when buf_count < n or split_pos == :none ->
|
{buf_count, split_pos} when buf_count < n or split_pos == :none ->
|
||||||
{input, ""}
|
{input, ""}
|
||||||
{_buf_count, split_pos} ->
|
{_buf_count, split_pos} ->
|
||||||
<<chars :: [binary, size(split_pos)], rest :: binary>> = input
|
<<chars :: binary-size(split_pos), rest :: binary>> = input
|
||||||
{chars, rest}
|
{chars, rest}
|
||||||
end
|
end
|
||||||
catch
|
catch
|
||||||
|
|||||||
+121
-19
@@ -202,7 +202,7 @@ defmodule System do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Register a program exit handler function.
|
Registers a program exit handler function.
|
||||||
|
|
||||||
Registers a function that will be invoked
|
Registers a function that will be invoked
|
||||||
at the end of program execution. Useful for
|
at the end of program execution. Useful for
|
||||||
@@ -215,19 +215,7 @@ defmodule System do
|
|||||||
:elixir_code_server.cast {:at_exit, fun}
|
:elixir_code_server.cast {:at_exit, fun}
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc false
|
||||||
Execute a system command.
|
|
||||||
|
|
||||||
Executes `command` in a command shell of the target OS,
|
|
||||||
captures the standard output of the command and returns
|
|
||||||
the result as a binary.
|
|
||||||
|
|
||||||
If `command` is a char list, a char list is returned.
|
|
||||||
Otherwise a string, correctly encoded in UTF-8, is expected.
|
|
||||||
"""
|
|
||||||
@spec cmd(String.t) :: String.t
|
|
||||||
@spec cmd(char_list) :: char_list
|
|
||||||
|
|
||||||
def cmd(command) when is_list(command) do
|
def cmd(command) when is_list(command) do
|
||||||
:os.cmd(command)
|
:os.cmd(command)
|
||||||
end
|
end
|
||||||
@@ -237,21 +225,18 @@ defmodule System do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Locate an executable on the system.
|
Locates an executable on the system.
|
||||||
|
|
||||||
This function looks up an executable program given
|
This function looks up an executable program given
|
||||||
its name using the environment variable PATH on Unix
|
its name using the environment variable PATH on Unix
|
||||||
and Windows. It also considers the proper executable
|
and Windows. It also considers the proper executable
|
||||||
extension for each OS, so for Windows it will try to
|
extension for each OS, so for Windows it will try to
|
||||||
lookup files with `.com`, `.cmd` or similar extensions.
|
lookup files with `.com`, `.cmd` or similar extensions.
|
||||||
|
|
||||||
If `program` is a char list, a char list is returned.
|
|
||||||
Returns a binary otherwise.
|
|
||||||
"""
|
"""
|
||||||
@spec find_executable(binary) :: binary | nil
|
@spec find_executable(binary) :: binary | nil
|
||||||
@spec find_executable(char_list) :: char_list | nil
|
|
||||||
|
|
||||||
def find_executable(program) when is_list(program) do
|
def find_executable(program) when is_list(program) do
|
||||||
|
IO.puts :stderr, "warning: passing a char_list to System.find_executable/1 is deprecated\n#{Exception.format_stacktrace}"
|
||||||
:os.find_executable(program) || nil
|
:os.find_executable(program) || nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -387,4 +372,121 @@ defmodule System do
|
|||||||
def halt(status) when is_binary(status) do
|
def halt(status) when is_binary(status) do
|
||||||
:erlang.halt(String.to_char_list(status))
|
:erlang.halt(String.to_char_list(status))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Executes the given `command` with `args`.
|
||||||
|
|
||||||
|
`command` is expected to be an executable available in PATH
|
||||||
|
unless an absolute path is given.
|
||||||
|
|
||||||
|
`args` must be a list of strings and they are not expanded
|
||||||
|
in any way. For example, this means wildcard expansion will
|
||||||
|
not happen unless `Path.wildcard/2` is used. On Windows though,
|
||||||
|
wildcard expansion is up to the program.
|
||||||
|
|
||||||
|
A set of options are also supported and described below.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
* `:into` - injects the result into the given collectable, defaults to `""`
|
||||||
|
* `:cd` - the directory to run the command on
|
||||||
|
* `:env` - an enumerable of tuples containing environment key-value as binary
|
||||||
|
* `:arg0` - set the command arg0
|
||||||
|
* `:stderr_to_stdout` - redirects stderr to stdout when true
|
||||||
|
* `:parallelism` - when true, the VM will schedule port tasks to improve
|
||||||
|
parallelism in the system. If set to false, the VM will try to perform
|
||||||
|
commands immediately, improving latency at the expense of parallelism.
|
||||||
|
The default can be set on system startup by passing the "+spp" argument
|
||||||
|
to `--erl`.
|
||||||
|
|
||||||
|
## Error reasons
|
||||||
|
|
||||||
|
If invalid arguments are given, `ArgumentError` is raised by
|
||||||
|
`System.cmd/3`. `System.cmd/3` also expect a strict set of
|
||||||
|
options and will raise if unknown or invalid options are given.
|
||||||
|
|
||||||
|
Furthermore, `System.cmd/3` may fail with one of the POSIX reasons
|
||||||
|
detailed below:
|
||||||
|
|
||||||
|
* `:system_limit` - all available ports in the Erlang emulator are in use
|
||||||
|
|
||||||
|
* `:enomem` - there was not enough memory to create the port
|
||||||
|
|
||||||
|
* `:eagain` - there are no more available operating system processes
|
||||||
|
|
||||||
|
* `:enametoolong` - the external command given was too long.
|
||||||
|
|
||||||
|
* `:emfile` - there are no more available file descriptors
|
||||||
|
(for the operating system process that the Erlang emulator runs in)
|
||||||
|
|
||||||
|
* `:enfile` - the file table is full (for the entire operating system)
|
||||||
|
|
||||||
|
* `:eacces` - the command does not point to an executable file
|
||||||
|
|
||||||
|
* `:enoent` - the command does not point to an existing file
|
||||||
|
|
||||||
|
## Shell commands
|
||||||
|
|
||||||
|
If you desire to execute a trusted command inside a shell, with pipes,
|
||||||
|
redirecting and so on, please check
|
||||||
|
[Erlang's :os.cmd/1 function](http://www.erlang.org/doc/man/os.html#cmd-1).
|
||||||
|
"""
|
||||||
|
@spec cmd(binary, [binary], Keyword.t) ::
|
||||||
|
{:ok, Collectable.t, exit_status :: non_neg_integer} | {:error, atom}
|
||||||
|
def cmd(command, args, opts \\ []) when is_binary(command) and is_list(args) do
|
||||||
|
command = String.to_char_list(command)
|
||||||
|
|
||||||
|
command =
|
||||||
|
if Path.type(command) == :absolute do
|
||||||
|
command
|
||||||
|
else
|
||||||
|
:os.find_executable(command) || command
|
||||||
|
end
|
||||||
|
|
||||||
|
{into, opts} = cmd_opts(opts, [:use_stdio, :exit_status, :binary, :hide, args: args], "")
|
||||||
|
{initial, fun} = Collectable.into(into)
|
||||||
|
do_cmd Port.open({:spawn_executable, command}, opts), initial, fun
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_cmd(port, acc, fun) do
|
||||||
|
receive do
|
||||||
|
{^port, {:data, data}} ->
|
||||||
|
do_cmd(port, fun.(acc, {:cont, data}), fun)
|
||||||
|
{^port, {:exit_status, status}} ->
|
||||||
|
{fun.(acc, :done), status}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp cmd_opts([{:into, any}|t], opts, _into),
|
||||||
|
do: cmd_opts(t, opts, any)
|
||||||
|
|
||||||
|
defp cmd_opts([{:cd, bin}|t], opts, into) when is_binary(bin),
|
||||||
|
do: cmd_opts(t, [{:cd, bin}|opts], into)
|
||||||
|
|
||||||
|
defp cmd_opts([{:arg0, bin}|t], opts, into) when is_binary(bin),
|
||||||
|
do: cmd_opts(t, [{:arg0, bin}|opts], into)
|
||||||
|
|
||||||
|
defp cmd_opts([{:stderr_to_stdout, bool}|t], opts, into) when is_boolean(bool),
|
||||||
|
do: cmd_opts(t, [:stderr_to_stdout|opts], into)
|
||||||
|
|
||||||
|
defp cmd_opts([{:parallelism, bool}|t], opts, into) when is_boolean(bool),
|
||||||
|
do: cmd_opts(t, [{:parallelism, bool}|opts], into)
|
||||||
|
|
||||||
|
defp cmd_opts([{:env, enum}|t], opts, into),
|
||||||
|
do: cmd_opts(t, [{:env, validate_env(enum)}|opts], into)
|
||||||
|
|
||||||
|
defp cmd_opts([{key, val}|_], _opts, _into),
|
||||||
|
do: raise(ArgumentError, "invalid option #{inspect key} with value #{inspect val}")
|
||||||
|
|
||||||
|
defp cmd_opts([], opts, into),
|
||||||
|
do: {into, opts}
|
||||||
|
|
||||||
|
defp validate_env(enum) do
|
||||||
|
Enum.map enum, fn
|
||||||
|
{k, v} ->
|
||||||
|
{String.to_char_list(k), String.to_char_list(v)}
|
||||||
|
other ->
|
||||||
|
raise ArgumentError, "invalid environment key-value #{inspect other}"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -93,6 +93,8 @@ defmodule Task do
|
|||||||
"""
|
"""
|
||||||
defstruct pid: nil, ref: nil
|
defstruct pid: nil, ref: nil
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Starts a task as part of a supervision tree.
|
Starts a task as part of a supervision tree.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -89,12 +89,12 @@ defmodule Task.Supervised do
|
|||||||
{fun, args} = get_running(mfa)
|
{fun, args} = get_running(mfa)
|
||||||
|
|
||||||
:error_logger.format(
|
:error_logger.format(
|
||||||
"** Task ~p terminating~n" <>
|
'** Task ~p terminating~n' ++
|
||||||
"** Started from ~p~n" <>
|
'** Started from ~p~n' ++
|
||||||
"** When function == ~p~n" <>
|
'** When function == ~p~n' ++
|
||||||
"** arguments == ~p~n" <>
|
'** arguments == ~p~n' ++
|
||||||
"** Reason for termination == ~n" <>
|
'** Reason for termination == ~n' ++
|
||||||
"** ~p~n", [self, get_from(info), fun, args, reason])
|
'** ~p~n', [self, get_from(info), fun, args, reason])
|
||||||
|
|
||||||
exit(reason)
|
exit(reason)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ defmodule URI do
|
|||||||
fragment: nil, authority: nil,
|
fragment: nil, authority: nil,
|
||||||
userinfo: nil, host: nil, port: nil
|
userinfo: nil, host: nil, port: nil
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
import Bitwise
|
import Bitwise
|
||||||
|
|
||||||
@ports %{
|
@ports %{
|
||||||
|
|||||||
@@ -58,14 +58,26 @@ defmodule Version do
|
|||||||
|
|
||||||
@type version :: String.t | t
|
@type version :: String.t | t
|
||||||
@type requirement :: String.t | Version.Requirement.t
|
@type requirement :: String.t | Version.Requirement.t
|
||||||
@type matchable :: {major :: String.t | non_neg_integer,
|
@type major :: String.t | non_neg_integer
|
||||||
minor :: non_neg_integer | nil,
|
@type minor :: non_neg_integer | nil
|
||||||
patch :: non_neg_integer | nil,
|
@type patch :: non_neg_integer | nil
|
||||||
pre :: [String.t]}
|
@type pre :: [String.t]
|
||||||
|
@type build :: String.t | nil
|
||||||
|
@type matchable :: {major :: major,
|
||||||
|
minor :: minor,
|
||||||
|
patch :: patch,
|
||||||
|
pre :: pre}
|
||||||
|
@type t :: %__MODULE__{
|
||||||
|
major: major,
|
||||||
|
minor: minor,
|
||||||
|
patch: patch,
|
||||||
|
pre: pre,
|
||||||
|
build: build}
|
||||||
|
|
||||||
defmodule Requirement do
|
defmodule Requirement do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
defstruct [:source, :matchspec]
|
defstruct [:source, :matchspec]
|
||||||
|
@type t :: %__MODULE__{}
|
||||||
end
|
end
|
||||||
|
|
||||||
defmodule InvalidRequirementError do
|
defmodule InvalidRequirementError do
|
||||||
@@ -157,7 +169,8 @@ defmodule Version do
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> Version.parse("2.0.1-alpha1") |> elem(1)
|
iex> {:ok, version} = Version.parse("2.0.1-alpha1")
|
||||||
|
iex> version
|
||||||
#Version<2.0.1-alpha1>
|
#Version<2.0.1-alpha1>
|
||||||
|
|
||||||
iex> Version.parse("2.0-alpha1")
|
iex> Version.parse("2.0-alpha1")
|
||||||
@@ -181,7 +194,8 @@ defmodule Version do
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> Version.parse_requirement("== 2.0.1") |> elem(1)
|
iex> {:ok, req} = Version.parse_requirement("== 2.0.1")
|
||||||
|
iex> req
|
||||||
#Version.Requirement<== 2.0.1>
|
#Version.Requirement<== 2.0.1>
|
||||||
|
|
||||||
iex> Version.parse_requirement("== == 2.0.1")
|
iex> Version.parse_requirement("== == 2.0.1")
|
||||||
|
|||||||
@@ -28,8 +28,10 @@ start(_Type, _Args) ->
|
|||||||
error -> [binary]
|
error -> [binary]
|
||||||
end,
|
end,
|
||||||
|
|
||||||
io:setopts(standard_io, Opts),
|
ok = io:setopts(standard_io, Opts),
|
||||||
io:setopts(standard_error, [{unicode,true}]),
|
%% Must use undocument {unicode, true} to set unicode on standard_error, more
|
||||||
|
%% info: http://erlang.org/pipermail/erlang-bugs/2014-April/004310.html
|
||||||
|
ok = io:setopts(standard_error, [{unicode,true}]),
|
||||||
case file:native_name_encoding() of
|
case file:native_name_encoding() of
|
||||||
latin1 ->
|
latin1 ->
|
||||||
io:format(standard_error,
|
io:format(standard_error,
|
||||||
@@ -51,13 +53,13 @@ config_change(_Changed, _New, _Remove) ->
|
|||||||
%% escript entry point
|
%% escript entry point
|
||||||
|
|
||||||
main(Args) ->
|
main(Args) ->
|
||||||
application:start(?MODULE),
|
ok = application:ensure_started(?MODULE),
|
||||||
'Elixir.Kernel.CLI':main(Args).
|
'Elixir.Kernel.CLI':main(Args).
|
||||||
|
|
||||||
%% Boot and process given options. Invoked by Elixir's script.
|
%% Boot and process given options. Invoked by Elixir's script.
|
||||||
|
|
||||||
start_cli() ->
|
start_cli() ->
|
||||||
application:start(?MODULE),
|
ok = application:ensure_started(?MODULE),
|
||||||
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
|
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
|
||||||
|
|
||||||
%% EVAL HOOKS
|
%% EVAL HOOKS
|
||||||
@@ -153,14 +155,19 @@ eval_forms(Tree, Binding, Env, Scope) ->
|
|||||||
{atom, _, Atom} ->
|
{atom, _, Atom} ->
|
||||||
{Atom, Binding, NewEnv, NewScope};
|
{Atom, Binding, NewEnv, NewScope};
|
||||||
_ ->
|
_ ->
|
||||||
{value, Value, NewBinding} = erl_eval(Erl, ParsedBinding),
|
{value, Value, NewBinding} = erl_eval(Erl, ParsedBinding, Env),
|
||||||
{Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope}
|
{Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
erl_eval(Erl, ParsedBinding) ->
|
erl_eval(Erl, ParsedBinding, E) ->
|
||||||
|
case erl_eval:check_command([Erl], ParsedBinding) of
|
||||||
|
ok -> ok;
|
||||||
|
{error, Desc} -> elixir_errors:handle_file_error(?m(E, file), Desc)
|
||||||
|
end,
|
||||||
|
|
||||||
% Below must be all one line for locations to be the same when the stacktrace
|
% Below must be all one line for locations to be the same when the stacktrace
|
||||||
% needs to be extended to the full stacktrace.
|
% needs to be extended to the full stacktrace.
|
||||||
try erl_eval:expr(Erl, ParsedBinding) catch Class:Exception -> erlang:raise(Class, Exception, get_stacktrace()) end.
|
try erl_eval:expr(Erl, ParsedBinding, none, none, none) catch Class:Exception -> erlang:raise(Class, Exception, get_stacktrace()) end.
|
||||||
|
|
||||||
get_stacktrace() ->
|
get_stacktrace() ->
|
||||||
Stacktrace = erlang:get_stacktrace(),
|
Stacktrace = erlang:get_stacktrace(),
|
||||||
|
|||||||
@@ -35,70 +35,106 @@ expand_bitstr(Fun, [H|T], Acc, E) ->
|
|||||||
|
|
||||||
%% Expand bit info
|
%% Expand bit info
|
||||||
|
|
||||||
expand_bit_info(Meta, Info, E) when is_list(Info) ->
|
|
||||||
expand_bit_info(Meta, Info, default, [], E);
|
|
||||||
|
|
||||||
expand_bit_info(Meta, Info, E) ->
|
expand_bit_info(Meta, Info, E) ->
|
||||||
expand_bit_info(Meta, [Info], E).
|
expand_bit_info(Meta, unpack_bit_info(Meta, Info, E), default, [], E).
|
||||||
|
|
||||||
expand_bit_info(Meta, [{Expr, ExprMeta, Args}|T], Size, Types, E) when is_atom(Expr) ->
|
expand_bit_info(Meta, [{size, _, [_]=Args}|T], Size, Types, E) ->
|
||||||
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
|
case Size of
|
||||||
case expand_bit_type_or_size(Expr, ListArgs) of
|
default ->
|
||||||
type ->
|
{[EArg], EE} = elixir_exp:expand_args(Args, E),
|
||||||
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
|
|
||||||
expand_bit_info(Meta, T, Size, [{Expr, [], EArgs}|Types], EE);
|
case EArg of
|
||||||
size ->
|
{Var, _, Context} when is_atom(Var) and is_atom(Context) ->
|
||||||
case Size of
|
ok;
|
||||||
default -> ok;
|
_ when is_integer(EArg) ->
|
||||||
_ -> elixir_errors:compile_error(Meta, ?m(E, file), "duplicated size definition in bitstring")
|
ok;
|
||||||
|
_ ->
|
||||||
|
elixir_errors:compile_error(Meta, ?m(E, file),
|
||||||
|
"size in bitstring expects an integer or a variable as argument, got: ~ts",
|
||||||
|
['Elixir.Macro':to_string(EArg)])
|
||||||
end,
|
end,
|
||||||
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
|
|
||||||
expand_bit_info(Meta, T, {Expr, [], EArgs}, Types, EE);
|
expand_bit_info(Meta, T, {size, [], [EArg]}, Types, EE);
|
||||||
none ->
|
_ ->
|
||||||
handle_unknown_bit_info(Meta, {Expr, ExprMeta, ListArgs}, T, Size, Types, E)
|
elixir_errors:compile_error(Meta, ?m(E, file),
|
||||||
|
"duplicated size definition in bitstring")
|
||||||
end;
|
end;
|
||||||
|
|
||||||
expand_bit_info(Meta, [Int|T], Size, Types, E) when is_integer(Int) ->
|
expand_bit_info(Meta, [{Expr, ExprMeta, Args}|T], Size, Types, E) when is_atom(Expr) ->
|
||||||
expand_bit_info(Meta, [{size, [], [Int]}|T], Size, Types, E);
|
case expand_bit_type(Expr, Args) of
|
||||||
|
type ->
|
||||||
|
{EArgs, EE} = elixir_exp:expand_args(Args, E),
|
||||||
|
validate_bit_type_args(Meta, Expr, EArgs, EE),
|
||||||
|
expand_bit_info(Meta, T, Size, [{Expr, [], EArgs}|Types], EE);
|
||||||
|
none ->
|
||||||
|
handle_unknown_bit_info(Meta, {Expr, ExprMeta, Args}, T, Size, Types, E)
|
||||||
|
end;
|
||||||
|
|
||||||
expand_bit_info(Meta, [Expr|_], _Size, _Types, E) ->
|
expand_bit_info(Meta, [Expr|_], _Size, _Types, E) ->
|
||||||
elixir_errors:compile_error(Meta, ?m(E, file),
|
elixir_errors:compile_error(Meta, ?m(E, file),
|
||||||
"unknown bitstring specifier ~ts", ['Elixir.Kernel':inspect(Expr)]);
|
"unknown bitstring specifier ~ts", ['Elixir.Kernel':inspect(Expr)]);
|
||||||
|
|
||||||
expand_bit_info(_Meta, [], Size, Types, _) ->
|
expand_bit_info(Meta, [], Size, Types, _) ->
|
||||||
case Size of
|
[H|T] = case Size of
|
||||||
default -> lists:reverse(Types);
|
default -> lists:reverse(Types);
|
||||||
_ -> [Size|lists:reverse(Types)]
|
_ -> lists:reverse(Types, [Size])
|
||||||
end.
|
end,
|
||||||
|
lists:foldl(fun(I, Acc) -> {'-', Meta, [Acc, I]} end, H, T).
|
||||||
|
|
||||||
expand_bit_type_or_size(binary, []) -> type;
|
expand_bit_type(binary, []) -> type;
|
||||||
expand_bit_type_or_size(integer, []) -> type;
|
expand_bit_type(integer, []) -> type;
|
||||||
expand_bit_type_or_size(float, []) -> type;
|
expand_bit_type(float, []) -> type;
|
||||||
expand_bit_type_or_size(bitstring, []) -> type;
|
expand_bit_type(bitstring, []) -> type;
|
||||||
expand_bit_type_or_size(bytes, []) -> type;
|
expand_bit_type(bytes, []) -> type;
|
||||||
expand_bit_type_or_size(bits, []) -> type;
|
expand_bit_type(bits, []) -> type;
|
||||||
expand_bit_type_or_size(utf8, []) -> type;
|
expand_bit_type(utf8, []) -> type;
|
||||||
expand_bit_type_or_size(utf16, []) -> type;
|
expand_bit_type(utf16, []) -> type;
|
||||||
expand_bit_type_or_size(utf32, []) -> type;
|
expand_bit_type(utf32, []) -> type;
|
||||||
expand_bit_type_or_size(signed, []) -> type;
|
expand_bit_type(signed, []) -> type;
|
||||||
expand_bit_type_or_size(unsigned, []) -> type;
|
expand_bit_type(unsigned, []) -> type;
|
||||||
expand_bit_type_or_size(big, []) -> type;
|
expand_bit_type(big, []) -> type;
|
||||||
expand_bit_type_or_size(little, []) -> type;
|
expand_bit_type(little, []) -> type;
|
||||||
expand_bit_type_or_size(native, []) -> type;
|
expand_bit_type(native, []) -> type;
|
||||||
expand_bit_type_or_size(unit, [_]) -> type;
|
expand_bit_type(unit, [_]) -> type;
|
||||||
expand_bit_type_or_size(size, [_]) -> size;
|
expand_bit_type(_, _) -> none.
|
||||||
expand_bit_type_or_size(_, _) -> none.
|
|
||||||
|
|
||||||
handle_unknown_bit_info(Meta, {_, ExprMeta, _} = Expr, T, Size, Types, E) ->
|
validate_bit_type_args(Meta, unit, [Unit], E) when not is_integer(Unit) ->
|
||||||
case 'Elixir.Macro':expand(Expr, elixir_env:linify({?line(ExprMeta), E})) of
|
elixir_errors:compile_error(Meta, ?m(E, file),
|
||||||
|
"unit in bitstring expects an integer as argument, got: ~ts",
|
||||||
|
['Elixir.Macro':to_string(Unit)]);
|
||||||
|
validate_bit_type_args(_Meta, _Expr, _Args, _E) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
handle_unknown_bit_info(Meta, Expr, T, Size, Types, E) ->
|
||||||
|
case 'Elixir.Macro':expand(Expr, elixir_env:linify({?line(Meta), E})) of
|
||||||
Expr ->
|
Expr ->
|
||||||
elixir_errors:compile_error(ExprMeta, ?m(E, file),
|
elixir_errors:compile_error(Meta, ?m(E, file),
|
||||||
"unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]);
|
"unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]);
|
||||||
Other ->
|
Info ->
|
||||||
List = case is_list(Other) of true -> Other; false -> [Other] end,
|
expand_bit_info(Meta, unpack_bit_info(Meta, Info, E) ++ T, Size, Types, E)
|
||||||
expand_bit_info(Meta, List ++ T, Size, Types, E)
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
%% Deprecate me: we can remove unpack_bit_info/3 once deprecated.
|
||||||
|
unpack_bit_info(Meta, [H|T], _E) ->
|
||||||
|
Dashed = lists:foldl(fun(I, Acc) -> {'-', Meta, [Acc, I]} end, H, T),
|
||||||
|
unpack_bit_info(Dashed, []);
|
||||||
|
unpack_bit_info(_Meta, Expr, _E) ->
|
||||||
|
unpack_bit_info(Expr, []).
|
||||||
|
|
||||||
|
unpack_bit_info({'-', _, [H, T]}, Acc) ->
|
||||||
|
unpack_bit_info(H, unpack_bit_info(T, Acc));
|
||||||
|
unpack_bit_info({'*', _, [{'_', _, Atom}, Unit]}, Acc) when is_atom(Atom) and is_integer(Unit) ->
|
||||||
|
[{unit, [], [Unit]}|Acc];
|
||||||
|
unpack_bit_info({'*', _, [Size, Unit]}, Acc) when is_integer(Size) and is_integer(Unit) ->
|
||||||
|
[{size, [], [Size]}, {unit, [], [Unit]}|Acc];
|
||||||
|
unpack_bit_info(Size, Acc) when is_integer(Size) ->
|
||||||
|
[{size, [], [Size]}|Acc];
|
||||||
|
unpack_bit_info({Expr, Meta, Args}, Acc) when is_atom(Expr) ->
|
||||||
|
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
|
||||||
|
[{Expr, Meta, ListArgs}|Acc];
|
||||||
|
unpack_bit_info(Other, Acc) ->
|
||||||
|
[Other|Acc].
|
||||||
|
|
||||||
%% Translation
|
%% Translation
|
||||||
|
|
||||||
has_size({bin, _, Elements}) ->
|
has_size({bin, _, Elements}) ->
|
||||||
@@ -124,7 +160,7 @@ build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
|
|||||||
{Acc, S};
|
{Acc, S};
|
||||||
|
|
||||||
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
|
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
|
||||||
{Size, Types} = extract_bit_info(Meta, V, S#elixir_scope{context=nil}),
|
{Size, Types} = extract_bit_info(V, S#elixir_scope{context=nil}),
|
||||||
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types);
|
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types);
|
||||||
|
|
||||||
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
|
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
|
||||||
@@ -201,23 +237,20 @@ unit_size([], Guess) -> Guess.
|
|||||||
|
|
||||||
%% Extra bitstring specifiers
|
%% Extra bitstring specifiers
|
||||||
|
|
||||||
extract_bit_info(Meta, [{size, _, [Arg]}|T], S) ->
|
extract_bit_info({'-', _, [L, {size, _, [Size]}]}, S) ->
|
||||||
case elixir_translator:translate(Arg, S) of
|
{extract_bit_size(Size, S), extract_bit_type(L, [])};
|
||||||
{{Kind, _, _} = Size, _} when Kind == integer; Kind == var ->
|
extract_bit_info({size, _, [Size]}, S) ->
|
||||||
{Size, extract_bit_type(Meta, T, S)};
|
{extract_bit_size(Size, S), []};
|
||||||
_ ->
|
extract_bit_info(L, _S) ->
|
||||||
elixir_errors:compile_error(Meta, S#elixir_scope.file,
|
{default, extract_bit_type(L, [])}.
|
||||||
"size in bitstring expects an integer or a variable as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)])
|
|
||||||
end;
|
|
||||||
extract_bit_info(Meta, T, S) ->
|
|
||||||
{default, extract_bit_type(Meta, T, S)}.
|
|
||||||
|
|
||||||
extract_bit_type(Meta, [{unit, _, [Arg]}|T], S) when is_integer(Arg) ->
|
extract_bit_size(Size, S) ->
|
||||||
[{unit, Arg}|extract_bit_type(Meta, T, S)];
|
{TSize, _} = elixir_translator:translate(Size, S),
|
||||||
extract_bit_type(Meta, [{unit, _, [Arg]}|_], S) ->
|
TSize.
|
||||||
elixir_errors:compile_error(Meta, S#elixir_scope.file,
|
|
||||||
"unit in bitstring expects an integer as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)]);
|
extract_bit_type({'-', _, [L, R]}, Acc) ->
|
||||||
extract_bit_type(Meta, [{Other, _, []}|T], S) ->
|
extract_bit_type(L, extract_bit_type(R, Acc));
|
||||||
[Other|extract_bit_type(Meta, T, S)];
|
extract_bit_type({unit, _, [Arg]}, Acc) ->
|
||||||
extract_bit_type(_Meta, [], _S) ->
|
[{unit, Arg}|Acc];
|
||||||
[].
|
extract_bit_type({Other, _, []}, Acc) ->
|
||||||
|
[Other|Acc].
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
loaded=[],
|
loaded=[],
|
||||||
at_exit=[],
|
at_exit=[],
|
||||||
pool={[],0},
|
pool={[],0},
|
||||||
|
paths={[],[]},
|
||||||
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
|
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
|
||||||
erl_compiler_options=nil
|
erl_compiler_options=nil
|
||||||
}).
|
}).
|
||||||
@@ -27,9 +28,13 @@ start_link() ->
|
|||||||
gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
|
gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
|
||||||
|
|
||||||
init(ok) ->
|
init(ok) ->
|
||||||
code:ensure_loaded('Elixir.Macro.Env'),
|
%% We attempt to load those modules here so throughout
|
||||||
code:ensure_loaded('Elixir.Module.LocalsTracker'),
|
%% the codebase we can use a code:is_loaded/1 check on
|
||||||
code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
|
%% bootstrap instead of using the more expensive
|
||||||
|
%% code:ensure_loaded/1 multiple times.
|
||||||
|
_ = code:ensure_loaded('Elixir.Macro.Env'),
|
||||||
|
_ = code:ensure_loaded('Elixir.Module.LocalsTracker'),
|
||||||
|
_ = code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
|
||||||
{ok, #elixir_code_server{}}.
|
{ok, #elixir_code_server{}}.
|
||||||
|
|
||||||
handle_call({acquire, Path}, From, Config) ->
|
handle_call({acquire, Path}, From, Config) ->
|
||||||
@@ -64,7 +69,8 @@ handle_call({compilation_status, CompilerPid}, _From, Config) ->
|
|||||||
CompilationStatusList = Config#elixir_code_server.compilation_status,
|
CompilationStatusList = Config#elixir_code_server.compilation_status,
|
||||||
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
|
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
|
||||||
CompilationStatus = orddict:fetch(CompilerPid, CompilationStatusList),
|
CompilationStatus = orddict:fetch(CompilerPid, CompilationStatusList),
|
||||||
{reply, CompilationStatus, Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
|
{reply, CompilationStatus,
|
||||||
|
Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
|
||||||
|
|
||||||
handle_call(retrieve_module_name, _From, Config) ->
|
handle_call(retrieve_module_name, _From, Config) ->
|
||||||
case Config#elixir_code_server.pool of
|
case Config#elixir_code_server.pool of
|
||||||
@@ -83,6 +89,9 @@ handle_call(erl_compiler_options, _From, Config) ->
|
|||||||
{reply, Opts, Config}
|
{reply, Opts, Config}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
handle_call(paths, _From, Config) ->
|
||||||
|
{reply, Config#elixir_code_server.paths, Config};
|
||||||
|
|
||||||
handle_call(Request, _From, Config) ->
|
handle_call(Request, _From, Config) ->
|
||||||
{stop, {badcall, Request}, Config}.
|
{stop, {badcall, Request}, Config}.
|
||||||
|
|
||||||
@@ -115,7 +124,7 @@ handle_cast({loaded, Path}, Config) ->
|
|||||||
{ok, true} ->
|
{ok, true} ->
|
||||||
{noreply, Config};
|
{noreply, Config};
|
||||||
{ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
|
{ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
|
||||||
[Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)],
|
_ = [Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)],
|
||||||
Done = orddict:store(Path, true, Current),
|
Done = orddict:store(Path, true, Current),
|
||||||
{noreply, Config#elixir_code_server{loaded=Done}};
|
{noreply, Config#elixir_code_server{loaded=Done}};
|
||||||
error ->
|
error ->
|
||||||
@@ -131,6 +140,9 @@ handle_cast({unload_files, Files}, Config) ->
|
|||||||
handle_cast({return_module_name, H}, #elixir_code_server{pool={T,Counter}} = Config) ->
|
handle_cast({return_module_name, H}, #elixir_code_server{pool={T,Counter}} = Config) ->
|
||||||
{noreply, Config#elixir_code_server{pool={[H|T],Counter}}};
|
{noreply, Config#elixir_code_server{pool={[H|T],Counter}}};
|
||||||
|
|
||||||
|
handle_cast({paths, PA, PZ}, #elixir_code_server{} = Config) ->
|
||||||
|
{noreply, Config#elixir_code_server{paths={PA,PZ}}};
|
||||||
|
|
||||||
handle_cast(Request, Config) ->
|
handle_cast(Request, Config) ->
|
||||||
{stop, {badcast, Request}, Config}.
|
{stop, {badcast, Request}, Config}.
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ file(Relative) when is_binary(Relative) ->
|
|||||||
|
|
||||||
file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
|
file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
|
||||||
Lists = file(File),
|
Lists = file(File),
|
||||||
[binary_to_path(X, Path) || X <- Lists],
|
_ = [binary_to_path(X, Path) || X <- Lists],
|
||||||
Lists.
|
Lists.
|
||||||
|
|
||||||
%% Evaluation
|
%% Evaluation
|
||||||
@@ -144,7 +144,7 @@ module(Forms, File, Options, Bootstrap, Callback) when
|
|||||||
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
|
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
|
||||||
{ok, ModuleName, Binary, Warnings} ->
|
{ok, ModuleName, Binary, Warnings} ->
|
||||||
format_warnings(Bootstrap, Warnings),
|
format_warnings(Bootstrap, Warnings),
|
||||||
code:load_binary(ModuleName, Listname, Binary),
|
{module, ModuleName} = code:load_binary(ModuleName, Listname, Binary),
|
||||||
Callback(ModuleName, Binary);
|
Callback(ModuleName, Binary);
|
||||||
{error, Errors, Warnings} ->
|
{error, Errors, Warnings} ->
|
||||||
format_warnings(Bootstrap, Warnings),
|
format_warnings(Bootstrap, Warnings),
|
||||||
@@ -157,14 +157,14 @@ no_auto_import() ->
|
|||||||
%% CORE HANDLING
|
%% CORE HANDLING
|
||||||
|
|
||||||
core() ->
|
core() ->
|
||||||
application:start(elixir),
|
ok = application:ensure_started(elixir),
|
||||||
elixir_code_server:cast({compiler_options, [{docs,false},{internal,true}]}),
|
elixir_code_server:cast({compiler_options, [{docs,false},{internal,true}]}),
|
||||||
[core_file(File) || File <- core_main()].
|
[core_file(File) || File <- core_main()].
|
||||||
|
|
||||||
core_file(File) ->
|
core_file(File) ->
|
||||||
try
|
try
|
||||||
Lists = file(File),
|
Lists = file(File),
|
||||||
[binary_to_path(X, "lib/elixir/ebin") || X <- Lists],
|
_ = [binary_to_path(X, "lib/elixir/ebin") || X <- Lists],
|
||||||
io:format("Compiled ~ts~n", [File])
|
io:format("Compiled ~ts~n", [File])
|
||||||
catch
|
catch
|
||||||
Kind:Reason ->
|
Kind:Reason ->
|
||||||
@@ -214,11 +214,11 @@ format_errors([]) ->
|
|||||||
format_errors(Errors) ->
|
format_errors(Errors) ->
|
||||||
lists:foreach(fun ({File, Each}) ->
|
lists:foreach(fun ({File, Each}) ->
|
||||||
BinFile = elixir_utils:characters_to_binary(File),
|
BinFile = elixir_utils:characters_to_binary(File),
|
||||||
lists:foreach(fun (Error) -> elixir_errors:handle_file_error(BinFile, Error) end, Each)
|
lists:foreach(fun(Error) -> elixir_errors:handle_file_error(BinFile, Error) end, Each)
|
||||||
end, Errors).
|
end, Errors).
|
||||||
|
|
||||||
format_warnings(Bootstrap, Warnings) ->
|
format_warnings(Bootstrap, Warnings) ->
|
||||||
lists:foreach(fun ({File, Each}) ->
|
lists:foreach(fun ({File, Each}) ->
|
||||||
BinFile = elixir_utils:characters_to_binary(File),
|
BinFile = elixir_utils:characters_to_binary(File),
|
||||||
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, BinFile, Warning) end, Each)
|
lists:foreach(fun(Warning) -> elixir_errors:handle_file_warning(Bootstrap, BinFile, Warning) end, Each)
|
||||||
end, Warnings).
|
end, Warnings).
|
||||||
|
|||||||
+137
-85
@@ -2,7 +2,7 @@
|
|||||||
-module(elixir_def).
|
-module(elixir_def).
|
||||||
-export([table/1, clauses_table/1, setup/1,
|
-export([table/1, clauses_table/1, setup/1,
|
||||||
cleanup/1, reset_last/1, lookup_definition/2,
|
cleanup/1, reset_last/1, lookup_definition/2,
|
||||||
delete_definition/2, store_definition/6, unwrap_definitions/1,
|
delete_definition/2, store_definition/6, unwrap_definitions/2,
|
||||||
store_each/8, format_error/1]).
|
store_each/8, format_error/1]).
|
||||||
-include("elixir.hrl").
|
-include("elixir.hrl").
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
|
|||||||
{Name, Args} = case NameAndArgs of
|
{Name, Args} = case NameAndArgs of
|
||||||
{N, _, A} when is_atom(N), is_atom(A) -> {N, []};
|
{N, _, A} when is_atom(N), is_atom(A) -> {N, []};
|
||||||
{N, _, A} when is_atom(N), is_list(A) -> {N, A};
|
{N, _, A} when is_atom(N), is_list(A) -> {N, A};
|
||||||
_ -> elixir_errors:form_error(Line, ?m(E, file), ?MODULE, {invalid_def, Kind, NameAndArgs})
|
_ -> elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE, {invalid_def, Kind, NameAndArgs})
|
||||||
end,
|
end,
|
||||||
|
|
||||||
%% Now that we have verified the call format,
|
%% Now that we have verified the call format,
|
||||||
@@ -64,11 +64,17 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
|
|||||||
DoCheckClauses = (not lists:keymember(context, 1, Meta)) andalso (CheckClauses),
|
DoCheckClauses = (not lists:keymember(context, 1, Meta)) andalso (CheckClauses),
|
||||||
|
|
||||||
%% Check if there is a file information in the definition.
|
%% Check if there is a file information in the definition.
|
||||||
%% If so, we assume this come from another source and we need
|
%% If so, we assume this come from another source and
|
||||||
%% to linify taking into account keep line numbers.
|
%% we need to linify taking into account keep line numbers.
|
||||||
{File, Key} = case lists:keyfind(file, 1, Meta) of
|
{Location, Key} = case lists:keyfind(file, 1, Meta) of
|
||||||
{file, Bin} when is_binary(Bin) -> {Bin, keep};
|
{file, KeepFile} when is_binary(KeepFile) ->
|
||||||
_ -> {nil, line}
|
case lists:keyfind(keep, 1, Meta) of
|
||||||
|
{keep, KeepLine} when is_integer(KeepLine) -> KeepLine;
|
||||||
|
_ -> KeepLine = 0
|
||||||
|
end,
|
||||||
|
{{KeepFile, KeepLine}, keep};
|
||||||
|
_ ->
|
||||||
|
{nil, line}
|
||||||
end,
|
end,
|
||||||
|
|
||||||
LinifyArgs = elixir_quote:linify(Line, Key, Args),
|
LinifyArgs = elixir_quote:linify(Line, Key, Args),
|
||||||
@@ -77,15 +83,15 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
|
|||||||
|
|
||||||
assert_no_aliases_name(Line, Name, Args, E),
|
assert_no_aliases_name(Line, Name, Args, E),
|
||||||
store_definition(Line, Kind, DoCheckClauses, Name,
|
store_definition(Line, Kind, DoCheckClauses, Name,
|
||||||
LinifyArgs, LinifyGuards, LinifyBody, File, E).
|
LinifyArgs, LinifyGuards, LinifyBody, Location, E).
|
||||||
|
|
||||||
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #{module := Module} = ER) ->
|
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepLocation, #{module := Module} = ER) ->
|
||||||
Arity = length(Args),
|
Arity = length(Args),
|
||||||
Tuple = {Name, Arity},
|
Tuple = {Name, Arity},
|
||||||
E = ER#{function := Tuple},
|
E = ER#{function := Tuple},
|
||||||
elixir_locals:record_definition(Tuple, Kind, Module),
|
elixir_locals:record_definition(Tuple, Kind, Module),
|
||||||
|
|
||||||
Location = retrieve_location(Line, MetaFile, Module),
|
Location = retrieve_location(KeepLocation, Module),
|
||||||
{Function, Defaults, Super} = translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E),
|
{Function, Defaults, Super} = translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E),
|
||||||
|
|
||||||
DefaultsLength = length(Defaults),
|
DefaultsLength = length(Defaults),
|
||||||
@@ -115,31 +121,36 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
|
|||||||
_ ->
|
_ ->
|
||||||
Env = elixir_env:linify({Line, E}),
|
Env = elixir_env:linify({Line, E}),
|
||||||
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
|
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
|
||||||
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks]
|
_ = [Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks],
|
||||||
|
ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
make_struct_available(def, Module, '__struct__', []) ->
|
make_struct_available(def, Module, '__struct__', []) ->
|
||||||
case erlang:get(elixir_compiler_pid) of
|
case erlang:get(elixir_compiler_pid) of
|
||||||
undefined -> ok;
|
undefined -> ok;
|
||||||
Pid -> Pid ! {struct_available, Module}
|
Pid ->
|
||||||
|
Pid ! {struct_available, Module},
|
||||||
|
ok
|
||||||
end;
|
end;
|
||||||
make_struct_available(_, _, _, _) ->
|
make_struct_available(_, _, _, _) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
%% Retrieve location from meta file or @file, otherwise nil
|
%% Retrieve location from meta file (if Key == keep)
|
||||||
|
%% or @file, otherwise nil
|
||||||
|
|
||||||
retrieve_location(Line, File, Module) ->
|
retrieve_location(Location, Module) ->
|
||||||
case get_location_attribute(Module) of
|
case get_location_attribute(Module) of
|
||||||
nil when not is_binary(File) ->
|
nil when is_tuple(Location) ->
|
||||||
nil;
|
{File, Line} = Location,
|
||||||
nil ->
|
|
||||||
{normalize_location(File), Line};
|
{normalize_location(File), Line};
|
||||||
X when is_binary(X) ->
|
nil ->
|
||||||
|
nil;
|
||||||
|
File when is_binary(File) ->
|
||||||
'Elixir.Module':delete_attribute(Module, file),
|
'Elixir.Module':delete_attribute(Module, file),
|
||||||
{normalize_location(X), 0};
|
{normalize_location(File), 0};
|
||||||
{X, L} when is_binary(X) andalso is_integer(L) ->
|
{File, Line} when is_binary(File) andalso is_integer(Line) ->
|
||||||
'Elixir.Module':delete_attribute(Module, file),
|
'Elixir.Module':delete_attribute(Module, file),
|
||||||
{normalize_location(X), L}
|
{normalize_location(File), Line}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
get_location_attribute(Module) ->
|
get_location_attribute(Module) ->
|
||||||
@@ -148,8 +159,8 @@ get_location_attribute(Module) ->
|
|||||||
false -> 'Elixir.Module':get_attribute(Module, file)
|
false -> 'Elixir.Module':get_attribute(Module, file)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
normalize_location(X) ->
|
normalize_location(File) ->
|
||||||
elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(X)).
|
elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)).
|
||||||
|
|
||||||
%% Compile super
|
%% Compile super
|
||||||
|
|
||||||
@@ -179,7 +190,7 @@ translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_in
|
|||||||
translate_clause(nil, _Line, _Kind, _Args, [], _Body, _S) ->
|
translate_clause(nil, _Line, _Kind, _Args, [], _Body, _S) ->
|
||||||
{[], false};
|
{[], false};
|
||||||
translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
|
translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
|
||||||
elixir_errors:form_error(Line, File, ?MODULE, {missing_do, Kind});
|
elixir_errors:form_error([{line, Line}], File, ?MODULE, {missing_do, Kind});
|
||||||
translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
|
translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
|
||||||
{TClause, TS} = elixir_clauses:clause(Line,
|
{TClause, TS} = elixir_clauses:clause(Line,
|
||||||
fun elixir_translator:translate_args/2, Args, Body, Guards, true, S),
|
fun elixir_translator:translate_args/2, Args, Body, Guards, true, S),
|
||||||
@@ -216,88 +227,117 @@ is_macro(_) -> false.
|
|||||||
% Unwrap the functions stored in the functions table.
|
% Unwrap the functions stored in the functions table.
|
||||||
% It returns a list of all functions to be exported, plus the macros,
|
% It returns a list of all functions to be exported, plus the macros,
|
||||||
% and the body of all functions.
|
% and the body of all functions.
|
||||||
unwrap_definitions(Module) ->
|
unwrap_definitions(File, Module) ->
|
||||||
Table = table(Module),
|
Table = table(Module),
|
||||||
CTable = clauses_table(Module),
|
CTable = clauses_table(Module),
|
||||||
ets:delete(Table, last),
|
ets:delete(Table, last),
|
||||||
unwrap_definition(ets:tab2list(Table), CTable, [], [], [], [], [], [], []).
|
|
||||||
|
|
||||||
unwrap_definition([Fun|T], CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
{All, Private} = unwrap_definition(ets:tab2list(Table), File, Module, CTable, [], []),
|
||||||
Tuple = element(1, Fun),
|
Unreachable = elixir_locals:warn_unused_local(File, Module, Private),
|
||||||
Clauses = [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)],
|
split_definition(All, Unreachable, [], [], [], [], {[], []}).
|
||||||
|
|
||||||
{NewFun, NewExports, NewPrivate, NewDef, NewDefmacro} =
|
unwrap_definition([Fun|T], File, Module, CTable, All, Private) ->
|
||||||
case Clauses of
|
{Tuple, Kind, Line, _, Check, Location, {Defaults, _, _}} = Fun,
|
||||||
[] -> {false, Exports, Private, Def, Defmacro};
|
Export = export(Kind, Tuple),
|
||||||
_ -> unwrap_definition(element(2, Fun), Tuple, Fun, Exports, Private, Def, Defmacro)
|
|
||||||
end,
|
|
||||||
|
|
||||||
{NewFunctions, NewTail} = case NewFun of
|
case [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)] of
|
||||||
|
[] ->
|
||||||
|
warn_bodyless_function(Line, File, Module, Kind, Tuple),
|
||||||
|
unwrap_definition(T, File, Module, CTable, All, Private);
|
||||||
|
Clauses ->
|
||||||
|
Unwrapped = {Tuple, Kind, Line, Location,
|
||||||
|
function_for_stored_definition(Line, Export, Clauses)},
|
||||||
|
|
||||||
|
NewPrivate =
|
||||||
|
if
|
||||||
|
Kind == defp; Kind == defmacrop ->
|
||||||
|
[{Tuple, Kind, Line, Check, Defaults}|Private];
|
||||||
|
true ->
|
||||||
|
Private
|
||||||
|
end,
|
||||||
|
|
||||||
|
unwrap_definition(T, File, Module, CTable, [Unwrapped|All], NewPrivate)
|
||||||
|
end;
|
||||||
|
|
||||||
|
unwrap_definition([], _File, _Module, _CTable, All, Private) ->
|
||||||
|
{All, Private}.
|
||||||
|
|
||||||
|
split_definition([{Tuple, def, Line, Location, Body}|T], Unreachable,
|
||||||
|
Def, Defp, Defmacro, Defmacrop, Functions) ->
|
||||||
|
split_definition(T, Unreachable, [Tuple|Def], Defp, Defmacro, Defmacrop,
|
||||||
|
add_definition(Line, Location, Body, Functions));
|
||||||
|
|
||||||
|
split_definition([{Tuple, defp, Line, Location, Body}|T], Unreachable,
|
||||||
|
Def, Defp, Defmacro, Defmacrop, Functions) ->
|
||||||
|
case lists:member(Tuple, Unreachable) of
|
||||||
false ->
|
false ->
|
||||||
NewAll = All,
|
split_definition(T, Unreachable, Def, [Tuple|Defp], Defmacro, Defmacrop,
|
||||||
{Functions, Tail};
|
add_definition(Line, Location, Body, Functions));
|
||||||
_ ->
|
true ->
|
||||||
NewAll = [Tuple|All],
|
split_definition(T, Unreachable, Def, [Tuple|Defp], Defmacro, Defmacrop, Functions)
|
||||||
function_for_stored_definition(NewFun, Clauses, Functions, Tail)
|
end;
|
||||||
end,
|
|
||||||
|
|
||||||
unwrap_definition(T, CTable, NewAll, NewExports, NewPrivate,
|
split_definition([{Tuple, defmacro, Line, Location, Body}|T], Unreachable,
|
||||||
NewDef, NewDefmacro, NewFunctions, NewTail);
|
Def, Defp, Defmacro, Defmacrop, Functions) ->
|
||||||
unwrap_definition([], _CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
|
split_definition(T, Unreachable, Def, Defp, [Tuple|Defmacro], Defmacrop,
|
||||||
{All, Exports, Private, ordsets:from_list(Def),
|
add_definition(Line, Location, Body, Functions));
|
||||||
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions)}.
|
|
||||||
|
|
||||||
unwrap_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
split_definition([{Tuple, defmacrop, _Line, _Location, _Body}|T], Unreachable,
|
||||||
{Fun, [Tuple|Exports], Private, [Tuple|Def], Defmacro};
|
Def, Defp, Defmacro, Defmacrop, Functions) ->
|
||||||
unwrap_definition(defmacro, {Name, Arity} = Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
split_definition(T, Unreachable, Def, Defp, Defmacro, [Tuple|Defmacrop], Functions);
|
||||||
Macro = {elixir_utils:macro_name(Name), Arity + 1},
|
|
||||||
{setelement(1, Fun, Macro), [Macro|Exports], Private, Def, [Tuple|Defmacro]};
|
split_definition([], _Unreachable, Def, Defp, Defmacro, Defmacrop, {Head, Tail}) ->
|
||||||
unwrap_definition(defp, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
{Def, Defp, Defmacro, Defmacrop, Head ++ Tail}.
|
||||||
%% {Name, Arity}, Kind, Line, Check, Defaults
|
|
||||||
Info = {Tuple, defp, element(3, Fun), element(5, Fun), element(7, Fun)},
|
|
||||||
{Fun, Exports, [Info|Private], Def, Defmacro};
|
|
||||||
unwrap_definition(defmacrop, Tuple, Fun, Exports, Private, Def, Defmacro) ->
|
|
||||||
%% {Name, Arity}, Kind, Line, Check, Defaults
|
|
||||||
Info = {Tuple, defmacrop, element(3, Fun), element(5, Fun), element(7, Fun)},
|
|
||||||
{false, Exports, [Info|Private], Def, Defmacro}.
|
|
||||||
|
|
||||||
%% Helpers
|
%% Helpers
|
||||||
|
|
||||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, nil, _}, Clauses, Functions, Tail) ->
|
export(Kind, {Name, Arity}) when Kind == defmacro; Kind == defmacrop ->
|
||||||
{[{function, Line, Name, Arity, Clauses}|Functions], Tail};
|
{elixir_utils:macro_name(Name), Arity + 1};
|
||||||
|
export(Kind, {Name, Arity}) when Kind == def; Kind == defp ->
|
||||||
|
{Name, Arity}.
|
||||||
|
|
||||||
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, Functions, Tail) ->
|
function_for_stored_definition(Line, {Name,Arity}, Clauses) ->
|
||||||
{Functions, [
|
{function, Line, Name, Arity, Clauses}.
|
||||||
{function, Line, Name, Arity, Clauses},
|
|
||||||
{attribute, Line, file, Location} | Tail
|
add_definition(_Line, nil, Body, {Head, Tail}) ->
|
||||||
]}.
|
{[Body|Head], Tail};
|
||||||
|
add_definition(Line, Location, Body, {Head, Tail}) ->
|
||||||
|
{Head,
|
||||||
|
[{attribute, Line, file, Location}, Body|Tail]}.
|
||||||
|
|
||||||
default_function_for(Kind, Name, {clause, Line, Args, _Guards, _Exprs} = Clause)
|
default_function_for(Kind, Name, {clause, Line, Args, _Guards, _Exprs} = Clause)
|
||||||
when Kind == defmacro; Kind == defmacrop ->
|
when Kind == defmacro; Kind == defmacrop ->
|
||||||
{function, Line, Name, length(Args) - 1, [Clause]};
|
{function, Line, Name, length(Args) - 1, [Clause]};
|
||||||
|
|
||||||
default_function_for(_, Name, {clause, Line, Args, _Guards, _Exprs} = Clause) ->
|
default_function_for(_, Name, {clause, Line, Args, _Guards, _Exprs} = Clause) ->
|
||||||
{function, Line, Name, length(Args), [Clause]}.
|
{function, Line, Name, length(Args), [Clause]}.
|
||||||
|
|
||||||
|
warn_bodyless_function(_Line, _File, Special, _Kind, _Tuple)
|
||||||
|
when Special == 'Elixir.Kernel.SpecialForms'; Special == 'Elixir.Module' ->
|
||||||
|
ok;
|
||||||
|
warn_bodyless_function(Line, File, _Module, Kind, Tuple) ->
|
||||||
|
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {bodyless_fun, Kind, Tuple}),
|
||||||
|
ok.
|
||||||
|
|
||||||
%% Store each definition in the table.
|
%% Store each definition in the table.
|
||||||
%% This function also checks and emit warnings in case
|
%% This function also checks and emit warnings in case
|
||||||
%% the kind, of the visibility of the function changes.
|
%% the kind, of the visibility of the function changes.
|
||||||
|
|
||||||
store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) ->
|
store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) ->
|
||||||
Tuple = {Name, Arity},
|
Tuple = {Name, Arity},
|
||||||
|
HasBody = Clauses =/= [],
|
||||||
case ets:lookup(Table, Tuple) of
|
case ets:lookup(Table, Tuple) of
|
||||||
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, StoredDefaults}] ->
|
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, {StoredDefaults, LastHasBody, LastDefaults}}] ->
|
||||||
FinalLine = StoredLine,
|
FinalLine = StoredLine,
|
||||||
FinalLocation = StoredLocation,
|
FinalLocation = StoredLocation,
|
||||||
FinalDefaults = max(Defaults, StoredDefaults),
|
FinalDefaults = {max(Defaults, StoredDefaults), HasBody, Defaults},
|
||||||
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
|
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
|
||||||
(Check and StoredCheck) andalso
|
(Check and StoredCheck) andalso
|
||||||
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile),
|
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile),
|
||||||
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults);
|
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, LastDefaults, LastHasBody);
|
||||||
[] ->
|
[] ->
|
||||||
FinalLine = Line,
|
FinalLine = Line,
|
||||||
FinalLocation = Location,
|
FinalLocation = Location,
|
||||||
FinalDefaults = Defaults
|
FinalDefaults = {Defaults, HasBody, Defaults}
|
||||||
end,
|
end,
|
||||||
Check andalso ets:insert(Table, {last, {Name, Arity}}),
|
Check andalso ets:insert(Table, {last, {Name, Arity}}),
|
||||||
ets:insert(CTable, [{Tuple, Clause} || Clause <- Clauses ]),
|
ets:insert(CTable, [{Tuple, Clause} || Clause <- Clauses ]),
|
||||||
@@ -307,7 +347,7 @@ store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line
|
|||||||
|
|
||||||
check_valid_kind(_Line, _File, _Name, _Arity, Kind, Kind) -> [];
|
check_valid_kind(_Line, _File, _Name, _Arity, Kind, Kind) -> [];
|
||||||
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
|
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
|
||||||
elixir_errors:form_error(Line, File, ?MODULE,
|
elixir_errors:form_error([{line, Line}], File, ?MODULE,
|
||||||
{changed_kind, {Name, Arity, StoredKind, Kind}}).
|
{changed_kind, {Name, Arity, StoredKind, Kind}}).
|
||||||
|
|
||||||
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile) ->
|
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile) ->
|
||||||
@@ -315,21 +355,29 @@ check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile)
|
|||||||
{Name,Arity} -> [];
|
{Name,Arity} -> [];
|
||||||
[] -> [];
|
[] -> [];
|
||||||
_ ->
|
_ ->
|
||||||
Relative = elixir_utils:relative_to_cwd(StoredFile),
|
Relative = elixir_utils:relative_to_cwd(elixir_utils:relative_to_cwd(StoredFile)),
|
||||||
elixir_errors:handle_file_warning(File, {Line, ?MODULE,
|
elixir_errors:form_warn([{line, Line}], File, ?MODULE,
|
||||||
{ungrouped_clause, {Kind, Name, Arity, StoredLine, Relative}}})
|
{ungrouped_clause, {Kind, Name, Arity, StoredLine, Relative}})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _) -> [];
|
% Any clause after clause with defaults (body less does not count)
|
||||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, 0) ->
|
check_valid_defaults(Line, File, Name, Arity, Kind, _, StoredDefaults, true) when StoredDefaults > 0 ->
|
||||||
elixir_errors:handle_file_warning(File, {Line, ?MODULE, {out_of_order_defaults, {Kind, Name, Arity}}});
|
elixir_errors:form_error([{line, Line}], File, ?MODULE,
|
||||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, _) ->
|
{clauses_with_defaults, {Kind, Name, Arity}});
|
||||||
elixir_errors:form_error(Line, File, ?MODULE, {clauses_with_defaults, {Kind, Name, Arity}}).
|
% Clause without defaults
|
||||||
|
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _, _) -> [];
|
||||||
|
% Clause with defaults after clause without defaults
|
||||||
|
check_valid_defaults(Line, File, Name, Arity, Kind, _, 0, _) ->
|
||||||
|
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {out_of_order_defaults, {Kind, Name, Arity}});
|
||||||
|
% Clause with defaults after clause with defaults
|
||||||
|
check_valid_defaults(Line, File, Name, Arity, Kind, _, _, _) ->
|
||||||
|
elixir_errors:form_error([{line, Line}], File, ?MODULE,
|
||||||
|
{clauses_with_defaults, {Kind, Name, Arity}}).
|
||||||
|
|
||||||
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, E) ->
|
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, E) ->
|
||||||
Matches = ets:match(Table, {{Name, '$2'}, '$1', '_', '_', '_', '_', '$3'}),
|
Matches = ets:match(Table, {{Name, '$2'}, '$1', '_', '_', '_', '_', {'$3', '_', '_'}}),
|
||||||
[ begin
|
[ begin
|
||||||
elixir_errors:form_error(Line, ?m(E, file), ?MODULE,
|
elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE,
|
||||||
{defs_with_defaults, Name, {Kind, Arity}, {K, A}})
|
{defs_with_defaults, Name, {Kind, Arity}, {K, A}})
|
||||||
end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
|
end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
|
||||||
|
|
||||||
@@ -339,7 +387,8 @@ defaults_conflict(A, D, Arity, Defaults) ->
|
|||||||
|
|
||||||
check_args_for_bodyless_clause(Line, Args, E) ->
|
check_args_for_bodyless_clause(Line, Args, E) ->
|
||||||
[ begin
|
[ begin
|
||||||
elixir_errors:form_error(Line, ?m(E, file), ?MODULE, invalid_args_for_bodyless_clause)
|
elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE,
|
||||||
|
invalid_args_for_bodyless_clause)
|
||||||
end || Arg <- Args, invalid_arg(Arg) ].
|
end || Arg <- Args, invalid_arg(Arg) ].
|
||||||
|
|
||||||
invalid_arg({Name, _, Kind}) when is_atom(Name), is_atom(Kind) ->
|
invalid_arg({Name, _, Kind}) when is_atom(Name), is_atom(Kind) ->
|
||||||
@@ -350,13 +399,16 @@ invalid_arg(_) ->
|
|||||||
true.
|
true.
|
||||||
|
|
||||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
|
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
|
||||||
elixir_errors:form_error(Line, File, ?MODULE, {no_alias, Atom});
|
elixir_errors:form_error([{line, Line}], File, ?MODULE, {no_alias, Atom});
|
||||||
|
|
||||||
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
%% Format errors
|
%% Format errors
|
||||||
|
|
||||||
|
format_error({bodyless_fun, Kind, {Name, Arity}}) ->
|
||||||
|
io_lib:format("bodyless clause provided for nonexistent ~ts ~ts/~B", [Kind, Name, Arity]);
|
||||||
|
|
||||||
format_error({no_module,{Kind,Name,Arity}}) ->
|
format_error({no_module,{Kind,Name,Arity}}) ->
|
||||||
io_lib:format("cannot define function outside module, invalid scope for ~ts ~ts/~B", [Kind, Name, Arity]);
|
io_lib:format("cannot define function outside module, invalid scope for ~ts ~ts/~B", [Kind, Name, Arity]);
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ store(Module, Function, GenerateName) ->
|
|||||||
{_Count, _Clause, _Neighbours, true} -> ok;
|
{_Count, _Clause, _Neighbours, true} -> ok;
|
||||||
{Count, Clause, Neighbours, false} ->
|
{Count, Clause, Neighbours, false} ->
|
||||||
overridable(Module, orddict:store(Function, {Count, Clause, Neighbours, true}, Overridable)),
|
overridable(Module, orddict:store(Function, {Count, Clause, Neighbours, true}, Overridable)),
|
||||||
{{{Name, Arity}, Kind, Line, File, _Check, Location, Defaults}, Clauses} = Clause,
|
{{{Name, Arity}, Kind, Line, File, _Check, Location, {Defaults, _HasBody, _LastDefaults}}, Clauses} = Clause,
|
||||||
|
|
||||||
{FinalKind, FinalName} = case GenerateName of
|
{FinalKind, FinalName} = case GenerateName of
|
||||||
true -> {defp, name(Module, Function, Overridable)};
|
true -> {defp, name(Module, Function, Overridable)};
|
||||||
@@ -58,8 +58,9 @@ store(Module, Function, GenerateName) ->
|
|||||||
%% Store pending declarations that were not manually made concrete.
|
%% Store pending declarations that were not manually made concrete.
|
||||||
|
|
||||||
store_pending(Module) ->
|
store_pending(Module) ->
|
||||||
[store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module),
|
_ = [store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module),
|
||||||
not 'Elixir.Module':'defines?'(Module, X)].
|
not 'Elixir.Module':'defines?'(Module, X)],
|
||||||
|
ok.
|
||||||
|
|
||||||
%% Error handling
|
%% Error handling
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
-module(elixir_dispatch).
|
-module(elixir_dispatch).
|
||||||
-export([dispatch_import/5, dispatch_require/6,
|
-export([dispatch_import/5, dispatch_require/6,
|
||||||
require_function/5, import_function/4,
|
require_function/5, import_function/4,
|
||||||
expand_import/5, expand_require/5,
|
expand_import/6, expand_require/5,
|
||||||
default_functions/0, default_macros/0, default_requires/0,
|
default_functions/0, default_macros/0, default_requires/0,
|
||||||
find_import/4, format_error/1]).
|
find_import/4, format_error/1]).
|
||||||
-include("elixir.hrl").
|
-include("elixir.hrl").
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
-define(list, 'Elixir.List').
|
-define(list, 'Elixir.List').
|
||||||
-define(map, 'Elixir.Map').
|
-define(map, 'Elixir.Map').
|
||||||
-define(node, 'Elixir.Node').
|
-define(node, 'Elixir.Node').
|
||||||
|
-define(port, 'Elixir.Port').
|
||||||
-define(process, 'Elixir.Process').
|
-define(process, 'Elixir.Process').
|
||||||
-define(string, 'Elixir.String').
|
-define(string, 'Elixir.String').
|
||||||
-define(system, 'Elixir.System').
|
-define(system, 'Elixir.System').
|
||||||
@@ -87,7 +88,7 @@ remote_function(Meta, Receiver, Name, Arity, E) ->
|
|||||||
|
|
||||||
dispatch_import(Meta, Name, Args, E, Callback) ->
|
dispatch_import(Meta, Name, Args, E, Callback) ->
|
||||||
Arity = length(Args),
|
Arity = length(Args),
|
||||||
case expand_import(Meta, {Name, Arity}, Args, E, []) of
|
case expand_import(Meta, {Name, Arity}, Args, E, [], false) of
|
||||||
{ok, Receiver, Quoted} ->
|
{ok, Receiver, Quoted} ->
|
||||||
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
|
||||||
{ok, Receiver, NewName, NewArgs} ->
|
{ok, Receiver, NewName, NewArgs} ->
|
||||||
@@ -114,12 +115,12 @@ dispatch_require(_Meta, Receiver, Name, Args, _E, Callback) ->
|
|||||||
|
|
||||||
%% Macros expansion
|
%% Macros expansion
|
||||||
|
|
||||||
expand_import(Meta, {Name, Arity} = Tuple, Args, E, Extra) ->
|
expand_import(Meta, {Name, Arity} = Tuple, Args, E, Extra, External) ->
|
||||||
Module = ?m(E, module),
|
Module = ?m(E, module),
|
||||||
Dispatch = find_dispatch(Meta, Tuple, Extra, E),
|
Dispatch = find_dispatch(Meta, Tuple, Extra, E),
|
||||||
Function = ?m(E, function),
|
Function = ?m(E, function),
|
||||||
Local = (Function /= nil) andalso (Function /= Tuple) andalso
|
AllowLocals = External orelse ((Function /= nil) andalso (Function /= Tuple)),
|
||||||
elixir_locals:macro_for(Module, Name, Arity),
|
Local = AllowLocals andalso elixir_locals:macro_for(Module, Name, Arity),
|
||||||
|
|
||||||
case Dispatch of
|
case Dispatch of
|
||||||
%% In case it is an import, we dispatch the import.
|
%% In case it is an import, we dispatch the import.
|
||||||
@@ -476,6 +477,17 @@ inline(?process, demonitor, 2) -> {erlang, demonitor};
|
|||||||
inline(?process, link, 1) -> {erlang, link};
|
inline(?process, link, 1) -> {erlang, link};
|
||||||
inline(?process, unlink, 1) -> {erlang, unlink};
|
inline(?process, unlink, 1) -> {erlang, unlink};
|
||||||
|
|
||||||
|
inline(?port, open, 2) -> {erlang, open_port};
|
||||||
|
inline(?port, call, 3) -> {erlang, port_call};
|
||||||
|
inline(?port, close, 1) -> {erlang, port_close};
|
||||||
|
inline(?port, command, 2) -> {erlang, port_command};
|
||||||
|
inline(?port, command, 3) -> {erlang, port_command};
|
||||||
|
inline(?port, connect, 2) -> {erlang, port_connect};
|
||||||
|
inline(?port, control, 3) -> {erlang, port_control};
|
||||||
|
inline(?port, info, 1) -> {erlang, port_info};
|
||||||
|
inline(?port, info, 2) -> {erlang, port_info};
|
||||||
|
inline(?port, list, 0) -> {erlang, ports};
|
||||||
|
|
||||||
inline(?string, to_float, 1) -> {erlang, binary_to_float};
|
inline(?string, to_float, 1) -> {erlang, binary_to_float};
|
||||||
inline(?string, to_integer, 1) -> {erlang, binary_to_integer};
|
inline(?string, to_integer, 1) -> {erlang, binary_to_integer};
|
||||||
inline(?string, to_integer, 2) -> {erlang, binary_to_integer};
|
inline(?string, to_integer, 2) -> {erlang, binary_to_integer};
|
||||||
@@ -517,5 +529,9 @@ deprecation('Elixir.EEx.AssignsEngine', '__using__', _) ->
|
|||||||
"check EEx.SmartEngine for how to build custom engines";
|
"check EEx.SmartEngine for how to build custom engines";
|
||||||
deprecation('Elixir.Kernel', 'xor', _) ->
|
deprecation('Elixir.Kernel', 'xor', _) ->
|
||||||
true; %% Remember to remove xor operator from tokenizer
|
true; %% Remember to remove xor operator from tokenizer
|
||||||
|
|
||||||
|
% deprecation('Elixir.System', 'cmd', 1) ->
|
||||||
|
% "use System.cmd/3 instead;"
|
||||||
|
|
||||||
deprecation(_, _, _) ->
|
deprecation(_, _, _) ->
|
||||||
false.
|
false.
|
||||||
|
|||||||
@@ -2,36 +2,37 @@
|
|||||||
% This is not exposed in the Elixir language.
|
% This is not exposed in the Elixir language.
|
||||||
-module(elixir_errors).
|
-module(elixir_errors).
|
||||||
-export([compile_error/3, compile_error/4,
|
-export([compile_error/3, compile_error/4,
|
||||||
form_error/4, parse_error/4, warn/2, warn/3,
|
form_error/4, form_warn/4, parse_error/4, warn/2, warn/3,
|
||||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2]).
|
handle_file_warning/2, handle_file_warning/3, handle_file_error/2]).
|
||||||
-include("elixir.hrl").
|
-include("elixir.hrl").
|
||||||
|
|
||||||
-type line_or_meta() :: integer() | list().
|
-spec warn(non_neg_integer(), binary(), iolist()) -> ok.
|
||||||
|
|
||||||
warn(Warning) ->
|
warn(Line, File, Warning) when is_integer(Line), is_binary(File) ->
|
||||||
CompilerPid = get(elixir_compiler_pid),
|
warn(file_format(Line, File), Warning).
|
||||||
if
|
|
||||||
CompilerPid =/= undefined ->
|
-spec warn(iolist(), iolist()) -> ok.
|
||||||
elixir_code_server:cast({register_warning, CompilerPid});
|
|
||||||
true -> false
|
|
||||||
end,
|
|
||||||
io:put_chars(standard_error, Warning).
|
|
||||||
|
|
||||||
warn(Caller, Warning) ->
|
warn(Caller, Warning) ->
|
||||||
warn([Caller, "warning: ", Warning]).
|
do_warn([Caller, "warning: ", Warning, $\n]).
|
||||||
|
|
||||||
warn(Line, File, Warning) when is_integer(Line) ->
|
%% General forms handling.
|
||||||
warn(file_format(Line, File, "warning: " ++ Warning)).
|
|
||||||
|
|
||||||
%% Raised during expansion/translation/compilation.
|
-spec form_error(list(), binary(), module(), any()) -> no_return().
|
||||||
|
|
||||||
-spec form_error(line_or_meta(), binary(), module(), any()) -> no_return().
|
|
||||||
|
|
||||||
form_error(Meta, File, Module, Desc) ->
|
form_error(Meta, File, Module, Desc) ->
|
||||||
compile_error(Meta, File, format_error(Module, Desc)).
|
compile_error(Meta, File, format_error(Module, Desc)).
|
||||||
|
|
||||||
-spec compile_error(line_or_meta(), binary(), iolist()) -> no_return().
|
-spec form_warn(list(), binary(), module(), any()) -> ok.
|
||||||
-spec compile_error(line_or_meta(), binary(), iolist(), list()) -> no_return().
|
|
||||||
|
form_warn(Meta, File, Module, Desc) when is_list(Meta) ->
|
||||||
|
{MetaLine, MetaFile} = meta_location(Meta, File),
|
||||||
|
warn(MetaLine, MetaFile, format_error(Module, Desc)).
|
||||||
|
|
||||||
|
%% Compilation error.
|
||||||
|
|
||||||
|
-spec compile_error(list(), binary(), iolist()) -> no_return().
|
||||||
|
-spec compile_error(list(), binary(), iolist(), list()) -> no_return().
|
||||||
|
|
||||||
compile_error(Meta, File, Message) when is_list(Message) ->
|
compile_error(Meta, File, Message) when is_list(Message) ->
|
||||||
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
|
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
|
||||||
@@ -39,23 +40,23 @@ compile_error(Meta, File, Message) when is_list(Message) ->
|
|||||||
compile_error(Meta, File, Format, Args) when is_list(Format) ->
|
compile_error(Meta, File, Format, Args) when is_list(Format) ->
|
||||||
compile_error(Meta, File, io_lib:format(Format, Args)).
|
compile_error(Meta, File, io_lib:format(Format, Args)).
|
||||||
|
|
||||||
%% Raised on tokenizing/parsing
|
%% Tokenization parsing/errors.
|
||||||
|
|
||||||
-spec parse_error(line_or_meta(), binary(), binary(), binary()) -> no_return().
|
-spec parse_error(non_neg_integer(), binary(), binary(), binary()) -> no_return().
|
||||||
|
|
||||||
parse_error(Meta, File, Error, <<>>) ->
|
parse_error(Line, File, Error, <<>>) ->
|
||||||
Message = case Error of
|
Message = case Error of
|
||||||
<<"syntax error before: ">> -> <<"syntax error: expression is incomplete">>;
|
<<"syntax error before: ">> -> <<"syntax error: expression is incomplete">>;
|
||||||
_ -> Error
|
_ -> Error
|
||||||
end,
|
end,
|
||||||
raise(Meta, File, 'Elixir.TokenMissingError', Message);
|
do_raise(Line, File, 'Elixir.TokenMissingError', Message);
|
||||||
|
|
||||||
%% Show a nicer message for missing end tokens
|
%% Show a nicer message for missing end tokens
|
||||||
parse_error(Meta, File, <<"syntax error before: ">>, <<"'end'">>) ->
|
parse_error(Line, File, <<"syntax error before: ">>, <<"'end'">>) ->
|
||||||
raise(Meta, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
|
do_raise(Line, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
|
||||||
|
|
||||||
%% Binaries are wrapped in [<<...>>], so we need to unwrap them
|
%% Binaries are wrapped in [<<...>>], so we need to unwrap them
|
||||||
parse_error(Meta, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) ->
|
parse_error(Line, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) ->
|
||||||
Rest =
|
Rest =
|
||||||
case binary:split(Full, <<"<<">>) of
|
case binary:split(Full, <<"<<">>) of
|
||||||
[Lead, Token] ->
|
[Lead, Token] ->
|
||||||
@@ -66,27 +67,27 @@ parse_error(Meta, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) -
|
|||||||
[_] ->
|
[_] ->
|
||||||
<<$">>
|
<<$">>
|
||||||
end,
|
end,
|
||||||
raise(Meta, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary >>);
|
do_raise(Line, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary >>);
|
||||||
|
|
||||||
%% Everything else is fine as is
|
%% Everything else is fine as is
|
||||||
parse_error(Meta, File, Error, Token) when is_binary(Error), is_binary(Token) ->
|
parse_error(Line, File, Error, Token) when is_binary(Error), is_binary(Token) ->
|
||||||
Message = <<Error / binary, Token / binary >>,
|
Message = <<Error/binary, Token/binary >>,
|
||||||
raise(Meta, File, 'Elixir.SyntaxError', Message).
|
do_raise(Line, File, 'Elixir.SyntaxError', Message).
|
||||||
|
|
||||||
%% Handle warnings and errors (called during module compilation)
|
%% Handle warnings and errors from Erlang land (called during module compilation)
|
||||||
|
|
||||||
%% Ignore on bootstrap
|
%% Ignore on bootstrap
|
||||||
handle_file_warning(true, _File, {_Line, sys_core_fold, nomatch_guard}) -> [];
|
handle_file_warning(true, _File, {_Line, sys_core_fold, nomatch_guard}) -> ok;
|
||||||
handle_file_warning(true, _File, {_Line, sys_core_fold, {nomatch_shadow, _}}) -> [];
|
handle_file_warning(true, _File, {_Line, sys_core_fold, {nomatch_shadow, _}}) -> ok;
|
||||||
|
|
||||||
%% Ignore always
|
%% Ignore always
|
||||||
handle_file_warning(_, _File, {_Line, sys_core_fold, useless_building}) -> [];
|
handle_file_warning(_, _File, {_Line, sys_core_fold, useless_building}) -> ok;
|
||||||
|
|
||||||
%% This is an Erlang bug, it considers {tuple, _}.call to always fail
|
%% This is an Erlang bug, it considers {tuple, _}.call to always fail
|
||||||
handle_file_warning(_, _File, {_Line, v3_kernel, bad_call}) -> [];
|
handle_file_warning(_, _File, {_Line, v3_kernel, bad_call}) -> ok;
|
||||||
|
|
||||||
%% We handle unused local warnings ourselves
|
%% We handle unused local warnings ourselves
|
||||||
handle_file_warning(_, _File, {_Line, erl_lint, {unused_function, _}}) -> [];
|
handle_file_warning(_, _File, {_Line, erl_lint, {unused_function, _}}) -> ok;
|
||||||
|
|
||||||
%% Make no_effect clauses pretty
|
%% Make no_effect clauses pretty
|
||||||
handle_file_warning(_, File, {Line, sys_core_fold, {no_effect, {erlang, F, A}}}) ->
|
handle_file_warning(_, File, {Line, sys_core_fold, {no_effect, {erlang, F, A}}}) ->
|
||||||
@@ -116,7 +117,7 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity
|
|||||||
|
|
||||||
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
||||||
case elixir_compiler:get_opt(internal) of
|
case elixir_compiler:get_opt(internal) of
|
||||||
true -> [];
|
true -> ok;
|
||||||
false ->
|
false ->
|
||||||
Message = io_lib:format("behaviour ~ts undefined", [elixir_aliases:inspect(Module)]),
|
Message = io_lib:format("behaviour ~ts undefined", [elixir_aliases:inspect(Module)]),
|
||||||
warn(Line, File, Message)
|
warn(Line, File, Message)
|
||||||
@@ -124,11 +125,15 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
|
|||||||
|
|
||||||
%% Ignore unused vars at "weird" lines (<= 0)
|
%% Ignore unused vars at "weird" lines (<= 0)
|
||||||
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
|
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
|
||||||
[];
|
ok;
|
||||||
|
|
||||||
%% Ignore shadowed vars as we guarantee no conflicts ourselves
|
%% Ignore shadowed vars as we guarantee no conflicts ourselves
|
||||||
handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) ->
|
handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) ->
|
||||||
[];
|
ok;
|
||||||
|
|
||||||
|
%% Rewrite nomatch_guard to be more generic it can happen inside if, unless, etc
|
||||||
|
handle_file_warning(_, File, {Line, sys_core_fold, nomatch_guard}) ->
|
||||||
|
warn(Line, File, "this check/guard will always yield the same result");
|
||||||
|
|
||||||
%% Properly format other unused vars
|
%% Properly format other unused vars
|
||||||
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
|
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
|
||||||
@@ -152,39 +157,27 @@ handle_file_error(File, {Line,erl_lint,{unsafe_var,Var,{In,_Where}}}) ->
|
|||||||
_ -> In
|
_ -> In
|
||||||
end,
|
end,
|
||||||
Message = io_lib:format("cannot define variable ~ts inside ~ts", [format_var(Var), Translated]),
|
Message = io_lib:format("cannot define variable ~ts inside ~ts", [format_var(Var), Translated]),
|
||||||
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message));
|
do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message));
|
||||||
|
|
||||||
handle_file_error(File, {Line,erl_lint,{spec_fun_undefined,{M,F,A}}}) ->
|
handle_file_error(File, {Line,erl_lint,{spec_fun_undefined,{M,F,A}}}) ->
|
||||||
Message = io_lib:format("spec for undefined function ~ts.~ts/~B", [elixir_aliases:inspect(M), F, A]),
|
Message = io_lib:format("spec for undefined function ~ts.~ts/~B", [elixir_aliases:inspect(M), F, A]),
|
||||||
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message));
|
do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message));
|
||||||
|
|
||||||
handle_file_error(File, {Line,Module,Desc}) ->
|
handle_file_error(File, {Line,Module,Desc}) ->
|
||||||
form_error(Line, File, Module, Desc).
|
Message = format_error(Module, Desc),
|
||||||
|
do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
|
||||||
|
|
||||||
%% Helpers
|
%% Helpers
|
||||||
|
|
||||||
raise(Meta, File, Kind, Message) when is_list(Meta) ->
|
raise(Meta, File, Kind, Message) when is_list(Meta) ->
|
||||||
raise(?line(Meta), File, Kind, Message);
|
{MetaLine, MetaFile} = meta_location(Meta, File),
|
||||||
|
do_raise(MetaLine, MetaFile, Kind, Message).
|
||||||
|
|
||||||
raise(none, File, Kind, Message) ->
|
file_format(0, File) ->
|
||||||
raise(0, File, Kind, Message);
|
io_lib:format("~ts: ", [elixir_utils:relative_to_cwd(File)]);
|
||||||
|
|
||||||
raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File) ->
|
file_format(Line, File) ->
|
||||||
%% Populate the stacktrace so we can raise it
|
io_lib:format("~ts:~w: ", [elixir_utils:relative_to_cwd(File), Line]).
|
||||||
try
|
|
||||||
throw(ok)
|
|
||||||
catch
|
|
||||||
ok -> ok
|
|
||||||
end,
|
|
||||||
Stacktrace = erlang:get_stacktrace(),
|
|
||||||
Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]),
|
|
||||||
erlang:raise(error, Exception, tl(Stacktrace)).
|
|
||||||
|
|
||||||
file_format(0, File, Message) when is_binary(File) ->
|
|
||||||
io_lib:format("~ts: ~ts~n", [elixir_utils:relative_to_cwd(File), Message]);
|
|
||||||
|
|
||||||
file_format(Line, File, Message) when is_binary(File) ->
|
|
||||||
io_lib:format("~ts:~w: ~ts~n", [elixir_utils:relative_to_cwd(File), Line, Message]).
|
|
||||||
|
|
||||||
format_var(Var) ->
|
format_var(Var) ->
|
||||||
list_to_atom(lists:takewhile(fun(X) -> X /= $@ end, atom_to_list(Var))).
|
list_to_atom(lists:takewhile(fun(X) -> X /= $@ end, atom_to_list(Var))).
|
||||||
@@ -215,3 +208,37 @@ translate_comp_op('=<') -> '<=';
|
|||||||
translate_comp_op('=:=') -> '===';
|
translate_comp_op('=:=') -> '===';
|
||||||
translate_comp_op('=/=') -> '!==';
|
translate_comp_op('=/=') -> '!==';
|
||||||
translate_comp_op(Other) -> Other.
|
translate_comp_op(Other) -> Other.
|
||||||
|
|
||||||
|
meta_location(Meta, File) ->
|
||||||
|
case lists:keyfind(file, 1, Meta) of
|
||||||
|
{file, MetaFile} when is_binary(MetaFile) ->
|
||||||
|
case lists:keyfind(keep, 1, Meta) of
|
||||||
|
{keep, MetaLine} when is_integer(MetaLine) -> ok;
|
||||||
|
_ -> MetaLine = 0
|
||||||
|
end,
|
||||||
|
{MetaLine, MetaFile};
|
||||||
|
_ ->
|
||||||
|
{?line(Meta), File}
|
||||||
|
end.
|
||||||
|
|
||||||
|
do_warn(Warning) ->
|
||||||
|
CompilerPid = get(elixir_compiler_pid),
|
||||||
|
if
|
||||||
|
CompilerPid =/= undefined ->
|
||||||
|
elixir_code_server:cast({register_warning, CompilerPid});
|
||||||
|
true -> false
|
||||||
|
end,
|
||||||
|
io:put_chars(standard_error, Warning),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
do_raise(none, File, Kind, Message) ->
|
||||||
|
do_raise(0, File, Kind, Message);
|
||||||
|
do_raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File), is_binary(Message) ->
|
||||||
|
try
|
||||||
|
throw(ok)
|
||||||
|
catch
|
||||||
|
ok -> ok
|
||||||
|
end,
|
||||||
|
Stacktrace = erlang:get_stacktrace(),
|
||||||
|
Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]),
|
||||||
|
erlang:raise(error, Exception, tl(Stacktrace)).
|
||||||
@@ -169,7 +169,7 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
|
|||||||
false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote")
|
false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote")
|
||||||
end,
|
end,
|
||||||
|
|
||||||
ValidOpts = [context, location, line, unquote, bind_quoted],
|
ValidOpts = [context, location, line, file, unquote, bind_quoted],
|
||||||
{EKV, ET} = expand_opts(Meta, quote, ValidOpts, KV, E),
|
{EKV, ET} = expand_opts(Meta, quote, ValidOpts, KV, E),
|
||||||
|
|
||||||
Context = case lists:keyfind(context, 1, EKV) of
|
Context = case lists:keyfind(context, 1, EKV) of
|
||||||
@@ -185,8 +185,20 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Keep = lists:keyfind(location, 1, EKV) == {location, keep},
|
{File, Line} = case lists:keyfind(location, 1, EKV) of
|
||||||
Line = proplists:get_value(line, EKV, false),
|
{location, keep} ->
|
||||||
|
{elixir_utils:relative_to_cwd(?m(E, file)), false};
|
||||||
|
false ->
|
||||||
|
{ case lists:keyfind(file, 1, EKV) of
|
||||||
|
{file, F} -> F;
|
||||||
|
false -> nil
|
||||||
|
end,
|
||||||
|
|
||||||
|
case lists:keyfind(line, 1, EKV) of
|
||||||
|
{line, L} -> L;
|
||||||
|
false -> false
|
||||||
|
end }
|
||||||
|
end,
|
||||||
|
|
||||||
{Binding, DefaultUnquote} = case lists:keyfind(bind_quoted, 1, EKV) of
|
{Binding, DefaultUnquote} = case lists:keyfind(bind_quoted, 1, EKV) of
|
||||||
{bind_quoted, BQ} -> {BQ, false};
|
{bind_quoted, BQ} -> {BQ, false};
|
||||||
@@ -194,11 +206,11 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
|
|||||||
end,
|
end,
|
||||||
|
|
||||||
Unquote = case lists:keyfind(unquote, 1, EKV) of
|
Unquote = case lists:keyfind(unquote, 1, EKV) of
|
||||||
{unquote, Bool} when is_boolean(Bool) -> Bool;
|
{unquote, U} when is_boolean(U) -> U;
|
||||||
false -> DefaultUnquote
|
false -> DefaultUnquote
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Q = #elixir_quote{line=Line, keep=Keep, unquote=Unquote, context=Context},
|
Q = #elixir_quote{line=Line, file=File, unquote=Unquote, context=Context},
|
||||||
|
|
||||||
{Quoted, _Q} = elixir_quote:quote(Exprs, Binding, Q, ET),
|
{Quoted, _Q} = elixir_quote:quote(Exprs, Binding, Q, ET),
|
||||||
expand(Quoted, ET);
|
expand(Quoted, ET);
|
||||||
@@ -296,13 +308,8 @@ expand({Name, Meta, Kind} = Var, #{vars := Vars} = E) when is_atom(Name), is_ato
|
|||||||
VarMeta = lists:keyfind(var, 1, Meta),
|
VarMeta = lists:keyfind(var, 1, Meta),
|
||||||
if
|
if
|
||||||
VarMeta == {var, true} ->
|
VarMeta == {var, true} ->
|
||||||
Extra = case Kind of
|
compile_error(Meta, ?m(E, file), "expected var \"~ts\"~ts to expand to an existing variable "
|
||||||
nil -> "";
|
"or be part of a match", [Name, elixir_scope:context_info(Kind)]);
|
||||||
_ -> io_lib:format(" (context ~ts)", [elixir_aliases:inspect(Kind)])
|
|
||||||
end,
|
|
||||||
|
|
||||||
compile_error(Meta, ?m(E, file), "expected var ~ts~ts to expand to an existing "
|
|
||||||
"variable or be a part of a match", [Name, Extra]);
|
|
||||||
true ->
|
true ->
|
||||||
expand({Name, Meta, []}, E)
|
expand({Name, Meta, []}, E)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -33,11 +33,8 @@ extract(Line, Scope, Interpol, [$\\, $\r, $\n|Rest], Buffer, Output, Last) ->
|
|||||||
extract(Line, Scope, Interpol, [$\n|Rest], Buffer, Output, Last) ->
|
extract(Line, Scope, Interpol, [$\n|Rest], Buffer, Output, Last) ->
|
||||||
extract(Line+1, Scope, Interpol, Rest, [$\n|Buffer], Output, Last);
|
extract(Line+1, Scope, Interpol, Rest, [$\n|Buffer], Output, Last);
|
||||||
|
|
||||||
extract(Line, Scope, Interpol, [$\\, $#, ${|Rest], Buffer, Output, Last) ->
|
extract(Line, Scope, true, [$\\, $#, ${|Rest], Buffer, Output, Last) ->
|
||||||
extract(Line, Scope, Interpol, Rest, [${,$#|Buffer], Output, Last);
|
extract(Line, Scope, true, Rest, [${,$#|Buffer], 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, Output, Last) ->
|
extract(Line, Scope, true, [$#, ${|Rest], Buffer, Output, Last) ->
|
||||||
Output1 = build_string(Line, Buffer, Output),
|
Output1 = build_string(Line, Buffer, Output),
|
||||||
@@ -52,6 +49,9 @@ extract(Line, Scope, true, [$#, ${|Rest], Buffer, Output, Last) ->
|
|||||||
{error, {string, Line, "missing interpolation terminator:}", []}}
|
{error, {string, Line, "missing interpolation terminator:}", []}}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
extract(Line, Scope, Interpol, [$\\,Char|Rest], Buffer, Output, Last) ->
|
||||||
|
extract(Line, Scope, Interpol, Rest, [Char,$\\|Buffer], Output, Last);
|
||||||
|
|
||||||
%% Catch all clause
|
%% Catch all clause
|
||||||
|
|
||||||
extract(Line, Scope, Interpol, [Char|Rest], Buffer, Output, Last) ->
|
extract(Line, Scope, Interpol, [Char|Rest], Buffer, Output, Last) ->
|
||||||
|
|||||||
@@ -27,51 +27,38 @@ run(File, Callback) ->
|
|||||||
%% RECORD
|
%% RECORD
|
||||||
|
|
||||||
record_alias(Module, Line, Warn, Ref) ->
|
record_alias(Module, Line, Warn, Ref) ->
|
||||||
if_tracker(Ref, fun(Pid) ->
|
if_tracker(Ref, fun(Pid) -> ?tracker:add_alias(Pid, Module, Line, Warn), ok end).
|
||||||
?tracker:add_alias(Pid, Module, Line, Warn),
|
|
||||||
true
|
|
||||||
end).
|
|
||||||
|
|
||||||
record_import(Module, Line, Warn, Ref) ->
|
record_import(Module, Line, Warn, Ref) ->
|
||||||
if_tracker(Ref, fun(Pid) ->
|
if_tracker(Ref, fun(Pid) -> ?tracker:add_import(Pid, Module, Line, Warn), ok end).
|
||||||
?tracker:add_import(Pid, Module, Line, Warn),
|
|
||||||
true
|
|
||||||
end).
|
|
||||||
|
|
||||||
record_alias(Module, Ref) ->
|
record_alias(Module, Ref) ->
|
||||||
if_tracker(Ref, fun(Pid) ->
|
if_tracker(Ref, fun(Pid) -> ?tracker:alias_dispatch(Pid, Module), ok end).
|
||||||
?tracker:alias_dispatch(Pid, Module),
|
|
||||||
true
|
|
||||||
end).
|
|
||||||
|
|
||||||
record_import(Module, Ref) ->
|
record_import(Module, Ref) ->
|
||||||
if_tracker(Ref, fun(Pid) ->
|
if_tracker(Ref, fun(Pid) -> ?tracker:import_dispatch(Pid, Module), ok end).
|
||||||
?tracker:import_dispatch(Pid, Module),
|
|
||||||
true
|
|
||||||
end).
|
|
||||||
|
|
||||||
record_remote(Module, Ref) ->
|
record_remote(Module, Ref) ->
|
||||||
if_tracker(Ref, fun(Pid) ->
|
if_tracker(Ref, fun(Pid) -> ?tracker:remote_dispatch(Pid, Module), ok end).
|
||||||
?tracker:remote_dispatch(Pid, Module),
|
|
||||||
true
|
|
||||||
end).
|
|
||||||
|
|
||||||
%% HELPERS
|
%% HELPERS
|
||||||
|
|
||||||
if_tracker(nil, _Callback) -> false;
|
if_tracker(nil, _Callback) -> ok;
|
||||||
if_tracker(Pid, Callback) when is_pid(Pid) -> Callback(Pid).
|
if_tracker(Pid, Callback) when is_pid(Pid) -> Callback(Pid).
|
||||||
|
|
||||||
%% ERROR HANDLING
|
%% ERROR HANDLING
|
||||||
|
|
||||||
warn_unused_imports(File, Pid) ->
|
warn_unused_imports(File, Pid) ->
|
||||||
[ begin
|
[begin
|
||||||
elixir_errors:handle_file_warning(File, {L, ?MODULE, {unused_import, M}})
|
elixir_errors:form_warn([{line, L}], File, ?MODULE, {unused_import, M})
|
||||||
end || {M, L} <- ?tracker:collect_unused_imports(Pid)].
|
end || {M, L} <- ?tracker:collect_unused_imports(Pid)],
|
||||||
|
ok.
|
||||||
|
|
||||||
warn_unused_aliases(File, Pid) ->
|
warn_unused_aliases(File, Pid) ->
|
||||||
[ begin
|
[begin
|
||||||
elixir_errors:handle_file_warning(File, {L, ?MODULE, {unused_alias, M}})
|
elixir_errors:form_warn([{line, L}], File, ?MODULE, {unused_alias, M})
|
||||||
end || {M, L} <- ?tracker:collect_unused_aliases(Pid)].
|
end || {M, L} <- ?tracker:collect_unused_aliases(Pid)],
|
||||||
|
ok.
|
||||||
|
|
||||||
format_error({unused_alias, Module}) ->
|
format_error({unused_alias, Module}) ->
|
||||||
io_lib:format("unused alias ~ts", [elixir_aliases:inspect(Module)]);
|
io_lib:format("unused alias ~ts", [elixir_aliases:inspect(Module)]);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
setup/1, cleanup/1, cache_env/1, get_cached_env/1,
|
setup/1, cleanup/1, cache_env/1, get_cached_env/1,
|
||||||
record_local/2, record_local/3, record_import/4,
|
record_local/2, record_local/3, record_import/4,
|
||||||
record_definition/3, record_defaults/4,
|
record_definition/3, record_defaults/4,
|
||||||
ensure_no_function_conflict/4, warn_unused_local/3, format_error/1
|
ensure_no_import_conflict/4, warn_unused_local/3, format_error/1
|
||||||
]).
|
]).
|
||||||
-export([macro_for/3, local_for/3, local_for/4]).
|
-export([macro_for/3, local_for/3, local_for/4]).
|
||||||
|
|
||||||
@@ -73,53 +73,41 @@ rewrite_clause(Else, _) -> Else.
|
|||||||
|
|
||||||
setup(Module) ->
|
setup(Module) ->
|
||||||
case code:is_loaded(?tracker) of
|
case code:is_loaded(?tracker) of
|
||||||
{file, _} -> ets:insert(Module, {?attr, ?tracker:start_link()});
|
{file, _} -> ets:insert(Module, {?attr, ?tracker:start_link()}), ok;
|
||||||
false -> ok
|
false -> ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
cleanup(Module) ->
|
cleanup(Module) ->
|
||||||
if_tracker(Module, fun(Pid) -> unlink(Pid), ?tracker:stop(Pid) end).
|
if_tracker(Module, fun(Pid) -> unlink(Pid), ?tracker:stop(Pid), ok end).
|
||||||
|
|
||||||
record_local(Tuple, Module) when is_atom(Module) ->
|
record_local(Tuple, Module) when is_atom(Module) ->
|
||||||
if_tracker(Module, fun(Pid) ->
|
if_tracker(Module, fun(Pid) -> ?tracker:add_local(Pid, Tuple), ok end).
|
||||||
?tracker:add_local(Pid, Tuple),
|
|
||||||
true
|
|
||||||
end).
|
|
||||||
record_local(Tuple, _Module, Function)
|
record_local(Tuple, _Module, Function)
|
||||||
when Function == nil; Function == Tuple -> false;
|
when Function == nil; Function == Tuple -> ok;
|
||||||
record_local(Tuple, Module, Function) ->
|
record_local(Tuple, Module, Function) ->
|
||||||
if_tracker(Module, fun(Pid) ->
|
if_tracker(Module, fun(Pid) -> ?tracker:add_local(Pid, Function, Tuple), ok end).
|
||||||
?tracker:add_local(Pid, Function, Tuple),
|
|
||||||
true
|
|
||||||
end).
|
|
||||||
|
|
||||||
record_import(_Tuple, Receiver, Module, _Function)
|
record_import(_Tuple, Receiver, Module, _Function)
|
||||||
when Module == nil; Module == Receiver -> false;
|
when Module == nil; Module == Receiver -> false;
|
||||||
record_import(Tuple, Receiver, Module, Function) ->
|
record_import(Tuple, Receiver, Module, Function) ->
|
||||||
if_tracker(Module, fun(Pid) ->
|
if_tracker(Module, fun(Pid) -> ?tracker:add_import(Pid, Function, Receiver, Tuple), ok end).
|
||||||
?tracker:add_import(Pid, Function, Receiver, Tuple),
|
|
||||||
true
|
|
||||||
end).
|
|
||||||
|
|
||||||
record_definition(Tuple, Kind, Module) ->
|
record_definition(Tuple, Kind, Module) ->
|
||||||
if_tracker(Module, fun(Pid) ->
|
if_tracker(Module, fun(Pid) -> ?tracker:add_definition(Pid, Kind, Tuple), ok end).
|
||||||
?tracker:add_definition(Pid, Kind, Tuple),
|
|
||||||
true
|
|
||||||
end).
|
|
||||||
|
|
||||||
record_defaults(_Tuple, _Kind, _Module, 0) ->
|
record_defaults(_Tuple, _Kind, _Module, 0) ->
|
||||||
true;
|
ok;
|
||||||
record_defaults(Tuple, Kind, Module, Defaults) ->
|
record_defaults(Tuple, Kind, Module, Defaults) ->
|
||||||
if_tracker(Module, fun(Pid) ->
|
if_tracker(Module, fun(Pid) -> ?tracker:add_defaults(Pid, Kind, Tuple, Defaults), ok end).
|
||||||
?tracker:add_defaults(Pid, Kind, Tuple, Defaults),
|
|
||||||
true
|
|
||||||
end).
|
|
||||||
|
|
||||||
if_tracker(Module, Callback) ->
|
if_tracker(Module, Callback) ->
|
||||||
|
if_tracker(Module, ok, Callback).
|
||||||
|
|
||||||
|
if_tracker(Module, Default, Callback) ->
|
||||||
try ets:lookup_element(Module, ?attr, 2) of
|
try ets:lookup_element(Module, ?attr, 2) of
|
||||||
Pid -> Callback(Pid)
|
Pid -> Callback(Pid)
|
||||||
catch
|
catch
|
||||||
error:badarg -> false
|
error:badarg -> Default
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% CACHING
|
%% CACHING
|
||||||
@@ -140,25 +128,29 @@ get_cached_env(Env) -> Env.
|
|||||||
|
|
||||||
%% ERROR HANDLING
|
%% ERROR HANDLING
|
||||||
|
|
||||||
ensure_no_function_conflict(Meta, File, Module, AllDefined) ->
|
ensure_no_import_conflict(_Line, _File, 'Elixir.Kernel', _All) ->
|
||||||
if_tracker(Module, fun(Pid) ->
|
ok;
|
||||||
[ begin
|
ensure_no_import_conflict(Line, File, Module, All) ->
|
||||||
elixir_errors:form_error(Meta, File, ?MODULE, {function_conflict, Error})
|
if_tracker(Module, ok, fun(Pid) ->
|
||||||
end || Error <- ?tracker:collect_imports_conflicts(Pid, AllDefined) ]
|
_ = [ begin
|
||||||
end),
|
elixir_errors:form_error([{line, Line}], File, ?MODULE, {function_conflict, Error})
|
||||||
ok.
|
end || Error <- ?tracker:collect_imports_conflicts(Pid, All) ],
|
||||||
|
ok
|
||||||
|
end).
|
||||||
|
|
||||||
warn_unused_local(File, Module, Private) ->
|
warn_unused_local(File, Module, Private) ->
|
||||||
if_tracker(Module, fun(Pid) ->
|
if_tracker(Module, [], fun(Pid) ->
|
||||||
Args = [ {Fun, Kind, Defaults} ||
|
Args = [{Fun, Kind, Defaults} ||
|
||||||
{Fun, Kind, _Line, true, Defaults} <- Private],
|
{Fun, Kind, _Line, true, Defaults} <- Private],
|
||||||
|
|
||||||
Unused = ?tracker:collect_unused_locals(Pid, Args),
|
{Unreachable, Warnings} = ?tracker:collect_unused_locals(Pid, Args),
|
||||||
|
|
||||||
[ begin
|
[begin
|
||||||
{_, _, Line, _, _} = lists:keyfind(element(2, Error), 1, Private),
|
{_, _, Line, _, _} = lists:keyfind(element(2, Error), 1, Private),
|
||||||
elixir_errors:handle_file_warning(File, {Line, ?MODULE, Error})
|
elixir_errors:form_warn([{line, Line}], File, ?MODULE, Error)
|
||||||
end || Error <- Unused ]
|
end || Error <- Warnings ],
|
||||||
|
|
||||||
|
Unreachable
|
||||||
end).
|
end).
|
||||||
|
|
||||||
format_error({function_conflict,{Receivers, Name, Arity}}) ->
|
format_error({function_conflict,{Receivers, Name, Arity}}) ->
|
||||||
|
|||||||
@@ -22,19 +22,8 @@ expand_struct(Meta, Left, Right, E) ->
|
|||||||
|
|
||||||
EMeta =
|
EMeta =
|
||||||
case lists:member(ELeft, ?m(E, context_modules)) of
|
case lists:member(ELeft, ?m(E, context_modules)) of
|
||||||
true ->
|
true -> [{struct, context}|Meta];
|
||||||
case (ELeft == ?m(E, module)) and
|
false -> Meta
|
||||||
(?m(E, function) == nil) of
|
|
||||||
true ->
|
|
||||||
compile_error(Meta, ?m(E, file),
|
|
||||||
"cannot access struct ~ts in body of the module that defines it as "
|
|
||||||
"the struct fields are not yet accessible",
|
|
||||||
[elixir_aliases:inspect(ELeft)]);
|
|
||||||
false ->
|
|
||||||
[{struct, context}|Meta]
|
|
||||||
end;
|
|
||||||
false ->
|
|
||||||
Meta
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
case ERight of
|
case ERight of
|
||||||
@@ -90,12 +79,11 @@ translate_struct(Meta, Name, {'%{}', MapMeta, Args}, S) ->
|
|||||||
%% Helpers
|
%% Helpers
|
||||||
|
|
||||||
load_struct(Meta, Name, S) ->
|
load_struct(Meta, Name, S) ->
|
||||||
|
Context = lists:keyfind(struct, 1, Meta) == {struct, context},
|
||||||
|
|
||||||
Local =
|
Local =
|
||||||
elixir_module:is_open(Name) andalso
|
elixir_module:is_open(Name) andalso
|
||||||
(case lists:keyfind(struct, 1, Meta) of
|
(Context orelse wait_for_struct(Name)),
|
||||||
{struct, context} -> true;
|
|
||||||
_ -> wait_for_struct(Name)
|
|
||||||
end),
|
|
||||||
|
|
||||||
try
|
try
|
||||||
case Local of
|
case Local of
|
||||||
@@ -112,8 +100,16 @@ load_struct(Meta, Name, S) ->
|
|||||||
catch
|
catch
|
||||||
error:undef ->
|
error:undef ->
|
||||||
Inspected = elixir_aliases:inspect(Name),
|
Inspected = elixir_aliases:inspect(Name),
|
||||||
compile_error(Meta, S#elixir_scope.file, "~ts.__struct__/0 is undefined, "
|
|
||||||
"cannot expand struct ~ts", [Inspected, Inspected])
|
case Context of
|
||||||
|
true ->
|
||||||
|
compile_error(Meta, S#elixir_scope.file,
|
||||||
|
"cannot access struct ~ts in the same context that defines it as "
|
||||||
|
"the struct fields are not yet accessible", [Inspected]);
|
||||||
|
false ->
|
||||||
|
compile_error(Meta, S#elixir_scope.file, "~ts.__struct__/0 is undefined, "
|
||||||
|
"cannot expand struct ~ts", [Inspected, Inspected])
|
||||||
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
wait_for_struct(Module) ->
|
wait_for_struct(Module) ->
|
||||||
|
|||||||
@@ -42,35 +42,40 @@ compile(Module, Block, Vars, #{line := Line} = Env) when is_atom(Module) ->
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
compile(Module, _Block, _Vars, #{line := Line, file := File}) ->
|
compile(Module, _Block, _Vars, #{line := Line, file := File}) ->
|
||||||
elixir_errors:form_error(Line, File, ?MODULE, {invalid_module, Module}).
|
elixir_errors:form_error([{line, Line}], File, ?MODULE, {invalid_module, Module}).
|
||||||
|
|
||||||
do_compile(Line, Module, Block, Vars, E) ->
|
do_compile(Line, Module, Block, Vars, E) ->
|
||||||
File = ?m(E, file),
|
File = ?m(E, file),
|
||||||
check_module_availability(Line, File, Module),
|
check_module_availability(Line, File, Module),
|
||||||
build(Line, File, Module, ?m(E, lexical_tracker)),
|
|
||||||
|
Docs = elixir_compiler:get_opt(docs),
|
||||||
|
build(Line, File, Module, Docs, ?m(E, lexical_tracker)),
|
||||||
|
|
||||||
try
|
try
|
||||||
{Result, NE} = eval_form(Line, Module, Block, Vars, E),
|
{Result, NE} = eval_form(Line, Module, Block, Vars, E),
|
||||||
{Base, Export, Private, Def, Defmacro, Functions} = elixir_def:unwrap_definitions(Module),
|
|
||||||
|
|
||||||
{All, Forms0} = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions),
|
_ = case ets:lookup(data_table(Module), 'on_load') of
|
||||||
Forms1 = specs_form(Module, Private, Defmacro, Forms0),
|
|
||||||
Forms2 = types_form(Module, Forms1),
|
|
||||||
Forms3 = attributes_form(Line, File, Module, Forms2),
|
|
||||||
|
|
||||||
case ets:lookup(data_table(Module), 'on_load') of
|
|
||||||
[] -> ok;
|
[] -> ok;
|
||||||
[{on_load,OnLoad}] ->
|
[{on_load,OnLoad}] ->
|
||||||
[elixir_locals:record_local(Tuple, Module) || Tuple <- OnLoad]
|
[elixir_locals:record_local(Tuple, Module) || Tuple <- OnLoad]
|
||||||
end,
|
end,
|
||||||
|
|
||||||
AllFunctions = Def ++ [T || {T, defp, _, _, _} <- Private],
|
{Def, Defp, Defmacro, Defmacrop, Functions} =
|
||||||
elixir_locals:ensure_no_function_conflict(Line, File, Module, AllFunctions),
|
elixir_def:unwrap_definitions(File, Module),
|
||||||
elixir_locals:warn_unused_local(File, Module, Private),
|
|
||||||
warn_invalid_clauses(Line, File, Module, All),
|
|
||||||
warn_unused_docs(Line, File, Module),
|
|
||||||
|
|
||||||
Location = {elixir_utils:relative_to_cwd(elixir_utils:characters_to_list(File)), Line},
|
{All, Forms0} = functions_form(Line, File, Module, Def, Defp, Defmacro, Defmacrop, Functions),
|
||||||
|
Forms1 = specs_form(Module, Defmacro, Defmacrop, Forms0),
|
||||||
|
Forms2 = types_form(Line, File, Module, Forms1),
|
||||||
|
Forms3 = attributes_form(Line, File, Module, Forms2),
|
||||||
|
|
||||||
|
elixir_locals:ensure_no_import_conflict(Line, File, Module, All),
|
||||||
|
|
||||||
|
case Docs of
|
||||||
|
true -> warn_unused_docs(Line, File, Module, doc);
|
||||||
|
false -> false
|
||||||
|
end,
|
||||||
|
|
||||||
|
Location = {elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line},
|
||||||
|
|
||||||
Final = [
|
Final = [
|
||||||
{attribute, Line, file, Location},
|
{attribute, Line, file, Location},
|
||||||
@@ -88,7 +93,7 @@ do_compile(Line, Module, Block, Vars, E) ->
|
|||||||
|
|
||||||
%% Hook that builds both attribute and functions and set up common hooks.
|
%% Hook that builds both attribute and functions and set up common hooks.
|
||||||
|
|
||||||
build(Line, File, Module, Lexical) ->
|
build(Line, File, Module, Docs, Lexical) ->
|
||||||
%% Table with meta information about the module.
|
%% Table with meta information about the module.
|
||||||
DataTable = data_table(Module),
|
DataTable = data_table(Module),
|
||||||
|
|
||||||
@@ -97,16 +102,16 @@ build(Line, File, Module, Lexical) ->
|
|||||||
true ->
|
true ->
|
||||||
[{OldFile, OldLine}] = ets:lookup_element(OldTable, ?location_attr, 2),
|
[{OldFile, OldLine}] = ets:lookup_element(OldTable, ?location_attr, 2),
|
||||||
Error = {module_in_definition, Module, OldFile, OldLine},
|
Error = {module_in_definition, Module, OldFile, OldLine},
|
||||||
elixir_errors:form_error(Line, File, ?MODULE, Error);
|
elixir_errors:form_error([{line, Line}], File, ?MODULE, Error);
|
||||||
false ->
|
false ->
|
||||||
[]
|
[]
|
||||||
end,
|
end,
|
||||||
|
|
||||||
ets:new(DataTable, [set, named_table, public]),
|
DataTable = ets:new(DataTable, [set, named_table, public]),
|
||||||
ets:insert(DataTable, {before_compile, []}),
|
ets:insert(DataTable, {before_compile, []}),
|
||||||
ets:insert(DataTable, {after_compile, []}),
|
ets:insert(DataTable, {after_compile, []}),
|
||||||
|
|
||||||
case elixir_compiler:get_opt(docs) of
|
case Docs of
|
||||||
true -> ets:insert(DataTable, {on_definition, [{'Elixir.Module', compile_doc}]});
|
true -> ets:insert(DataTable, {on_definition, [{'Elixir.Module', compile_doc}]});
|
||||||
_ -> ets:insert(DataTable, {on_definition, []})
|
_ -> ets:insert(DataTable, {on_definition, []})
|
||||||
end,
|
end,
|
||||||
@@ -121,7 +126,8 @@ build(Line, File, Module, Lexical) ->
|
|||||||
|
|
||||||
%% Setup other modules
|
%% Setup other modules
|
||||||
elixir_def:setup(Module),
|
elixir_def:setup(Module),
|
||||||
elixir_locals:setup(Module).
|
elixir_locals:setup(Module),
|
||||||
|
ok.
|
||||||
|
|
||||||
%% Receives the module representation and evaluates it.
|
%% Receives the module representation and evaluates it.
|
||||||
|
|
||||||
@@ -143,16 +149,15 @@ eval_callbacks(Line, Module, Name, Args, E) ->
|
|||||||
|
|
||||||
%% Return the form with exports and function declarations.
|
%% Return the form with exports and function declarations.
|
||||||
|
|
||||||
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, BaseFunctions) ->
|
functions_form(Line, File, Module, Def, Defp, Defmacro, Defmacrop, Body) ->
|
||||||
{InfoSpec, Info} = add_info_function(Line, File, Module, BaseExport, Def, Defmacro),
|
All = Def ++ Defp ++ Defmacro ++ Defmacrop,
|
||||||
|
{Spec, Info} = add_info_function(Line, File, Module, All, Def, Defmacro),
|
||||||
|
|
||||||
All = [{'__info__', 1}|BaseAll],
|
NewBody = [Spec, Info|Body],
|
||||||
Export = [{'__info__', 1}|BaseExport],
|
Export = [{Name, Arity} || {function, _, Name, Arity, _} <- NewBody],
|
||||||
Functions = [InfoSpec,Info|BaseFunctions],
|
|
||||||
|
|
||||||
{All, [
|
{[{'__info__',1}|All],
|
||||||
{attribute, Line, export, lists:sort(Export)} | Functions
|
[{attribute, Line, export, lists:sort(Export)} | NewBody]}.
|
||||||
]}.
|
|
||||||
|
|
||||||
%% Add attributes handling to the form
|
%% Add attributes handling to the form
|
||||||
|
|
||||||
@@ -188,12 +193,12 @@ process_attribute(_Line, _File, _Key, Values) ->
|
|||||||
process_external_resource(_Line, _File, Value) when is_binary(Value) ->
|
process_external_resource(_Line, _File, Value) when is_binary(Value) ->
|
||||||
Value;
|
Value;
|
||||||
process_external_resource(Line, File, Value) ->
|
process_external_resource(Line, File, Value) ->
|
||||||
elixir_errors:handle_file_error(File,
|
elixir_errors:form_error([{line, Line}], File,
|
||||||
{Line, ?MODULE, {invalid_external_resource, Value}}).
|
?MODULE, {invalid_external_resource, Value}).
|
||||||
|
|
||||||
%% Types
|
%% Types
|
||||||
|
|
||||||
types_form(Module, Forms0) ->
|
types_form(Line, File, Module, Forms0) ->
|
||||||
case code:ensure_loaded('Elixir.Kernel.Typespec') of
|
case code:ensure_loaded('Elixir.Kernel.Typespec') of
|
||||||
{module, 'Elixir.Kernel.Typespec'} ->
|
{module, 'Elixir.Kernel.Typespec'} ->
|
||||||
Types0 = 'Elixir.Module':get_attribute(Module, type) ++
|
Types0 = 'Elixir.Module':get_attribute(Module, type) ++
|
||||||
@@ -207,6 +212,8 @@ types_form(Module, Forms0) ->
|
|||||||
'Elixir.Module':delete_attribute(Module, typep),
|
'Elixir.Module':delete_attribute(Module, typep),
|
||||||
'Elixir.Module':delete_attribute(Module, opaque),
|
'Elixir.Module':delete_attribute(Module, opaque),
|
||||||
|
|
||||||
|
warn_unused_docs(Line, File, Module, typedoc),
|
||||||
|
|
||||||
Forms1 = types_attributes(Types1, Forms0),
|
Forms1 = types_attributes(Types1, Forms0),
|
||||||
Forms2 = export_types_attributes(Types1, Forms1),
|
Forms2 = export_types_attributes(Types1, Forms1),
|
||||||
typedocs_attributes(Types1, Forms2);
|
typedocs_attributes(Types1, Forms2);
|
||||||
@@ -241,11 +248,9 @@ typedocs_attributes(Types, Forms) ->
|
|||||||
|
|
||||||
%% Specs
|
%% Specs
|
||||||
|
|
||||||
specs_form(Module, Private, Defmacro, Forms) ->
|
specs_form(Module, Defmacro, Defmacrop, Forms) ->
|
||||||
case code:ensure_loaded('Elixir.Kernel.Typespec') of
|
case code:ensure_loaded('Elixir.Kernel.Typespec') of
|
||||||
{module, 'Elixir.Kernel.Typespec'} ->
|
{module, 'Elixir.Kernel.Typespec'} ->
|
||||||
Defmacrop = [Tuple || {Tuple, defmacrop, _, _, _} <- Private],
|
|
||||||
|
|
||||||
Specs0 = 'Elixir.Module':get_attribute(Module, spec) ++
|
Specs0 = 'Elixir.Module':get_attribute(Module, spec) ++
|
||||||
'Elixir.Module':get_attribute(Module, callback),
|
'Elixir.Module':get_attribute(Module, callback),
|
||||||
|
|
||||||
@@ -274,10 +279,10 @@ specs_attributes(Forms, Specs) ->
|
|||||||
end, Forms, Dict).
|
end, Forms, Dict).
|
||||||
|
|
||||||
translate_macro_spec({{spec, NameArity, Spec}, Line}, Defmacro, Defmacrop) ->
|
translate_macro_spec({{spec, NameArity, Spec}, Line}, Defmacro, Defmacrop) ->
|
||||||
case ordsets:is_element(NameArity, Defmacrop) of
|
case lists:member(NameArity, Defmacrop) of
|
||||||
true -> [];
|
true -> [];
|
||||||
false ->
|
false ->
|
||||||
case ordsets:is_element(NameArity, Defmacro) of
|
case lists:member(NameArity, Defmacro) of
|
||||||
true ->
|
true ->
|
||||||
{Name, Arity} = NameArity,
|
{Name, Arity} = NameArity,
|
||||||
[{{spec, {elixir_utils:macro_name(Name), Arity + 1}, spec_for_macro(Spec)}, Line}];
|
[{{spec, {elixir_utils:macro_name(Name), Arity + 1}, spec_for_macro(Spec)}, Line}];
|
||||||
@@ -314,14 +319,14 @@ load_form(Line, Forms, Opts, #{file := File} = E) ->
|
|||||||
put(elixir_compiled, [{Module,Binary}|Current]),
|
put(elixir_compiled, [{Module,Binary}|Current]),
|
||||||
|
|
||||||
case get(elixir_compiler_pid) of
|
case get(elixir_compiler_pid) of
|
||||||
undefined -> [];
|
undefined -> ok;
|
||||||
PID ->
|
PID ->
|
||||||
Ref = make_ref(),
|
Ref = make_ref(),
|
||||||
PID ! {module_available, self(), Ref, File, Module, Binary},
|
PID ! {module_available, self(), Ref, File, Module, Binary},
|
||||||
receive {Ref, ack} -> ok end
|
receive {Ref, ack} -> ok end
|
||||||
end;
|
end;
|
||||||
_ ->
|
_ ->
|
||||||
[]
|
ok
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Binary
|
Binary
|
||||||
@@ -345,13 +350,12 @@ get_docs(Module) ->
|
|||||||
get_moduledoc(Line, Module) ->
|
get_moduledoc(Line, Module) ->
|
||||||
{Line, 'Elixir.Module':get_attribute(Module, moduledoc)}.
|
{Line, 'Elixir.Module':get_attribute(Module, moduledoc)}.
|
||||||
|
|
||||||
|
|
||||||
check_module_availability(Line, File, Module) ->
|
check_module_availability(Line, File, Module) ->
|
||||||
Reserved = ['Elixir.Any', 'Elixir.BitString', 'Elixir.Function', 'Elixir.PID',
|
Reserved = ['Elixir.Any', 'Elixir.BitString', 'Elixir.Function', 'Elixir.PID',
|
||||||
'Elixir.Reference', 'Elixir.Elixir', 'Elixir'],
|
'Elixir.Reference', 'Elixir.Elixir', 'Elixir'],
|
||||||
|
|
||||||
case lists:member(Module, Reserved) of
|
case lists:member(Module, Reserved) of
|
||||||
true -> elixir_errors:handle_file_error(File, {Line, ?MODULE, {module_reserved, Module}});
|
true -> elixir_errors:form_error([{line, Line}], File, ?MODULE, {module_reserved, Module});
|
||||||
false -> ok
|
false -> ok
|
||||||
end,
|
end,
|
||||||
|
|
||||||
@@ -359,7 +363,7 @@ check_module_availability(Line, File, Module) ->
|
|||||||
false ->
|
false ->
|
||||||
case code:ensure_loaded(Module) of
|
case code:ensure_loaded(Module) of
|
||||||
{module, _} ->
|
{module, _} ->
|
||||||
elixir_errors:handle_file_warning(File, {Line, ?MODULE, {module_defined, Module}});
|
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {module_defined, Module});
|
||||||
{error, _} ->
|
{error, _} ->
|
||||||
ok
|
ok
|
||||||
end;
|
end;
|
||||||
@@ -367,56 +371,64 @@ check_module_availability(Line, File, Module) ->
|
|||||||
ok
|
ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
warn_invalid_clauses(_Line, _File, 'Elixir.Kernel.SpecialForms', _All) -> ok;
|
warn_unused_docs(Line, File, Module, Attribute) ->
|
||||||
warn_invalid_clauses(_Line, File, Module, All) ->
|
case ets:member(data_table(Module), Attribute) of
|
||||||
ets:foldl(fun
|
true ->
|
||||||
({_, _, Kind, _, _}, _) when Kind == type; Kind == opaque ->
|
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {unused_doc, Attribute});
|
||||||
ok;
|
_ ->
|
||||||
({Tuple, Line, _, _, _}, _) ->
|
ok
|
||||||
case lists:member(Tuple, All) of
|
end.
|
||||||
false ->
|
|
||||||
elixir_errors:handle_file_warning(File, {Line, ?MODULE, {invalid_clause, Tuple}});
|
|
||||||
true ->
|
|
||||||
ok
|
|
||||||
end
|
|
||||||
end, ok, docs_table(Module)).
|
|
||||||
|
|
||||||
warn_unused_docs(Line, File, Module) ->
|
% __INFO__
|
||||||
lists:foreach(fun(Attribute) ->
|
|
||||||
case ets:member(data_table(Module), Attribute) of
|
|
||||||
true ->
|
|
||||||
elixir_errors:handle_file_warning(File, {Line, ?MODULE, {unused_doc, Attribute}});
|
|
||||||
_ ->
|
|
||||||
ok
|
|
||||||
end
|
|
||||||
end, [typedoc]).
|
|
||||||
|
|
||||||
% EXTRA FUNCTIONS
|
add_info_function(Line, File, Module, All, Def, Defmacro) ->
|
||||||
|
|
||||||
add_info_function(Line, File, Module, Export, Def, Defmacro) ->
|
|
||||||
Pair = {'__info__', 1},
|
Pair = {'__info__', 1},
|
||||||
case lists:member(Pair, Export) of
|
case lists:member(Pair, All) of
|
||||||
true ->
|
true ->
|
||||||
elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair});
|
elixir_errors:form_error([{line, Line}], File, ?MODULE, {internal_function_overridden, Pair});
|
||||||
false ->
|
false ->
|
||||||
{
|
Spec =
|
||||||
{attribute, Line, spec, {{'__info__', 1},
|
{attribute, Line, spec, {Pair,
|
||||||
[{type, Line, 'fun', [{type, Line, product, [ {type, Line, atom, []}]}, {type, Line, term, []} ]}]
|
[{type, Line, 'fun', [
|
||||||
|
{type, Line, product, [
|
||||||
|
{type, Line, union, [
|
||||||
|
{atom, Line, attributes},
|
||||||
|
{atom, Line, compile},
|
||||||
|
{atom, Line, exports},
|
||||||
|
{atom, Line, functions},
|
||||||
|
{atom, Line, imports},
|
||||||
|
{atom, Line, macros},
|
||||||
|
{atom, Line, module}
|
||||||
|
]}
|
||||||
|
]},
|
||||||
|
{type, Line, union, [
|
||||||
|
{type, Line, atom, []},
|
||||||
|
{type, Line, list, [
|
||||||
|
{type, Line, tuple, [
|
||||||
|
{type, Line, atom, []},
|
||||||
|
{type, Line, any, []}
|
||||||
|
]}
|
||||||
|
]}
|
||||||
|
]}
|
||||||
|
]}]
|
||||||
}},
|
}},
|
||||||
|
|
||||||
|
Info =
|
||||||
{function, 0, '__info__', 1, [
|
{function, 0, '__info__', 1, [
|
||||||
functions_clause(Def),
|
functions_clause(Def),
|
||||||
macros_clause(Defmacro),
|
macros_clause(Defmacro),
|
||||||
module_clause(Module),
|
module_clause(Module),
|
||||||
else_clause()
|
else_clause()
|
||||||
]}
|
]},
|
||||||
}
|
|
||||||
|
{Spec, Info}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
functions_clause(Def) ->
|
functions_clause(Def) ->
|
||||||
{clause, 0, [{atom, 0, functions}], [], [elixir_utils:elixir_to_erl(Def)]}.
|
{clause, 0, [{atom, 0, functions}], [], [elixir_utils:elixir_to_erl(lists:sort(Def))]}.
|
||||||
|
|
||||||
macros_clause(Defmacro) ->
|
macros_clause(Defmacro) ->
|
||||||
{clause, 0, [{atom, 0, macros}], [], [elixir_utils:elixir_to_erl(Defmacro)]}.
|
{clause, 0, [{atom, 0, macros}], [], [elixir_utils:elixir_to_erl(lists:sort(Defmacro))]}.
|
||||||
|
|
||||||
module_clause(Module) ->
|
module_clause(Module) ->
|
||||||
{clause, 0, [{atom, 0, module}], [], [{atom, 0, Module}]}.
|
{clause, 0, [{atom, 0, module}], [], [{atom, 0, Module}]}.
|
||||||
@@ -476,8 +488,6 @@ prune_stacktrace(Info, []) ->
|
|||||||
|
|
||||||
% ERROR HANDLING
|
% ERROR HANDLING
|
||||||
|
|
||||||
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}) ->
|
format_error({invalid_external_resource, Value}) ->
|
||||||
io_lib:format("expected a string value for @external_resource, got: ~p",
|
io_lib:format("expected a string value for @external_resource, got: ~p",
|
||||||
['Elixir.Kernel':inspect(Value)]);
|
['Elixir.Kernel':inspect(Value)]);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
-module(elixir_quote).
|
-module(elixir_quote).
|
||||||
-export([escape/2, linify/2, linify/3, linify_with_context_counter/3, quote/4]).
|
-export([escape/2, linify/2, linify/3, linify_with_context_counter/3, quote/4]).
|
||||||
-export([dot/6, tail_list/3, list/2]). %% Quote callbacks
|
-export([dot/5, tail_list/3, list/2]). %% Quote callbacks
|
||||||
|
|
||||||
-include("elixir.hrl").
|
-include("elixir.hrl").
|
||||||
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
|
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
|
||||||
-define(lexical(Kind), Kind == import; Kind == alias; Kind == '__aliases__').
|
-define(lexical(Kind), Kind == import; Kind == alias; Kind == require).
|
||||||
-compile({inline, [keyfind/2, keystore/3, keydelete/2, keyreplace/3, keynew/3]}).
|
-compile({inline, [keyfind/2, keystore/3, keydelete/2, keyreplace/3, keynew/3]}).
|
||||||
|
|
||||||
%% Apply the line from site call on quoted contents.
|
%% Apply the line from site call on quoted contents.
|
||||||
@@ -23,7 +23,8 @@ do_linify(Line, Key, {Receiver, Counter} = Var, {Left, Meta, Receiver})
|
|||||||
when is_atom(Left), is_list(Meta), Left /= '_' ->
|
when is_atom(Left), is_list(Meta), Left /= '_' ->
|
||||||
do_tuple_linify(Line, Key, Var, keynew(counter, Meta, Counter), Left, Receiver);
|
do_tuple_linify(Line, Key, Var, keynew(counter, Meta, Counter), Left, Receiver);
|
||||||
|
|
||||||
do_linify(Line, Key, {_, Counter} = Var, {Lexical, [_|_] = Meta, [_|_] = Args}) when ?lexical(Lexical) ->
|
do_linify(Line, Key, {_, Counter} = Var, {Lexical, [_|_] = Meta, [_|_] = Args})
|
||||||
|
when ?lexical(Lexical); Lexical == '__aliases__' ->
|
||||||
do_tuple_linify(Line, Key, Var, keynew(counter, Meta, Counter), Lexical, Args);
|
do_tuple_linify(Line, Key, Var, keynew(counter, Meta, Counter), Lexical, Args);
|
||||||
|
|
||||||
do_linify(Line, Key, Var, {Left, Meta, Right}) when is_list(Meta) ->
|
do_linify(Line, Key, Var, {Left, Meta, Right}) when is_list(Meta) ->
|
||||||
@@ -61,8 +62,8 @@ do_linify_meta(Line, Key, Meta) ->
|
|||||||
|
|
||||||
%% Some expressions cannot be unquoted at compilation time.
|
%% Some expressions cannot be unquoted at compilation time.
|
||||||
%% This function is responsible for doing runtime unquoting.
|
%% This function is responsible for doing runtime unquoting.
|
||||||
dot(Meta, Left, Right, Args, Context, File) ->
|
dot(Meta, Left, Right, Args, Context) ->
|
||||||
annotate(dot(Meta, Left, Right, Args), Context, File).
|
annotate(dot(Meta, Left, Right, Args), Context).
|
||||||
|
|
||||||
dot(Meta, Left, {'__aliases__', _, Args}, nil) ->
|
dot(Meta, Left, {'__aliases__', _, Args}, nil) ->
|
||||||
{'__aliases__', Meta, [Left|Args]};
|
{'__aliases__', Meta, [Left|Args]};
|
||||||
@@ -117,28 +118,29 @@ validate_list(List) when not is_list(List) ->
|
|||||||
argument_error(Message) ->
|
argument_error(Message) ->
|
||||||
error('Elixir.ArgumentError':exception([{message,Message}])).
|
error('Elixir.ArgumentError':exception([{message,Message}])).
|
||||||
|
|
||||||
%% Annotates the AST with context and other info
|
%% Annotates the AST with context and other info.
|
||||||
|
%%
|
||||||
|
%% Note we need to delete the counter because linify
|
||||||
|
%% adds the counter recursively, even inside quoted
|
||||||
|
%% expressions, so we need to clean up the forms to
|
||||||
|
%% allow them to get a new counter on the next expansion.
|
||||||
|
|
||||||
annotate({Def, Meta, [{H, M, A}|T]}, Context, File) when ?defs(Def) ->
|
annotate({Def, Meta, [{H, M, A}|T]}, Context) when ?defs(Def) ->
|
||||||
%% Store the context information in the first element of the
|
{Def, Meta, [{H, keystore(context, M, Context), A}|T]};
|
||||||
%% definition tuple so we can access it later on.
|
annotate({{'.', _, [_, Def]} = Target, Meta, [{H, M, A}|T]}, Context) when ?defs(Def) ->
|
||||||
MM = keystore(context, keystore(file, M, File), Context),
|
{Target, Meta, [{H, keystore(context, M, Context), A}|T]};
|
||||||
{Def, Meta, [{H, MM, A}|T]};
|
|
||||||
annotate({{'.', _, [_, Def]} = Target, Meta, [{H, M, A}|T]}, Context, File) when ?defs(Def) ->
|
|
||||||
MM = keystore(context, keystore(file, M, File), Context),
|
|
||||||
{Target, Meta, [{H, MM, A}|T]};
|
|
||||||
|
|
||||||
annotate({Lexical, Meta, [_|_] = Args}, Context, _File) when Lexical == import; Lexical == alias ->
|
annotate({Lexical, Meta, [_|_] = Args}, Context) when ?lexical(Lexical) ->
|
||||||
NewMeta = keystore(context, keydelete(counter, Meta), Context),
|
NewMeta = keystore(context, keydelete(counter, Meta), Context),
|
||||||
{Lexical, NewMeta, Args};
|
{Lexical, NewMeta, Args};
|
||||||
annotate(Tree, _Context, _File) -> Tree.
|
annotate(Tree, _Context) -> Tree.
|
||||||
|
|
||||||
%% Escapes the given expression. It is similar to quote, but
|
%% Escapes the given expression. It is similar to quote, but
|
||||||
%% lines are kept and hygiene mechanisms are disabled.
|
%% lines are kept and hygiene mechanisms are disabled.
|
||||||
escape(Expr, Unquote) ->
|
escape(Expr, Unquote) ->
|
||||||
{Res, Q} = quote(Expr, nil, #elixir_quote{
|
{Res, Q} = quote(Expr, nil, #elixir_quote{
|
||||||
line=true,
|
line=true,
|
||||||
keep=false,
|
file=nil,
|
||||||
vars_hygiene=false,
|
vars_hygiene=false,
|
||||||
aliases_hygiene=false,
|
aliases_hygiene=false,
|
||||||
imports_hygiene=false,
|
imports_hygiene=false,
|
||||||
@@ -221,11 +223,11 @@ do_quote({Name, Meta, ArgsOrAtom}, #elixir_quote{imports_hygiene=true} = Q, E) w
|
|||||||
keystore(import, keystore(context, Meta, Q#elixir_quote.context), Receiver)
|
keystore(import, keystore(context, Meta, Q#elixir_quote.context), Receiver)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Annotated = annotate({Name, NewMeta, ArgsOrAtom}, Q#elixir_quote.context, file(E, Q)),
|
Annotated = annotate({Name, NewMeta, ArgsOrAtom}, Q#elixir_quote.context),
|
||||||
do_quote_tuple(Annotated, Q, E);
|
do_quote_tuple(Annotated, Q, E);
|
||||||
|
|
||||||
do_quote({_, _, _} = Tuple, #elixir_quote{escape=false} = Q, E) ->
|
do_quote({_, _, _} = Tuple, #elixir_quote{escape=false} = Q, E) ->
|
||||||
Annotated = annotate(Tuple, Q#elixir_quote.context, file(E, Q)),
|
Annotated = annotate(Tuple, Q#elixir_quote.context),
|
||||||
do_quote_tuple(Annotated, Q, E);
|
do_quote_tuple(Annotated, Q, E);
|
||||||
|
|
||||||
%% Literals
|
%% Literals
|
||||||
@@ -267,7 +269,7 @@ do_quote(Other, Q, _) ->
|
|||||||
|
|
||||||
do_quote_call(Left, Meta, Expr, Args, Q, E) ->
|
do_quote_call(Left, Meta, Expr, Args, Q, E) ->
|
||||||
All = [meta(Meta, Q), Left, {unquote, Meta, [Expr]}, Args,
|
All = [meta(Meta, Q), Left, {unquote, Meta, [Expr]}, Args,
|
||||||
Q#elixir_quote.context, file(E, Q)],
|
Q#elixir_quote.context],
|
||||||
{TAll, TQ} = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, E) end, Q, All),
|
{TAll, TQ} = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, E) end, Q, All),
|
||||||
{{{'.', Meta, [elixir_quote, dot]}, Meta, TAll}, TQ}.
|
{{{'.', Meta, [elixir_quote, dot]}, Meta, TAll}, TQ}.
|
||||||
|
|
||||||
@@ -285,16 +287,19 @@ do_quote_tuple({Left, Meta, Right}, Q, E) ->
|
|||||||
{TRight, RQ} = do_quote(Right, LQ, E),
|
{TRight, RQ} = do_quote(Right, LQ, E),
|
||||||
{{'{}', [], [TLeft, meta(Meta, Q), TRight]}, RQ}.
|
{{'{}', [], [TLeft, meta(Meta, Q), TRight]}, RQ}.
|
||||||
|
|
||||||
file(#{file := File}, #elixir_quote{keep=true}) -> File;
|
meta(Meta, Q) ->
|
||||||
file(_, _) -> nil.
|
file(line(Meta, Q), Q).
|
||||||
|
|
||||||
meta(Meta, #elixir_quote{keep=true}) ->
|
file(Meta, #elixir_quote{file=nil}) -> Meta;
|
||||||
|
file(Meta, #elixir_quote{file=File}) -> [{file, File}|Meta].
|
||||||
|
|
||||||
|
line(Meta, #elixir_quote{file=File}) when File /= nil ->
|
||||||
[case KV of {line, V} -> {keep, V}; _ -> KV end || KV <- Meta];
|
[case KV of {line, V} -> {keep, V}; _ -> KV end || KV <- Meta];
|
||||||
meta(Meta, #elixir_quote{line=true}) ->
|
line(Meta, #elixir_quote{line=true}) ->
|
||||||
Meta;
|
Meta;
|
||||||
meta(Meta, #elixir_quote{line=false}) ->
|
line(Meta, #elixir_quote{line=false}) ->
|
||||||
keydelete(line, Meta);
|
keydelete(line, Meta);
|
||||||
meta(Meta, #elixir_quote{line=Line}) ->
|
line(Meta, #elixir_quote{line=Line}) ->
|
||||||
keystore(line, Meta, Line).
|
keystore(line, Meta, Line).
|
||||||
|
|
||||||
reverse_improper(L) -> reverse_improper(L, []).
|
reverse_improper(L) -> reverse_improper(L, []).
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
%% Convenience functions used to manipulate scope and its variables.
|
%% Convenience functions used to manipulate scope and its variables.
|
||||||
-module(elixir_scope).
|
-module(elixir_scope).
|
||||||
-export([translate_var/4, build_var/2,
|
-export([translate_var/4, build_var/2, context_info/1,
|
||||||
load_binding/2, dump_binding/2,
|
load_binding/2, dump_binding/2,
|
||||||
mergev/2, mergec/2, mergef/2,
|
mergev/2, mergec/2, mergef/2,
|
||||||
merge_vars/2, merge_opt_vars/2
|
merge_vars/2, merge_opt_vars/2,
|
||||||
|
format_error/1
|
||||||
]).
|
]).
|
||||||
-include("elixir.hrl").
|
-include("elixir.hrl").
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ translate_var(Meta, Name, Kind, S) when is_atom(Kind); is_integer(Kind) ->
|
|||||||
|
|
||||||
case Exists andalso ordsets:is_element(Tuple, MatchVars) of
|
case Exists andalso ordsets:is_element(Tuple, MatchVars) of
|
||||||
true ->
|
true ->
|
||||||
|
warn_underscored_var(Line, S#elixir_scope.file, Name, Kind),
|
||||||
{{var, Line, Current}, S};
|
{{var, Line, Current}, S};
|
||||||
false ->
|
false ->
|
||||||
%% We attempt to give vars a nice name because we
|
%% We attempt to give vars a nice name because we
|
||||||
@@ -60,6 +62,19 @@ build_var(Key, S) ->
|
|||||||
Cnt = orddict:fetch(Key, New),
|
Cnt = orddict:fetch(Key, New),
|
||||||
{elixir_utils:atom_concat([Key, "@", Cnt]), Cnt, S#elixir_scope{counter=New}}.
|
{elixir_utils:atom_concat([Key, "@", Cnt]), Cnt, S#elixir_scope{counter=New}}.
|
||||||
|
|
||||||
|
context_info(Kind) when Kind == nil; is_integer(Kind) -> "";
|
||||||
|
context_info(Kind) -> io_lib:format(" (context ~ts)", [elixir_aliases:inspect(Kind)]).
|
||||||
|
|
||||||
|
warn_underscored_var(Line, File, Name, Kind) ->
|
||||||
|
case atom_to_list(Name) of
|
||||||
|
"_@" ++ _ ->
|
||||||
|
ok; %% Automatically generated variables
|
||||||
|
"_" ++ _ ->
|
||||||
|
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {unused_match, Name, Kind});
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
%% SCOPE MERGING
|
%% SCOPE MERGING
|
||||||
|
|
||||||
%% Receives two scopes and return a new scope based on
|
%% Receives two scopes and return a new scope based on
|
||||||
@@ -138,3 +153,12 @@ dump_binding([{{Var, Kind} = Key, {InternalName,_}}|T], Binding, Acc) when is_at
|
|||||||
dump_binding([_|T], Binding, Acc) ->
|
dump_binding([_|T], Binding, Acc) ->
|
||||||
dump_binding(T, Binding, Acc);
|
dump_binding(T, Binding, Acc);
|
||||||
dump_binding([], _Binding, Acc) -> Acc.
|
dump_binding([], _Binding, Acc) -> Acc.
|
||||||
|
|
||||||
|
%% Errors
|
||||||
|
|
||||||
|
format_error({unused_match, Name, Kind}) ->
|
||||||
|
io_lib:format("the underscored variable \"~ts\"~ts appears more than once in a "
|
||||||
|
"match. This means the pattern will only match if all \"~ts\" bind "
|
||||||
|
"to the same value. If this is the intended behaviour, please "
|
||||||
|
"remove the leading underscore from the variable name, otherwise "
|
||||||
|
"give the variables different names", [Name, context_info(Kind), Name]).
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ translate({fn, Meta, Clauses}, S) ->
|
|||||||
|
|
||||||
%% Cond
|
%% Cond
|
||||||
|
|
||||||
translate({'cond', _Meta, [[{do, Pairs}]]}, S) ->
|
translate({'cond', CondMeta, [[{do, Pairs}]]}, S) ->
|
||||||
[{'->', Meta, [[Condition], Body]}|T] = lists:reverse(Pairs),
|
[{'->', Meta, [[Condition], Body]}|T] = lists:reverse(Pairs),
|
||||||
Case =
|
Case =
|
||||||
case Condition of
|
case Condition of
|
||||||
@@ -88,7 +88,7 @@ translate({'cond', _Meta, [[{do, Pairs}]]}, S) ->
|
|||||||
Acc = {'case', Meta, [Condition, [{do, [Truthy, Falsy]}]]},
|
Acc = {'case', Meta, [Condition, [{do, [Truthy, Falsy]}]]},
|
||||||
build_cond_clauses(T, Acc, Meta)
|
build_cond_clauses(T, Acc, Meta)
|
||||||
end,
|
end,
|
||||||
translate(Case, S);
|
translate(replace_case_meta(CondMeta, Case), S);
|
||||||
|
|
||||||
%% Case
|
%% Case
|
||||||
|
|
||||||
@@ -249,7 +249,7 @@ translate({{'.', _, [Left, Right]}, Meta, Args}, S)
|
|||||||
{atom, Line, 'true'}},
|
{atom, Line, 'true'}},
|
||||||
{map_field_assoc, Line,
|
{map_field_assoc, Line,
|
||||||
{atom, Line, key},
|
{atom, Line, key},
|
||||||
{atom, Line, TRight}},
|
TRight},
|
||||||
{map_field_assoc, Line,
|
{map_field_assoc, Line,
|
||||||
{atom, Line, term},
|
{atom, Line, term},
|
||||||
TVar}]},
|
TVar}]},
|
||||||
@@ -387,6 +387,11 @@ build_cond_clauses([{'->', NewMeta, [[Condition], Body]}|T], Acc, OldMeta) ->
|
|||||||
build_cond_clauses([], Acc, _) ->
|
build_cond_clauses([], Acc, _) ->
|
||||||
Acc.
|
Acc.
|
||||||
|
|
||||||
|
replace_case_meta(Meta, {'case', _, Args}) ->
|
||||||
|
{'case', Meta, Args};
|
||||||
|
replace_case_meta(_Meta, Other) ->
|
||||||
|
Other.
|
||||||
|
|
||||||
build_truthy_clause(Meta, Condition, Body) ->
|
build_truthy_clause(Meta, Condition, Body) ->
|
||||||
case elixir_utils:returns_boolean(Condition) of
|
case elixir_utils:returns_boolean(Condition) of
|
||||||
true ->
|
true ->
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ read_link_type(File) ->
|
|||||||
relative_to_cwd(Path) ->
|
relative_to_cwd(Path) ->
|
||||||
case elixir_compiler:get_opt(internal) of
|
case elixir_compiler:get_opt(internal) of
|
||||||
true -> Path;
|
true -> Path;
|
||||||
false -> 'Elixir.String':to_char_list('Elixir.Path':relative_to_cwd(Path))
|
false -> 'Elixir.Path':relative_to_cwd(Path)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
characters_to_list(Data) when is_list(Data) ->
|
characters_to_list(Data) when is_list(Data) ->
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ defmodule AccessTest do
|
|||||||
test "for nil" do
|
test "for nil" do
|
||||||
assert nil[:foo] == nil
|
assert nil[:foo] == nil
|
||||||
assert Access.get(nil, :foo) == nil
|
assert Access.get(nil, :foo) == nil
|
||||||
assert Access.get_and_update(nil, :foo, fn nil -> {:ok, :bar} end) == {:ok, :bar}
|
assert_raise ArgumentError, "could not put/update key :foo on a nil value", fn ->
|
||||||
|
Access.get_and_update(nil, :foo, fn nil -> {:ok, :bar} end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "for keywords" do
|
test "for keywords" do
|
||||||
|
|||||||
@@ -28,6 +28,16 @@ defmodule CodeTest do
|
|||||||
assert Code.eval_string("var!(a, Sample) = 1") == {1, [{{:a,Sample},1}]}
|
assert Code.eval_string("var!(a, Sample) = 1") == {1, [{{:a,Sample},1}]}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test :eval_binary_errors do
|
||||||
|
msg = "nofile:2: a binary field without size is only allowed at the end of a binary pattern"
|
||||||
|
assert_raise CompileError, msg, fn ->
|
||||||
|
Code.eval_string("""
|
||||||
|
foo = "foo"
|
||||||
|
"\\"" <> bar <> "\\"" = foo
|
||||||
|
""")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
test :eval_with_unnamed_scopes do
|
test :eval_with_unnamed_scopes do
|
||||||
assert {%RuntimeError{}, [a: %RuntimeError{}]} =
|
assert {%RuntimeError{}, [a: %RuntimeError{}]} =
|
||||||
Code.eval_string("a = (try do (raise \"hello\") rescue e -> e end)")
|
Code.eval_string("a = (try do (raise \"hello\") rescue e -> e end)")
|
||||||
|
|||||||
@@ -280,6 +280,28 @@ defmodule EnumTest.List do
|
|||||||
assert Enum.sort([5, 3, 2, 4, 1], &(&1 > &2)) == [5, 4, 3, 2, 1]
|
assert Enum.sort([5, 3, 2, 4, 1], &(&1 > &2)) == [5, 4, 3, 2, 1]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test :sort_by do
|
||||||
|
collection = [
|
||||||
|
[other_data: 1, sorted_data: 5],
|
||||||
|
[other_data: 3, sorted_data: 4],
|
||||||
|
[other_data: 4, sorted_data: 3],
|
||||||
|
[other_data: 2, sorted_data: 2],
|
||||||
|
[other_data: 5, sorted_data: 1]
|
||||||
|
]
|
||||||
|
|
||||||
|
assert Enum.sort_by(
|
||||||
|
collection,
|
||||||
|
&(&1[:sorted_data])
|
||||||
|
) == [
|
||||||
|
[other_data: 5, sorted_data: 1],
|
||||||
|
[other_data: 2, sorted_data: 2],
|
||||||
|
[other_data: 4, sorted_data: 3],
|
||||||
|
[other_data: 3, sorted_data: 4],
|
||||||
|
[other_data: 1, sorted_data: 5]
|
||||||
|
]
|
||||||
|
assert Enum.sort_by(collection, &(&1[:sorted_data]), &>=/2) == collection
|
||||||
|
end
|
||||||
|
|
||||||
test :split do
|
test :split do
|
||||||
assert Enum.split([1, 2, 3], 0) == {[], [1, 2, 3]}
|
assert Enum.split([1, 2, 3], 0) == {[], [1, 2, 3]}
|
||||||
assert Enum.split([1, 2, 3], 1) == {[1], [2, 3]}
|
assert Enum.split([1, 2, 3], 1) == {[1], [2, 3]}
|
||||||
|
|||||||
@@ -5,4 +5,4 @@ defmodule AtExit do
|
|||||||
end
|
end
|
||||||
System.at_exit fn(status) -> IO.puts "cruel world with status #{status}" end
|
System.at_exit fn(status) -> IO.puts "cruel world with status #{status}" end
|
||||||
AtExit.at_exit("goodbye ")
|
AtExit.at_exit("goodbye ")
|
||||||
exit(0)
|
exit({:shutdown, 1})
|
||||||
|
|||||||
@@ -28,35 +28,53 @@ defmodule FloatTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
test :floor do
|
test :floor do
|
||||||
assert Float.floor(12) === 12
|
assert Float.floor(12.524235) === 12.0
|
||||||
assert Float.floor(-12) === -12
|
assert Float.floor(-12.5) === -13.0
|
||||||
assert Float.floor(12.524235) === 12
|
assert Float.floor(-12.524235) === -13.0
|
||||||
assert Float.floor(-12.5) === -13
|
assert Float.floor(7.5e3) === 7500.0
|
||||||
assert Float.floor(-12.524235) === -13
|
assert Float.floor(7.5432e3) === 7543.0
|
||||||
assert Float.floor(7.5e3) === 7500
|
assert Float.floor(7.5e-3) === 0.0
|
||||||
assert Float.floor(7.5432e3) === 7543
|
assert Float.floor(-12.32453e4) === -123246.0
|
||||||
assert Float.floor(7.5e-3) === 0
|
assert Float.floor(-12.32453e-10) === -1.0
|
||||||
assert Float.floor(-12.32453e4) === -123246
|
assert Float.floor(0.32453e-10) === 0.0
|
||||||
assert Float.floor(-12.32453e-10) === -1
|
assert Float.floor(-0.32453e-10) === -1.0
|
||||||
assert Float.floor(0.32453e-10) === 0
|
assert Float.floor(1.32453e-10) === 0.0
|
||||||
assert Float.floor(-0.32453e-10) === -1
|
end
|
||||||
assert Float.floor(1.32453e-10) === 0
|
|
||||||
|
test :floor_with_precision do
|
||||||
|
assert Float.floor(12.524235, 0) === 12.0
|
||||||
|
assert Float.floor(-12.524235, 0) === -13.0
|
||||||
|
|
||||||
|
assert Float.floor(12.52, 2) === 12.52
|
||||||
|
assert Float.floor(-12.52, 2) === -12.52
|
||||||
|
|
||||||
|
assert Float.floor(12.524235, 2) === 12.52
|
||||||
|
assert Float.floor(-12.524235, 3) === -12.525
|
||||||
end
|
end
|
||||||
|
|
||||||
test :ceil do
|
test :ceil do
|
||||||
assert Float.ceil(12) === 12
|
assert Float.ceil(12.524235) === 13.0
|
||||||
assert Float.ceil(-12) === -12
|
assert Float.ceil(-12.5) === -12.0
|
||||||
assert Float.ceil(12.524235) === 13
|
assert Float.ceil(-12.524235) === -12.0
|
||||||
assert Float.ceil(-12.5) === -12
|
assert Float.ceil(7.5e3) === 7500.0
|
||||||
assert Float.ceil(-12.524235) === -12
|
assert Float.ceil(7.5432e3) === 7544.0
|
||||||
assert Float.ceil(7.5e3) === 7500
|
assert Float.ceil(7.5e-3) === 1.0
|
||||||
assert Float.ceil(7.5432e3) === 7544
|
assert Float.ceil(-12.32453e4) === -123245.0
|
||||||
assert Float.ceil(7.5e-3) === 1
|
assert Float.ceil(-12.32453e-10) === 0.0
|
||||||
assert Float.ceil(-12.32453e4) === -123245
|
assert Float.ceil(0.32453e-10) === 1.0
|
||||||
assert Float.ceil(-12.32453e-10) === 0
|
assert Float.ceil(-0.32453e-10) === 0.0
|
||||||
assert Float.ceil(0.32453e-10) === 1
|
assert Float.ceil(1.32453e-10) === 1.0
|
||||||
assert Float.ceil(-0.32453e-10) === 0
|
end
|
||||||
assert Float.ceil(1.32453e-10) === 1
|
|
||||||
|
test :ceil_with_precision do
|
||||||
|
assert Float.ceil(12.524235, 0) === 13.0
|
||||||
|
assert Float.ceil(-12.524235, 0) === -12.0
|
||||||
|
|
||||||
|
assert Float.ceil(12.52, 2) === 12.52
|
||||||
|
assert Float.ceil(-12.52, 2) === -12.52
|
||||||
|
|
||||||
|
assert Float.ceil(12.524235, 2) === 12.53
|
||||||
|
assert Float.ceil(-12.524235, 3) === -12.524
|
||||||
end
|
end
|
||||||
|
|
||||||
test :round do
|
test :round do
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ defmodule Inspect.BitStringTest do
|
|||||||
use ExUnit.Case, async: true
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
test :bitstring do
|
test :bitstring do
|
||||||
assert inspect(<<1 :: [size(12), integer, signed]>>) == "<<0, 1::size(4)>>"
|
assert inspect(<<1 :: size(12)-integer-signed>>) == "<<0, 1::size(4)>>"
|
||||||
end
|
end
|
||||||
|
|
||||||
test :binary do
|
test :binary do
|
||||||
@@ -256,15 +256,16 @@ defmodule Inspect.MapTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defimpl Inspect do
|
defimpl Inspect do
|
||||||
def inspect(_, _) do
|
def inspect(struct, _) do
|
||||||
raise "failing"
|
struct.unknown
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test :bad_implementation do
|
test :bad_implementation do
|
||||||
msg = "Got RuntimeError with message \"failing\" " <>
|
msg = "Got KeyError with message \"key :unknown not found in: " <>
|
||||||
"while inspecting %{__struct__: Inspect.MapTest.Failing, key: 0}"
|
"%{__struct__: Inspect.MapTest.Failing, key: 0}\" while " <>
|
||||||
|
"inspecting %{__struct__: Inspect.MapTest.Failing, key: 0}"
|
||||||
|
|
||||||
assert_raise ArgumentError, msg, fn ->
|
assert_raise ArgumentError, msg, fn ->
|
||||||
inspect(%Failing{})
|
inspect(%Failing{})
|
||||||
|
|||||||
@@ -58,6 +58,20 @@ defmodule IOTest do
|
|||||||
assert File.close(file) == :ok
|
assert File.close(file) == :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test :readall do
|
||||||
|
{:ok, file} = File.open(Path.expand('fixtures/file.txt', __DIR__))
|
||||||
|
assert "FOO\n" == IO.read(file, :all)
|
||||||
|
assert "" == IO.read(file, :all)
|
||||||
|
assert File.close(file) == :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
test :readall_with_utf8_and_binary do
|
||||||
|
{:ok, file} = File.open(Path.expand('fixtures/utf8.txt', __DIR__), [:utf8])
|
||||||
|
assert "Русский\n日\n" == IO.read(file, :all)
|
||||||
|
assert "" == IO.read(file, :all)
|
||||||
|
assert File.close(file) == :ok
|
||||||
|
end
|
||||||
|
|
||||||
test :readline do
|
test :readline do
|
||||||
{:ok, file} = File.open(Path.expand('fixtures/file.txt', __DIR__))
|
{:ok, file} = File.open(Path.expand('fixtures/file.txt', __DIR__))
|
||||||
assert "FOO\n" == IO.read(file, :line)
|
assert "FOO\n" == IO.read(file, :line)
|
||||||
@@ -72,6 +86,13 @@ defmodule IOTest do
|
|||||||
assert File.close(file) == :ok
|
assert File.close(file) == :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test :binreadall do
|
||||||
|
{:ok, file} = File.open(Path.expand('fixtures/utf8.txt', __DIR__))
|
||||||
|
assert "Русский\n日\n" == IO.binread(file, :all)
|
||||||
|
assert "" == IO.binread(file, :all)
|
||||||
|
assert File.close(file) == :ok
|
||||||
|
end
|
||||||
|
|
||||||
test :binreadline do
|
test :binreadline do
|
||||||
{:ok, file} = File.open(Path.expand('fixtures/utf8.txt', __DIR__))
|
{:ok, file} = File.open(Path.expand('fixtures/utf8.txt', __DIR__))
|
||||||
assert "Русский\n" == IO.binread(file, :line)
|
assert "Русский\n" == IO.binread(file, :line)
|
||||||
|
|||||||
@@ -60,22 +60,22 @@ bar """
|
|||||||
<<"f", "oo">> <> x = "foobar"
|
<<"f", "oo">> <> x = "foobar"
|
||||||
assert x == "bar"
|
assert x == "bar"
|
||||||
|
|
||||||
<<x :: [binary, size(3)]>> <> _ = "foobar"
|
<<x :: binary-size(3)>> <> _ = "foobar"
|
||||||
assert x == "foo"
|
assert x == "foo"
|
||||||
|
|
||||||
size = 3
|
size = 3
|
||||||
<<x :: [binary, size(size)]>> <> _ = "foobar"
|
<<x :: binary-size(size)>> <> _ = "foobar"
|
||||||
assert x == "foo"
|
assert x == "foo"
|
||||||
|
|
||||||
<<x :: [binary, size(6), unit(4)]>> <> _ = "foobar"
|
<<x :: 6*4-binary>> <> _ = "foobar"
|
||||||
assert x == "foo"
|
assert x == "foo"
|
||||||
|
|
||||||
assert_raise ErlangError, fn ->
|
assert_raise CompileError, fn ->
|
||||||
Code.eval_string(~s{<<x :: [binary, size(3), unit(4)]>> <> _ = "foobar"})
|
Code.eval_string(~s{<<x :: binary-size(3)-unit(4)>> <> _ = "foobar"})
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_raise ErlangError, fn ->
|
assert_raise CompileError, fn ->
|
||||||
Code.eval_string(~s{<<x :: [integer, size(4)]>> <> _ = "foobar"})
|
Code.eval_string(~s{<<x :: integer-size(4)>> <> _ = "foobar"})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ bar """
|
|||||||
|
|
||||||
assert <<106,111,115,195,169>> == << "josé" :: utf8 >>
|
assert <<106,111,115,195,169>> == << "josé" :: utf8 >>
|
||||||
assert <<0,106,0,111,0,115,0,233>> == << "josé" :: utf16 >>
|
assert <<0,106,0,111,0,115,0,233>> == << "josé" :: utf16 >>
|
||||||
assert <<106,0,111,0,115,0,233,0>> == << "josé" :: [utf16, little] >>
|
assert <<106,0,111,0,115,0,233,0>> == << "josé" :: little-utf16 >>
|
||||||
assert <<0,0,0,106,0,0,0,111,0,0,0,115,0,0,0,233>> == << "josé" :: utf32 >>
|
assert <<0,0,0,106,0,0,0,111,0,0,0,115,0,0,0,233>> == << "josé" :: utf32 >>
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -167,26 +167,26 @@ bar """
|
|||||||
test :bitsyntax_translation do
|
test :bitsyntax_translation do
|
||||||
refb = "sample"
|
refb = "sample"
|
||||||
sec_data = "another"
|
sec_data = "another"
|
||||||
<< byte_size(refb) :: [size(1), big, signed, integer, unit(8)],
|
<< byte_size(refb) :: size(1)-big-signed-integer-unit(8),
|
||||||
refb :: binary,
|
refb :: binary,
|
||||||
byte_size(sec_data) :: [size(1), big, signed, integer, unit(16)],
|
byte_size(sec_data) :: 1*16-big-signed-integer,
|
||||||
sec_data :: binary >>
|
sec_data :: binary >>
|
||||||
end
|
end
|
||||||
|
|
||||||
test :bitsyntax_size_shorcut do
|
test :bitsyntax_size_shorcut do
|
||||||
assert << 1 :: 3 >> == << 1 :: size(3) >>
|
assert << 1 :: 3 >> == << 1 :: size(3) >>
|
||||||
assert << 1 :: [unit(8), 3] >> == << 1 :: [unit(8), size(3)] >>
|
assert << 1 :: 3*8 >> == << 1 :: size(3)-unit(8) >>
|
||||||
end
|
end
|
||||||
|
|
||||||
defmacrop signed_16 do
|
defmacrop signed_16 do
|
||||||
quote do
|
quote do
|
||||||
[big, signed, integer, unit(16)]
|
big-signed-integer-unit(16)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defmacrop refb_spec do
|
defmacrop refb_spec do
|
||||||
quote do
|
quote do
|
||||||
[size(1), big, signed, integer, unit(8)]
|
1*8-big-signed-integer
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ bar """
|
|||||||
sec_data = "another"
|
sec_data = "another"
|
||||||
<< byte_size(refb) :: refb_spec,
|
<< byte_size(refb) :: refb_spec,
|
||||||
refb :: binary,
|
refb :: binary,
|
||||||
byte_size(sec_data) :: [size(1), signed_16],
|
byte_size(sec_data) :: size(1)-signed_16,
|
||||||
sec_data :: binary >>
|
sec_data :: binary >>
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ defmodule Kernel.CLI.AtExitTest do
|
|||||||
|
|
||||||
test "invokes at_exit callbacks" do
|
test "invokes at_exit callbacks" do
|
||||||
assert elixir(fixture_path("at_exit.exs") |> to_char_list) ==
|
assert elixir(fixture_path("at_exit.exs") |> to_char_list) ==
|
||||||
'goodbye cruel world with status 0\n'
|
'goodbye cruel world with status 1\n'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -69,9 +69,7 @@ defmodule Kernel.CLI.ErrorTest do
|
|||||||
test "properly format errors" do
|
test "properly format errors" do
|
||||||
assert :string.str('** (throw) 1', elixir('-e "throw 1"')) == 0
|
assert :string.str('** (throw) 1', elixir('-e "throw 1"')) == 0
|
||||||
assert :string.str('** (ErlangError) erlang error: 1', elixir('-e "error 1"')) == 0
|
assert :string.str('** (ErlangError) erlang error: 1', elixir('-e "error 1"')) == 0
|
||||||
|
assert elixir('-e "IO.puts(Process.flag(:trap_exit, false)); exit({:shutdown, 1})"') == 'false\n'
|
||||||
# It does not catch exits with integers nor strings...
|
|
||||||
assert elixir('-e "exit 1"') == ''
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -133,7 +131,7 @@ defmodule Kernel.CLI.ParallelCompilerTest do
|
|||||||
test "does not hang on missing dependencies" do
|
test "does not hang on missing dependencies" do
|
||||||
fixtures = [fixture_path("parallel_compiler/bat.ex")]
|
fixtures = [fixture_path("parallel_compiler/bat.ex")]
|
||||||
assert capture_io(fn ->
|
assert capture_io(fn ->
|
||||||
assert catch_exit(Kernel.ParallelCompiler.files(fixtures)) == 1
|
assert catch_exit(Kernel.ParallelCompiler.files(fixtures)) == {:shutdown, 1}
|
||||||
end) =~ "Compilation error"
|
end) =~ "Compilation error"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -142,7 +140,7 @@ defmodule Kernel.CLI.ParallelCompilerTest do
|
|||||||
fixture_path("parallel_deadlock/bar.ex")]
|
fixture_path("parallel_deadlock/bar.ex")]
|
||||||
|
|
||||||
msg = capture_io(fn ->
|
msg = capture_io(fn ->
|
||||||
assert catch_exit(Kernel.ParallelCompiler.files fixtures) == 1
|
assert catch_exit(Kernel.ParallelCompiler.files fixtures) == {:shutdown, 1}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert msg =~ ~r"== Compilation error on file .+parallel_deadlock/foo\.ex =="
|
assert msg =~ ~r"== Compilation error on file .+parallel_deadlock/foo\.ex =="
|
||||||
@@ -157,7 +155,7 @@ defmodule Kernel.CLI.ParallelCompilerTest do
|
|||||||
Code.compiler_options(warnings_as_errors: true)
|
Code.compiler_options(warnings_as_errors: true)
|
||||||
|
|
||||||
capture_io :stderr, fn ->
|
capture_io :stderr, fn ->
|
||||||
assert catch_exit(Kernel.ParallelCompiler.files fixtures) == 1
|
assert catch_exit(Kernel.ParallelCompiler.files fixtures) == {:shutdown, 1}
|
||||||
end
|
end
|
||||||
after
|
after
|
||||||
Code.compiler_options(warnings_as_errors: warnings_as_errors)
|
Code.compiler_options(warnings_as_errors: warnings_as_errors)
|
||||||
|
|||||||
@@ -137,6 +137,19 @@ defmodule Kernel.ErrorsTest do
|
|||||||
def hello(arg \\ 1), do: nil
|
def hello(arg \\ 1), do: nil
|
||||||
end
|
end
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
assert_compile_fail CompileError,
|
||||||
|
"nofile:6: def hello/1 has default values and multiple clauses, " <>
|
||||||
|
"define a function head with the defaults",
|
||||||
|
~C'''
|
||||||
|
defmodule ErrorsTest do
|
||||||
|
def bye(arg \\ 0)
|
||||||
|
def bye(arg), do: arg
|
||||||
|
|
||||||
|
def hello(arg \\ 0), do: nil
|
||||||
|
def hello(arg), do: arg
|
||||||
|
end
|
||||||
|
'''
|
||||||
end
|
end
|
||||||
|
|
||||||
test :invalid_match_pattern do
|
test :invalid_match_pattern do
|
||||||
@@ -219,7 +232,7 @@ defmodule Kernel.ErrorsTest do
|
|||||||
|
|
||||||
test :struct_access_on_body do
|
test :struct_access_on_body do
|
||||||
assert_compile_fail CompileError,
|
assert_compile_fail CompileError,
|
||||||
"nofile:3: cannot access struct TZ in body of the module that defines it " <>
|
"nofile:3: cannot access struct TZ in the same context that defines it " <>
|
||||||
"as the struct fields are not yet accessible",
|
"as the struct fields are not yet accessible",
|
||||||
'''
|
'''
|
||||||
defmodule TZ do
|
defmodule TZ do
|
||||||
@@ -499,7 +512,7 @@ defmodule Kernel.ErrorsTest do
|
|||||||
test :duplicated_bitstring_size do
|
test :duplicated_bitstring_size do
|
||||||
assert_compile_fail CompileError,
|
assert_compile_fail CompileError,
|
||||||
"nofile:1: duplicated size definition in bitstring",
|
"nofile:1: duplicated size definition in bitstring",
|
||||||
'<<1 :: [size(12), size(13)]>>'
|
'<<1 :: size(12)-size(13)>>'
|
||||||
end
|
end
|
||||||
|
|
||||||
test :invalid_bitstring_specified do
|
test :invalid_bitstring_specified do
|
||||||
@@ -524,12 +537,6 @@ defmodule Kernel.ErrorsTest do
|
|||||||
'<<1 :: unit(:x)>>'
|
'<<1 :: unit(:x)>>'
|
||||||
end
|
end
|
||||||
|
|
||||||
test :invalid_var! do
|
|
||||||
assert_compile_fail CompileError,
|
|
||||||
"nofile:1: expected var x to expand to an existing variable or be a part of a match",
|
|
||||||
'var!(x)'
|
|
||||||
end
|
|
||||||
|
|
||||||
test :invalid_alias do
|
test :invalid_alias do
|
||||||
assert_compile_fail CompileError,
|
assert_compile_fail CompileError,
|
||||||
"nofile:1: invalid value for keyword :as, expected an alias, got nested alias: Sample.Lists",
|
"nofile:1: invalid value for keyword :as, expected an alias, got nested alias: Sample.Lists",
|
||||||
|
|||||||
@@ -127,10 +127,10 @@ defmodule Kernel.ExpansionTest do
|
|||||||
test "vars: forces variable to exist" do
|
test "vars: forces variable to exist" do
|
||||||
assert expand(quote do: (var!(a) = 1; var!(a)))
|
assert expand(quote do: (var!(a) = 1; var!(a)))
|
||||||
|
|
||||||
message = ~r"expected var a to expand to an existing variable or be a part of a match"
|
message = ~r"expected var \"a\" to expand to an existing variable or be part of a match"
|
||||||
assert_raise CompileError, message, fn -> expand(quote do: var!(a)) end
|
assert_raise CompileError, message, fn -> expand(quote do: var!(a)) end
|
||||||
|
|
||||||
message = ~r"expected var a \(context Unknown\) to expand to an existing variable or be a part of a match"
|
message = ~r"expected var \"a\" \(context Unknown\) to expand to an existing variable or be part of a match"
|
||||||
assert_raise CompileError, message, fn -> expand(quote do: var!(a, Unknown)) end
|
assert_raise CompileError, message, fn -> expand(quote do: var!(a, Unknown)) end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -424,18 +424,37 @@ defmodule Kernel.ExpansionTest do
|
|||||||
|
|
||||||
## Binaries
|
## Binaries
|
||||||
|
|
||||||
|
test "bitstrings: size * unit" do
|
||||||
|
import Kernel, except: [-: 2]
|
||||||
|
|
||||||
|
assert expand(quote do: << x :: 13 >>) ==
|
||||||
|
quote do: << x() :: size(13) >>
|
||||||
|
|
||||||
|
assert expand(quote do: << x :: 13 * 6 >>) ==
|
||||||
|
quote do: << x() :: unit(6)-size(13) >>
|
||||||
|
|
||||||
|
assert expand(quote do: << x :: _ * 6 >>) ==
|
||||||
|
quote do: << x() :: unit(6) >>
|
||||||
|
|
||||||
|
assert expand(quote do: << x :: 13 * 6 - binary >>) ==
|
||||||
|
quote do: << x() :: unit(6)-binary()-size(13) >>
|
||||||
|
|
||||||
|
assert expand(quote do: << x :: binary - 13 * 6 >>) ==
|
||||||
|
quote do: << x() :: binary()-unit(6)-size(13) >>
|
||||||
|
end
|
||||||
|
|
||||||
test "bitstrings: expands modifiers" do
|
test "bitstrings: expands modifiers" do
|
||||||
assert expand(quote do: (import Kernel.ExpansionTarget; << x :: seventeen >>)) ==
|
assert expand(quote do: (import Kernel.ExpansionTarget; << x :: seventeen >>)) ==
|
||||||
quote do: (import :"Elixir.Kernel.ExpansionTarget", []; << x() :: [unquote(:size)(17)] >>)
|
quote do: (import :"Elixir.Kernel.ExpansionTarget", []; << x() :: size(17) >>)
|
||||||
|
|
||||||
assert expand(quote do: (import Kernel.ExpansionTarget; << seventeen :: seventeen, x :: size(seventeen) >> = 1)) ==
|
assert expand(quote do: (import Kernel.ExpansionTarget; << seventeen :: seventeen, x :: size(seventeen) >> = 1)) ==
|
||||||
quote do: (import :"Elixir.Kernel.ExpansionTarget", [];
|
quote do: (import :"Elixir.Kernel.ExpansionTarget", [];
|
||||||
<< seventeen :: [unquote(:size)(17)], x :: [unquote(:size)(seventeen)] >> = 1)
|
<< seventeen :: size(17), x :: size(seventeen) >> = 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "bitstrings: expands modifiers args" do
|
test "bitstrings: expands modifiers args" do
|
||||||
assert expand(quote do: (require Kernel.ExpansionTarget; << x :: size(Kernel.ExpansionTarget.seventeen) >>)) ==
|
assert expand(quote do: (require Kernel.ExpansionTarget; << x :: size(Kernel.ExpansionTarget.seventeen) >>)) ==
|
||||||
quote do: (require :"Elixir.Kernel.ExpansionTarget", []; << x() :: [unquote(:size)(17)] >>)
|
quote do: (require :"Elixir.Kernel.ExpansionTarget", []; << x() :: size(17) >>)
|
||||||
end
|
end
|
||||||
|
|
||||||
## Invalid
|
## Invalid
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ defmodule Kernel.QuoteTest do
|
|||||||
|
|
||||||
test :keep_line do
|
test :keep_line do
|
||||||
## DO NOT MOVE THIS LINE
|
## DO NOT MOVE THIS LINE
|
||||||
assert quote(location: :keep, do: bar(1, 2, 3)) == {:bar, [keep: 16], [1, 2, 3]}
|
assert quote(location: :keep, do: bar(1, 2, 3)) ==
|
||||||
|
{:bar, [file: Path.relative_to_cwd(__ENV__.file), keep: 16], [1, 2, 3]}
|
||||||
end
|
end
|
||||||
|
|
||||||
test :fixed_line do
|
test :fixed_line do
|
||||||
@@ -23,7 +24,7 @@ defmodule Kernel.QuoteTest do
|
|||||||
test :quote_line_var do
|
test :quote_line_var do
|
||||||
## DO NOT MOVE THIS LINE
|
## DO NOT MOVE THIS LINE
|
||||||
line = __ENV__.line
|
line = __ENV__.line
|
||||||
assert quote(line: line, do: bar(1, 2, 3)) == {:bar, [line: 25], [1, 2, 3]}
|
assert quote(line: line, do: bar(1, 2, 3)) == {:bar, [line: 26], [1, 2, 3]}
|
||||||
end
|
end
|
||||||
|
|
||||||
test :unquote_call do
|
test :unquote_call do
|
||||||
@@ -201,7 +202,7 @@ defmodule Kernel.QuoteTest.ErrorsTest do
|
|||||||
|
|
||||||
mod = Kernel.QuoteTest.ErrorsTest
|
mod = Kernel.QuoteTest.ErrorsTest
|
||||||
file = __ENV__.file |> Path.relative_to_cwd |> String.to_char_list
|
file = __ENV__.file |> Path.relative_to_cwd |> String.to_char_list
|
||||||
assert [{^mod, :add, 2, [file: ^file, line: 181]}|_] = System.stacktrace
|
assert [{^mod, :add, 2, [file: ^file, line: 182]}|_] = System.stacktrace
|
||||||
end
|
end
|
||||||
|
|
||||||
test :outside_function_error do
|
test :outside_function_error do
|
||||||
@@ -211,7 +212,7 @@ defmodule Kernel.QuoteTest.ErrorsTest do
|
|||||||
|
|
||||||
mod = Kernel.QuoteTest.ErrorsTest
|
mod = Kernel.QuoteTest.ErrorsTest
|
||||||
file = __ENV__.file |> Path.relative_to_cwd |> String.to_char_list
|
file = __ENV__.file |> Path.relative_to_cwd |> String.to_char_list
|
||||||
assert [{^mod, _, _, [file: ^file, line: 209]}|_] = System.stacktrace
|
assert [{^mod, _, _, [file: ^file, line: 210]}|_] = System.stacktrace
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ defmodule Kernel.SigilsTest do
|
|||||||
assert ~S/foo/ == "foo"
|
assert ~S/foo/ == "foo"
|
||||||
assert ~S|foo| == "foo"
|
assert ~S|foo| == "foo"
|
||||||
assert ~S(f#{o}o) == "f\#{o}o"
|
assert ~S(f#{o}o) == "f\#{o}o"
|
||||||
|
assert ~S(f\#{o}o) == "f\\\#{o}o"
|
||||||
assert ~S(f\no) == "f\\no"
|
assert ~S(f\no) == "f\\no"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ Code.require_file "../test_helper.exs", __DIR__
|
|||||||
defmodule Kernel.TypespecTest do
|
defmodule Kernel.TypespecTest do
|
||||||
use ExUnit.Case, async: true
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
alias Kernel.TypespecTest.TestTypespec
|
||||||
|
|
||||||
|
defstruct [:hello]
|
||||||
|
|
||||||
# This macro allows us to focus on the result of the
|
# This macro allows us to focus on the result of the
|
||||||
# definition and not on the hassles of handling test
|
# definition and not on the hassles of handling test
|
||||||
# module
|
# module
|
||||||
@@ -174,28 +178,86 @@ defmodule Kernel.TypespecTest do
|
|||||||
|
|
||||||
test "@type with a struct" do
|
test "@type with a struct" do
|
||||||
module = test_module do
|
module = test_module do
|
||||||
@type mytype :: %User{hello: :world}
|
defstruct [hello: nil, eric: nil]
|
||||||
|
@type mytype :: %TestTypespec{hello: :world}
|
||||||
end
|
end
|
||||||
|
|
||||||
assert [type: {:mytype,
|
assert [type: {:mytype,
|
||||||
{:type, _, :map, [
|
{:type, _, :map, [
|
||||||
{:type, _, :map_field_assoc, {:atom, _, :__struct__}, {:atom, _, User}},
|
{:type, _, :map_field_assoc, {:atom, _, :__struct__}, {:atom, _, TestTypespec}},
|
||||||
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}}
|
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}},
|
||||||
|
{:type, _, :map_field_assoc, {:atom, _, :eric}, {:type, _, :term, []}}
|
||||||
]},
|
]},
|
||||||
[]}] = types(module)
|
[]}] = types(module)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "@type with a tuple" do
|
test "@type with undefined struct" do
|
||||||
module = test_module do
|
assert_raise UndefinedFunctionError, fn ->
|
||||||
@type mytype :: tuple
|
test_module do
|
||||||
@type mytype1 :: {}
|
@type mytype :: %ThisModuleDoesNotExist{}
|
||||||
@type mytype2 :: {1, 2}
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
assert [type: {:mytype, {:type, _, :tuple, :any}, []},
|
assert_raise CompileError, ~r"struct is not defined for TestTypespec", fn ->
|
||||||
type: {:mytype1, {:type, _, :tuple, []}, []},
|
test_module do
|
||||||
type: {:mytype2, {:type, _, :tuple, [{:integer, _, 1}, {:integer, _, 2}]}, []}] =
|
@type mytype :: %TestTypespec{}
|
||||||
types(module)
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "@type with a struct with undefined field" do
|
||||||
|
assert_raise CompileError, ~r"undefined field no_field on struct TestTypespec", fn ->
|
||||||
|
test_module do
|
||||||
|
defstruct [hello: nil, eric: nil]
|
||||||
|
@type mytype :: %TestTypespec{no_field: :world}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "@type with public record" do
|
||||||
|
module = test_module do
|
||||||
|
require Record
|
||||||
|
Record.defrecord :timestamp, [date: 1, time: 2]
|
||||||
|
@type mytype :: record(:timestamp, time: :foo)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert [type: {:mytype,
|
||||||
|
{:type, _, :tuple, [
|
||||||
|
{:atom, 0, :timestamp}, {:atom, 0, :foo}, {:type, 0, :term, []}
|
||||||
|
]},
|
||||||
|
[]}] = types(module)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "@type with private record" do
|
||||||
|
module = test_module do
|
||||||
|
require Record
|
||||||
|
Record.defrecordp :timestamp, [date: 1, time: 2]
|
||||||
|
@type mytype :: record(:timestamp, time: :foo)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert [type: {:mytype,
|
||||||
|
{:type, _, :tuple, [
|
||||||
|
{:atom, 0, :timestamp}, {:atom, 0, :foo}, {:type, 0, :term, []}
|
||||||
|
]},
|
||||||
|
[]}] = types(module)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "@type with undefined record" do
|
||||||
|
assert_raise CompileError, ~r"unknown record :this_record_does_not_exist", fn ->
|
||||||
|
test_module do
|
||||||
|
@type mytype :: record(:this_record_does_not_exist, [])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "@type with a record with undefined field" do
|
||||||
|
assert_raise CompileError, ~r"undefined field no_field on record :timestamp", fn ->
|
||||||
|
test_module do
|
||||||
|
require Record
|
||||||
|
Record.defrecord :timestamp, [date: 1, time: 2]
|
||||||
|
@type mytype :: record(:timestamp, no_field: :foo)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "@type with list shortcuts" do
|
test "@type with list shortcuts" do
|
||||||
@@ -320,31 +382,6 @@ defmodule Kernel.TypespecTest do
|
|||||||
types(module)
|
types(module)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "@type from structs" do
|
|
||||||
module = test_module do
|
|
||||||
defstruct name: nil, age: 0 :: non_neg_integer
|
|
||||||
end
|
|
||||||
|
|
||||||
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, _, TestTypespec}}
|
|
||||||
]}, []}] = types(module)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "@type from dynamic structs" do
|
|
||||||
module = test_module do
|
|
||||||
fields = [name: nil, age: 0]
|
|
||||||
defstruct fields
|
|
||||||
end
|
|
||||||
|
|
||||||
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, _, TestTypespec}}
|
|
||||||
]}, []}] = types(module)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "@type unquote fragment" do
|
test "@type unquote fragment" do
|
||||||
module = test_module do
|
module = test_module do
|
||||||
quoted = quote unquote: false do
|
quoted = quote unquote: false do
|
||||||
@@ -473,11 +510,16 @@ defmodule Kernel.TypespecTest do
|
|||||||
(quote do: @type opts() :: [first: integer(), step: integer(), last: integer()]),
|
(quote do: @type opts() :: [first: integer(), step: integer(), last: integer()]),
|
||||||
(quote do: @type ops() :: {+1,-1}),
|
(quote do: @type ops() :: {+1,-1}),
|
||||||
(quote do: @type my_map() :: %{hello: :world}),
|
(quote do: @type my_map() :: %{hello: :world}),
|
||||||
(quote do: @type my_struct() :: %User{hello: :world}),
|
(quote do: @type my_struct() :: %Kernel.TypespecTest{hello: :world}),
|
||||||
|
(quote do: @type list1() :: list()),
|
||||||
|
(quote do: @type list2() :: [0]),
|
||||||
|
(quote do: @type list3() :: [...]),
|
||||||
|
(quote do: @type list4() :: [0, ...]),
|
||||||
|
(quote do: @type nil_list() :: []),
|
||||||
] |> Enum.sort
|
] |> Enum.sort
|
||||||
|
|
||||||
module = test_module do
|
module = test_module do
|
||||||
Module.eval_quoted __MODULE__, quote do: (unquote_splicing(quoted))
|
Module.eval_quoted __MODULE__, quoted
|
||||||
end
|
end
|
||||||
|
|
||||||
types = types(module)
|
types = types(module)
|
||||||
|
|||||||
@@ -20,6 +20,16 @@ defmodule Kernel.WarningTest do
|
|||||||
purge Sample
|
purge Sample
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test :underscored_variable_on_match do
|
||||||
|
assert capture_err(fn ->
|
||||||
|
Code.eval_string """
|
||||||
|
{_arg, _arg} = {1, 1}
|
||||||
|
"""
|
||||||
|
end) =~ "warning: the underscored variable \"_arg\" appears more than once in a match"
|
||||||
|
after
|
||||||
|
purge Sample
|
||||||
|
end
|
||||||
|
|
||||||
test :unused_function do
|
test :unused_function do
|
||||||
assert capture_err(fn ->
|
assert capture_err(fn ->
|
||||||
Code.eval_string """
|
Code.eval_string """
|
||||||
@@ -195,7 +205,7 @@ defmodule Kernel.WarningTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
"""
|
"""
|
||||||
end) =~ "nofile:5: warning: the guard for this clause evaluates to 'false'"
|
end) =~ "nofile:5: warning: this check/guard will always yield the same result"
|
||||||
|
|
||||||
assert capture_err(fn ->
|
assert capture_err(fn ->
|
||||||
Code.eval_string """
|
Code.eval_string """
|
||||||
@@ -221,7 +231,7 @@ defmodule Kernel.WarningTest do
|
|||||||
def hello
|
def hello
|
||||||
end
|
end
|
||||||
"""
|
"""
|
||||||
end) =~ "warning: empty clause provided for nonexistent function or macro hello/0"
|
end) =~ "warning: bodyless clause provided for nonexistent def hello/0"
|
||||||
after
|
after
|
||||||
purge [Sample1]
|
purge [Sample1]
|
||||||
end
|
end
|
||||||
@@ -347,7 +357,7 @@ defmodule Kernel.WarningTest do
|
|||||||
def a(x) when x in [], do: x
|
def a(x) when x in [], do: x
|
||||||
end
|
end
|
||||||
"""
|
"""
|
||||||
end) =~ "warning: the guard for this clause evaluates to 'false'"
|
end) =~ "nofile:2: warning: this check/guard will always yield the same result"
|
||||||
after
|
after
|
||||||
purge Sample
|
purge Sample
|
||||||
end
|
end
|
||||||
@@ -482,6 +492,18 @@ defmodule Kernel.WarningTest do
|
|||||||
purge [Sample]
|
purge [Sample]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test :doc_with_no_function do
|
||||||
|
assert capture_err(fn ->
|
||||||
|
Code.eval_string """
|
||||||
|
defmodule Sample do
|
||||||
|
@doc "Something"
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
end) =~ "nofile:1: warning: @doc provided but no definition follows it"
|
||||||
|
after
|
||||||
|
purge [Sample]
|
||||||
|
end
|
||||||
|
|
||||||
defp purge(list) when is_list(list) do
|
defp purge(list) when is_list(list) do
|
||||||
Enum.each list, &purge/1
|
Enum.each list, &purge/1
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ defmodule KernelTest do
|
|||||||
dynamic = :dynamic_flatten
|
dynamic = :dynamic_flatten
|
||||||
defdelegate unquote(dynamic)(list), to: List, as: :flatten
|
defdelegate unquote(dynamic)(list), to: List, as: :flatten
|
||||||
|
|
||||||
test "defdelefate/2" do
|
test "defdelegate/2" do
|
||||||
assert my_flatten([[1]]) == [1]
|
assert my_flatten([[1]]) == [1]
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -191,6 +191,11 @@ defmodule KernelTest do
|
|||||||
assert get_in(users, ["dave", :age]) == nil
|
assert get_in(users, ["dave", :age]) == nil
|
||||||
assert get_in(nil, ["josé", :age]) == nil
|
assert get_in(nil, ["josé", :age]) == nil
|
||||||
|
|
||||||
|
map = %{"fruits" => ["banana", "apple", "orange"]}
|
||||||
|
assert get_in(map, ["fruits", by_index(0)]) == "banana"
|
||||||
|
assert get_in(map, ["fruits", by_index(3)]) == nil
|
||||||
|
assert get_in(map, ["unknown", by_index(3)]) == :oops
|
||||||
|
|
||||||
assert_raise FunctionClauseError, fn ->
|
assert_raise FunctionClauseError, fn ->
|
||||||
get_in(users, [])
|
get_in(users, [])
|
||||||
end
|
end
|
||||||
@@ -199,18 +204,16 @@ defmodule KernelTest do
|
|||||||
test "put_in/3" do
|
test "put_in/3" do
|
||||||
users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||||
|
|
||||||
assert put_in(nil, ["josé", :age], 28) ==
|
|
||||||
%{"josé" => %{age: 28}}
|
|
||||||
|
|
||||||
assert put_in(users, ["josé", :age], 28) ==
|
assert put_in(users, ["josé", :age], 28) ==
|
||||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||||
|
|
||||||
assert put_in(users, ["dave", :age], 19) ==
|
|
||||||
%{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 19}}
|
|
||||||
|
|
||||||
assert_raise FunctionClauseError, fn ->
|
assert_raise FunctionClauseError, fn ->
|
||||||
put_in(users, [], %{})
|
put_in(users, [], %{})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
assert_raise ArgumentError, "could not put/update key \"josé\" on a nil value", fn ->
|
||||||
|
put_in(nil, ["josé", :age], 28)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "put_in/2" do
|
test "put_in/2" do
|
||||||
@@ -222,14 +225,10 @@ defmodule KernelTest do
|
|||||||
assert put_in(users["josé"][:age], 28) ==
|
assert put_in(users["josé"][:age], 28) ==
|
||||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||||
|
|
||||||
assert put_in(users["dave"][:age], 19) ==
|
|
||||||
%{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 19}}
|
|
||||||
|
|
||||||
|
|
||||||
assert put_in(users["josé"].age, 28) ==
|
assert put_in(users["josé"].age, 28) ==
|
||||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||||
|
|
||||||
assert_raise ArgumentError, fn ->
|
assert_raise ArgumentError, "could not put/update key :age. Expected map/struct, got: nil", fn ->
|
||||||
put_in(users["dave"].age, 19)
|
put_in(users["dave"].age, 19)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -241,36 +240,28 @@ defmodule KernelTest do
|
|||||||
test "update_in/3" do
|
test "update_in/3" do
|
||||||
users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||||
|
|
||||||
assert update_in(nil, ["josé", :age], fn nil -> 28 end) ==
|
|
||||||
%{"josé" => %{age: 28}}
|
|
||||||
|
|
||||||
assert update_in(users, ["josé", :age], &(&1 + 1)) ==
|
assert update_in(users, ["josé", :age], &(&1 + 1)) ==
|
||||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||||
|
|
||||||
assert update_in(users, ["dave", :age], fn nil -> 19 end) ==
|
|
||||||
%{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 19}}
|
|
||||||
|
|
||||||
assert_raise FunctionClauseError, fn ->
|
assert_raise FunctionClauseError, fn ->
|
||||||
update_in(users, [], fn _ -> %{} end)
|
update_in(users, [], fn _ -> %{} end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
assert_raise ArgumentError, "could not put/update key \"josé\" on a nil value", fn ->
|
||||||
|
update_in(nil, ["josé", :age], fn _ -> %{} end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "update_in/2" do
|
test "update_in/2" do
|
||||||
users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||||
|
|
||||||
assert update_in(nil["josé"][:age], fn nil -> 28 end) ==
|
|
||||||
%{"josé" => %{age: 28}}
|
|
||||||
|
|
||||||
assert update_in(users["josé"][:age], &(&1 + 1)) ==
|
assert update_in(users["josé"][:age], &(&1 + 1)) ==
|
||||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||||
|
|
||||||
assert update_in(users["dave"][:age], fn nil -> 19 end) ==
|
|
||||||
%{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 19}}
|
|
||||||
|
|
||||||
assert update_in(users["josé"].age, &(&1 + 1)) ==
|
assert update_in(users["josé"].age, &(&1 + 1)) ==
|
||||||
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
|
||||||
|
|
||||||
assert_raise ArgumentError, fn ->
|
assert_raise ArgumentError, "could not put/update key :age. Expected map/struct, got: nil", fn ->
|
||||||
update_in(users["dave"].age, &(&1 + 1))
|
update_in(users["dave"].age, &(&1 + 1))
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -282,12 +273,19 @@ defmodule KernelTest do
|
|||||||
test "get_and_update_in/3" do
|
test "get_and_update_in/3" do
|
||||||
users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
|
||||||
|
|
||||||
assert get_and_update_in(nil, ["josé", :age], fn nil -> {:ok, 28} end) ==
|
|
||||||
{:ok, %{"josé" => %{age: 28}}}
|
|
||||||
|
|
||||||
assert get_and_update_in(users, ["josé", :age], &{&1, &1 + 1}) ==
|
assert get_and_update_in(users, ["josé", :age], &{&1, &1 + 1}) ==
|
||||||
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}}
|
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}}
|
||||||
|
|
||||||
|
map = %{"fruits" => ["banana", "apple", "orange"]}
|
||||||
|
assert get_and_update_in(map, ["fruits", by_index(0)], &{&1, String.reverse(&1)}) ==
|
||||||
|
{"banana", %{"fruits" => ["ananab", "apple", "orange"]}}
|
||||||
|
|
||||||
|
assert get_and_update_in(map, ["fruits", by_index(3)], &{&1, &1}) ==
|
||||||
|
{nil, %{"fruits" => ["banana", "apple", "orange"]}}
|
||||||
|
|
||||||
|
assert get_and_update_in(map, ["unknown", by_index(3)], &{&1, []}) ==
|
||||||
|
{:oops, %{"fruits" => ["banana", "apple", "orange"], "unknown" => []}}
|
||||||
|
|
||||||
assert_raise FunctionClauseError, fn ->
|
assert_raise FunctionClauseError, fn ->
|
||||||
update_in(users, [], fn _ -> %{} end)
|
update_in(users, [], fn _ -> %{} end)
|
||||||
end
|
end
|
||||||
@@ -302,7 +300,7 @@ defmodule KernelTest do
|
|||||||
assert get_and_update_in(users["josé"].age, &{&1, &1 + 1}) ==
|
assert get_and_update_in(users["josé"].age, &{&1, &1 + 1}) ==
|
||||||
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}}
|
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}}
|
||||||
|
|
||||||
assert_raise ArgumentError, fn ->
|
assert_raise ArgumentError, "could not put/update key :age. Expected map/struct, got: nil", fn ->
|
||||||
get_and_update_in(users["dave"].age, &{&1, &1 + 1})
|
get_and_update_in(users["dave"].age, &{&1, &1 + 1})
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -319,7 +317,7 @@ defmodule KernelTest do
|
|||||||
assert put_in(KernelTest.empty_map()[:foo], "bar") == %{foo: "bar"}
|
assert put_in(KernelTest.empty_map()[:foo], "bar") == %{foo: "bar"}
|
||||||
assert put_in(__MODULE__.empty_map()[:foo], "bar") == %{foo: "bar"}
|
assert put_in(__MODULE__.empty_map()[:foo], "bar") == %{foo: "bar"}
|
||||||
|
|
||||||
assert_raise ArgumentError, ~r"access at least one field,", fn ->
|
assert_raise ArgumentError, ~r"access at least one element,", fn ->
|
||||||
Code.eval_quoted(quote(do: put_in(map, "bar")), [])
|
Code.eval_quoted(quote(do: put_in(map, "bar")), [])
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -330,6 +328,18 @@ defmodule KernelTest do
|
|||||||
|
|
||||||
def empty_map, do: %{}
|
def empty_map, do: %{}
|
||||||
|
|
||||||
|
def by_index(index) do
|
||||||
|
fn
|
||||||
|
_, nil, next ->
|
||||||
|
next.(:oops)
|
||||||
|
:get, data, next ->
|
||||||
|
next.(Enum.at(data, index))
|
||||||
|
:get_and_update, data, next ->
|
||||||
|
{get, update} = next.(Enum.at(data, index))
|
||||||
|
{get, List.replace_at(data, index, update)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defmodule PipelineOp do
|
defmodule PipelineOp do
|
||||||
use ExUnit.Case, async: true
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
@@ -349,12 +359,9 @@ defmodule KernelTest do
|
|||||||
assert Enum.map([1, 2, 3], &(&1 |> twice |> twice)) == [4, 8, 12]
|
assert Enum.map([1, 2, 3], &(&1 |> twice |> twice)) == [4, 8, 12]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "non-call" do
|
test "anonymous functions" do
|
||||||
assert 1 |> (&(&1*2)).() == 2
|
assert 1 |> (&(&1*2)).() == 2
|
||||||
assert [1] |> (&hd(&1)).() == 1
|
assert [1] |> (&hd(&1)).() == 1
|
||||||
|
|
||||||
import CompileAssertion
|
|
||||||
assert_compile_fail ArgumentError, "cannot pipe 1 into 2", "1 |> 2"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp twice(a), do: a * 2
|
defp twice(a), do: a * 2
|
||||||
|
|||||||
@@ -275,6 +275,16 @@ defmodule MacroTest do
|
|||||||
assert Macro.to_string(quote do: Foo.Bar.baz([1, 2, 3])) == "Foo.Bar.baz([1, 2, 3])"
|
assert Macro.to_string(quote do: Foo.Bar.baz([1, 2, 3])) == "Foo.Bar.baz([1, 2, 3])"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test :sigil_call_to_string do
|
||||||
|
assert Macro.to_string(quote do: ~r"123") == ~s/~r"123"/
|
||||||
|
assert Macro.to_string(quote do: ~r"123"u) == ~s/~r"123"u/
|
||||||
|
assert Macro.to_string(quote do: ~r"\n123") == ~s/~r"\\\\n123"/
|
||||||
|
|
||||||
|
assert Macro.to_string(quote do: ~R"123") == ~s/~R"123"/
|
||||||
|
assert Macro.to_string(quote do: ~R"123"u) == ~s/~R"123"u/
|
||||||
|
assert Macro.to_string(quote do: ~R"\n123") == ~s/~R"\\\\n123"/
|
||||||
|
end
|
||||||
|
|
||||||
test :arrow_to_string do
|
test :arrow_to_string do
|
||||||
assert Macro.to_string(quote do: foo(1, (2 -> 3))) == "foo(1, (2 -> 3))"
|
assert Macro.to_string(quote do: foo(1, (2 -> 3))) == "foo(1, (2 -> 3))"
|
||||||
end
|
end
|
||||||
@@ -479,7 +489,7 @@ defmodule MacroTest do
|
|||||||
assert Macro.pipe(1, quote(do: foo), -1) == quote(do: foo(1))
|
assert Macro.pipe(1, quote(do: foo), -1) == quote(do: foo(1))
|
||||||
assert Macro.pipe(2, quote(do: foo(1)), -1) == quote(do: foo(1, 2))
|
assert Macro.pipe(2, quote(do: foo(1)), -1) == quote(do: foo(1, 2))
|
||||||
|
|
||||||
assert_raise ArgumentError, "cannot pipe 1 into 2", fn ->
|
assert_raise ArgumentError, ~r"cannot pipe 1 into 2", fn ->
|
||||||
Macro.pipe(1, 2, 0)
|
Macro.pipe(1, 2, 0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -54,6 +54,19 @@ defmodule Module.LocalsTrackerTest do
|
|||||||
assert {:private, 1} in D.reachable(config[:pid])
|
assert {:private, 1} in D.reachable(config[:pid])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "can yank and reattach nodes", config do
|
||||||
|
D.add_definition(config[:pid], :def, {:foo, 1})
|
||||||
|
D.add_local(config[:pid], {:foo, 1}, {:bar, 1})
|
||||||
|
D.add_definition(config[:pid], :defp, {:bar, 1})
|
||||||
|
|
||||||
|
{infoo, outfoo} = D.yank(config[:pid], {:foo, 1})
|
||||||
|
{inbar, outbar} = D.yank(config[:pid], {:bar, 1})
|
||||||
|
|
||||||
|
D.reattach(config[:pid], :defp, {:bar, 1}, {inbar, outbar})
|
||||||
|
D.reattach(config[:pid], :def, {:foo, 1}, {infoo, outfoo})
|
||||||
|
assert {:bar, 1} in D.reachable(config[:pid])
|
||||||
|
end
|
||||||
|
|
||||||
@unused [
|
@unused [
|
||||||
{{:private, 1}, :defp, 0}
|
{{:private, 1}, :defp, 0}
|
||||||
]
|
]
|
||||||
@@ -62,11 +75,11 @@ defmodule Module.LocalsTrackerTest do
|
|||||||
D.add_definition(config[:pid], :def, {:public, 1})
|
D.add_definition(config[:pid], :def, {:public, 1})
|
||||||
|
|
||||||
unused = D.collect_unused_locals(config[:pid], @unused)
|
unused = D.collect_unused_locals(config[:pid], @unused)
|
||||||
assert unused == [{:unused_def, {:private, 1}, :defp}]
|
assert unused == {[private: 1], [{:unused_def, {:private, 1}, :defp}]}
|
||||||
|
|
||||||
D.add_local(config[:pid], {:public, 1}, {:private, 1})
|
D.add_local(config[:pid], {:public, 1}, {:private, 1})
|
||||||
unused = D.collect_unused_locals(config[:pid], @unused)
|
unused = D.collect_unused_locals(config[:pid], @unused)
|
||||||
refute unused == [{:unused_def, {:private, 1}, :defp}]
|
assert unused == {[], []}
|
||||||
end
|
end
|
||||||
|
|
||||||
@unused [
|
@unused [
|
||||||
@@ -77,25 +90,25 @@ defmodule Module.LocalsTrackerTest do
|
|||||||
D.add_definition(config[:pid], :def, {:public, 1})
|
D.add_definition(config[:pid], :def, {:public, 1})
|
||||||
|
|
||||||
unused = D.collect_unused_locals(config[:pid], @unused)
|
unused = D.collect_unused_locals(config[:pid], @unused)
|
||||||
assert unused == [{:unused_def, {:private, 3}, :defp}]
|
assert unused == {[private: 3], [{:unused_def, {:private, 3}, :defp}]}
|
||||||
|
|
||||||
D.add_local(config[:pid], {:public, 1}, {:private, 3})
|
D.add_local(config[:pid], {:public, 1}, {:private, 3})
|
||||||
unused = D.collect_unused_locals(config[:pid], @unused)
|
unused = D.collect_unused_locals(config[:pid], @unused)
|
||||||
assert unused == [{:unused_args, {:private, 3}}]
|
assert unused == {[], [unused_args: {:private, 3}]}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "private definitions with some unused default arguments", config do
|
test "private definitions with some unused default arguments", config do
|
||||||
D.add_definition(config[:pid], :def, {:public, 1})
|
D.add_definition(config[:pid], :def, {:public, 1})
|
||||||
D.add_local(config[:pid], {:public, 1}, {:private, 1})
|
D.add_local(config[:pid], {:public, 1}, {:private, 1})
|
||||||
unused = D.collect_unused_locals(config[:pid], @unused)
|
unused = D.collect_unused_locals(config[:pid], @unused)
|
||||||
assert unused == [{:unused_args, {:private, 3}, 1}]
|
assert unused == {[private: 3], [{:unused_args, {:private, 3}, 1}]}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "private definitions with all used default arguments", config do
|
test "private definitions with all used default arguments", config do
|
||||||
D.add_definition(config[:pid], :def, {:public, 1})
|
D.add_definition(config[:pid], :def, {:public, 1})
|
||||||
D.add_local(config[:pid], {:public, 1}, {:private, 0})
|
D.add_local(config[:pid], {:public, 1}, {:private, 0})
|
||||||
unused = D.collect_unused_locals(config[:pid], @unused)
|
unused = D.collect_unused_locals(config[:pid], @unused)
|
||||||
assert unused == []
|
assert unused == {[private: 3], []}
|
||||||
end
|
end
|
||||||
|
|
||||||
## Defaults
|
## Defaults
|
||||||
@@ -147,4 +160,15 @@ defmodule Module.LocalsTrackerTest do
|
|||||||
D.add_import(config[:pid], {:foo, 2}, Module, {:conflict, 1})
|
D.add_import(config[:pid], {:foo, 2}, Module, {:conflict, 1})
|
||||||
assert {[Module], :conflict, 1} in D.collect_imports_conflicts(config[:pid], [conflict: 1])
|
assert {[Module], :conflict, 1} in D.collect_imports_conflicts(config[:pid], [conflict: 1])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defmodule NoPrivate do
|
||||||
|
defmacrop foo(), do: bar()
|
||||||
|
defp bar(), do: :baz
|
||||||
|
def baz(), do: foo()
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not include unreachable locals" do
|
||||||
|
assert NoPrivate.module_info(:functions) ==
|
||||||
|
[__info__: 1, baz: 0, module_info: 0, module_info: 1]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ defmodule OptionParserTest do
|
|||||||
assert OptionParser.parse(["--no-bool"], strict: [])
|
assert OptionParser.parse(["--no-bool"], strict: [])
|
||||||
== {[], [], [{"--no-bool", nil}]}
|
== {[], [], [{"--no-bool", nil}]}
|
||||||
assert OptionParser.parse(["--no-bool=...", "other"])
|
assert OptionParser.parse(["--no-bool=...", "other"])
|
||||||
== {[], ["other"], [{"--no-bool", "..."}]}
|
== {[no_bool: "..."], ["other"], []}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "does not parse -- as an alias" do
|
test "does not parse -- as an alias" do
|
||||||
@@ -103,7 +103,7 @@ defmodule OptionParserTest do
|
|||||||
assert OptionParser.parse(["--value"], switches: [value: :string])
|
assert OptionParser.parse(["--value"], switches: [value: :string])
|
||||||
== {[], [], [{"--value", nil}]}
|
== {[], [], [{"--value", nil}]}
|
||||||
assert OptionParser.parse(["--no-value"], switches: [value: :string])
|
assert OptionParser.parse(["--no-value"], switches: [value: :string])
|
||||||
== {[], [], [{"--no-value", nil}]}
|
== {[no_value: true], [], []}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "parses configured integers" do
|
test "parses configured integers" do
|
||||||
@@ -134,11 +134,6 @@ defmodule OptionParserTest do
|
|||||||
== {[], ["foo"], [{"--value", "WAT"}]}
|
== {[], ["foo"], [{"--value", "WAT"}]}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "parses no switches as flags" do
|
|
||||||
assert OptionParser.parse(["--no-docs", "foo"])
|
|
||||||
== {[no_docs: true], ["foo"], []}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "overrides options by default" do
|
test "overrides options by default" do
|
||||||
assert OptionParser.parse(["--require", "foo", "--require", "bar", "baz"])
|
assert OptionParser.parse(["--require", "foo", "--require", "bar", "baz"])
|
||||||
== {[require: "bar"], ["baz"], []}
|
== {[require: "bar"], ["baz"], []}
|
||||||
@@ -157,17 +152,17 @@ defmodule OptionParserTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "stops on --" do
|
test "stops on --" do
|
||||||
options = OptionParser.parse(["--source", "from_docs/", "--", "1", "2", "3"])
|
options = OptionParser.parse(["--source", "foo", "--", "1", "2", "3"])
|
||||||
assert options == {[source: "from_docs/"], ["1", "2", "3"], []}
|
assert options == {[source: "foo"], ["1", "2", "3"], []}
|
||||||
|
|
||||||
options = OptionParser.parse_head(["--source", "from_docs/", "--", "1", "2", "3"])
|
options = OptionParser.parse_head(["--source", "foo", "--", "1", "2", "3"])
|
||||||
assert options == {[source: "from_docs/"], ["1", "2", "3"], []}
|
assert options == {[source: "foo"], ["1", "2", "3"], []}
|
||||||
|
|
||||||
options = OptionParser.parse(["--no-dash", "foo", "bar", "--", "-x"])
|
options = OptionParser.parse(["--source", "foo", "bar", "--", "-x"])
|
||||||
assert options == {[no_dash: true], ["foo", "bar", "-x"], []}
|
assert options == {[source: "foo"], ["bar", "-x"], []}
|
||||||
|
|
||||||
options = OptionParser.parse_head(["--no-dash", "foo", "bar", "--", "-x"])
|
options = OptionParser.parse_head(["--source", "foo", "bar", "--", "-x"])
|
||||||
assert options == {[no_dash: true], ["foo", "bar", "--", "-x"], []}
|
assert options == {[source: "foo"], ["bar", "--", "-x"], []}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "goes beyond the first non option arguments" do
|
test "goes beyond the first non option arguments" do
|
||||||
@@ -288,6 +283,35 @@ defmodule OptionParserTest do
|
|||||||
assert OptionParser.next(["--bool=", "..."], config)
|
assert OptionParser.next(["--bool=", "..."], config)
|
||||||
== {:invalid, "--bool", "", ["..."]}
|
== {:invalid, "--bool", "", ["..."]}
|
||||||
assert OptionParser.next(["--no-bool=", "..."], config)
|
assert OptionParser.next(["--no-bool=", "..."], config)
|
||||||
== {:undefined, "--no-bool", "", ["..."]}
|
== {:invalid, "--no-bool", "", ["..."]}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "split" do
|
||||||
|
assert OptionParser.split(~S[])
|
||||||
|
== []
|
||||||
|
|
||||||
|
assert OptionParser.split(~S[foo])
|
||||||
|
== ["foo"]
|
||||||
|
|
||||||
|
assert OptionParser.split(~S[foo bar])
|
||||||
|
== ["foo", "bar"]
|
||||||
|
|
||||||
|
assert OptionParser.split(~S[ foo bar ])
|
||||||
|
== ["foo", "bar"]
|
||||||
|
|
||||||
|
assert OptionParser.split(~S[foo\ bar])
|
||||||
|
== ["foo bar"]
|
||||||
|
|
||||||
|
assert OptionParser.split(~S[foo" bar"])
|
||||||
|
== ["foo bar"]
|
||||||
|
|
||||||
|
assert OptionParser.split(~S[foo\" bar\"])
|
||||||
|
== ["foo\"", "bar\""]
|
||||||
|
|
||||||
|
assert OptionParser.split(~S[foo "\ bar\""])
|
||||||
|
== ["foo", "\\ bar\""]
|
||||||
|
|
||||||
|
assert OptionParser.split(~S[foo '\"bar"\'\ '])
|
||||||
|
== ["foo", "\\\"bar\"'\\ "]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ defmodule PathTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
if is_win? do
|
if is_win? do
|
||||||
test :relative do
|
test :relative_win do
|
||||||
assert Path.relative("C:/usr/local/bin") == "usr/local/bin"
|
assert Path.relative("C:/usr/local/bin") == "usr/local/bin"
|
||||||
assert Path.relative("C:\\usr\\local\\bin") == "usr\\local\\bin"
|
assert Path.relative("C:\\usr\\local\\bin") == "usr\\local\\bin"
|
||||||
assert Path.relative("C:usr\\local\\bin") == "usr\\local\\bin"
|
assert Path.relative("C:usr\\local\\bin") == "usr\\local\\bin"
|
||||||
@@ -40,7 +40,7 @@ defmodule PathTest do
|
|||||||
assert Path.relative("../usr/local/bin") == "../usr/local/bin"
|
assert Path.relative("../usr/local/bin") == "../usr/local/bin"
|
||||||
end
|
end
|
||||||
|
|
||||||
test :type do
|
test :type_win do
|
||||||
assert Path.type("C:/usr/local/bin") == :absolute
|
assert Path.type("C:/usr/local/bin") == :absolute
|
||||||
assert Path.type('C:\\usr\\local\\bin') == :absolute
|
assert Path.type('C:\\usr\\local\\bin') == :absolute
|
||||||
assert Path.type("C:usr\\local\\bin") == :volumerelative
|
assert Path.type("C:usr\\local\\bin") == :volumerelative
|
||||||
@@ -49,15 +49,20 @@ defmodule PathTest do
|
|||||||
assert Path.type('usr/local/bin') == :relative
|
assert Path.type('usr/local/bin') == :relative
|
||||||
assert Path.type("../usr/local/bin") == :relative
|
assert Path.type("../usr/local/bin") == :relative
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test :split_win do
|
||||||
|
assert Path.split("C:\\foo\\bar") == ["c:/", "foo", "bar"]
|
||||||
|
assert Path.split("C:/foo/bar") == ["c:/", "foo", "bar"]
|
||||||
|
end
|
||||||
else
|
else
|
||||||
test :relative do
|
test :relative_unix do
|
||||||
assert Path.relative("/usr/local/bin") == "usr/local/bin"
|
assert Path.relative("/usr/local/bin") == "usr/local/bin"
|
||||||
assert Path.relative("usr/local/bin") == "usr/local/bin"
|
assert Path.relative("usr/local/bin") == "usr/local/bin"
|
||||||
assert Path.relative("../usr/local/bin") == "../usr/local/bin"
|
assert Path.relative("../usr/local/bin") == "../usr/local/bin"
|
||||||
assert Path.relative(['/usr', ?/, "local/bin"]) == "usr/local/bin"
|
assert Path.relative(['/usr', ?/, "local/bin"]) == "usr/local/bin"
|
||||||
end
|
end
|
||||||
|
|
||||||
test :type do
|
test :type_unix do
|
||||||
assert Path.type("/usr/local/bin") == :absolute
|
assert Path.type("/usr/local/bin") == :absolute
|
||||||
assert Path.type("usr/local/bin") == :relative
|
assert Path.type("usr/local/bin") == :relative
|
||||||
assert Path.type("../usr/local/bin") == :relative
|
assert Path.type("../usr/local/bin") == :relative
|
||||||
@@ -201,7 +206,7 @@ defmodule PathTest do
|
|||||||
assert Path.join([?/, "foo"], "./bar") == "/foo/bar"
|
assert Path.join([?/, "foo"], "./bar") == "/foo/bar"
|
||||||
end
|
end
|
||||||
|
|
||||||
test :split_with_binary do
|
test :split do
|
||||||
assert Path.split("") == []
|
assert Path.split("") == []
|
||||||
assert Path.split("foo") == ["foo"]
|
assert Path.split("foo") == ["foo"]
|
||||||
assert Path.split("/foo/bar") == ["/", "foo", "bar"]
|
assert Path.split("/foo/bar") == ["/", "foo", "bar"]
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ defmodule RecordTest do
|
|||||||
|
|
||||||
user(name: name) = record
|
user(name: name) = record
|
||||||
assert name == "Eric"
|
assert name == "Eric"
|
||||||
|
|
||||||
|
assert user(:name) == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
test "records with no tag" do
|
test "records with no tag" do
|
||||||
@@ -76,6 +78,16 @@ defmodule RecordTest do
|
|||||||
test "records with dynamic arguments" do
|
test "records with dynamic arguments" do
|
||||||
record = file_info()
|
record = file_info()
|
||||||
assert file_info(record, :size) == :undefined
|
assert file_info(record, :size) == :undefined
|
||||||
|
|
||||||
|
record = user()
|
||||||
|
assert user(record) == [name: "José", age: 25]
|
||||||
|
assert user(user()) == [name: "José", age: 25]
|
||||||
|
|
||||||
|
msg = "expected argument to be a literal atom, literal keyword or a :file_info record, " <>
|
||||||
|
"got runtime: {RecordTest, \"José\", 25}"
|
||||||
|
assert_raise ArgumentError, msg, fn ->
|
||||||
|
file_info(record)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "records visibility" do
|
test "records visibility" do
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ defmodule RegexTest do
|
|||||||
assert Regex.regex?(regex)
|
assert Regex.regex?(regex)
|
||||||
assert {:error, _} = Regex.compile("*foo")
|
assert {:error, _} = Regex.compile("*foo")
|
||||||
assert {:error, _} = Regex.compile("foo", "y")
|
assert {:error, _} = Regex.compile("foo", "y")
|
||||||
|
assert {:error, _} = Regex.compile("foo", "uy")
|
||||||
end
|
end
|
||||||
|
|
||||||
test :compile_with_erl_opts do
|
test :compile_with_erl_opts do
|
||||||
@@ -80,6 +81,10 @@ defmodule RegexTest do
|
|||||||
assert "josé" =~ ~r"\p{Latin}$"u
|
assert "josé" =~ ~r"\p{Latin}$"u
|
||||||
refute "£" =~ ~r/\p{Lu}/u
|
refute "£" =~ ~r/\p{Lu}/u
|
||||||
|
|
||||||
|
# Non breaking space matches [[:space:]] with unicode
|
||||||
|
assert <<0xA0::utf8>> =~ ~r/[[:space:]]/u
|
||||||
|
assert <<0xA0::utf8>> =~ ~r/\s/u
|
||||||
|
|
||||||
assert <<?<, 255, ?>>> =~ ~r/<.>/
|
assert <<?<, 255, ?>>> =~ ~r/<.>/
|
||||||
refute <<?<, 255, ?>>> =~ ~r/<.>/u
|
refute <<?<, 255, ?>>> =~ ~r/<.>/u
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -282,6 +282,16 @@ defmodule StreamTest do
|
|||||||
assert Process.get(:stream_flat_map)
|
assert Process.get(:stream_flat_map)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "flat_map/2 with inner flat_map/2" do
|
||||||
|
stream = Stream.flat_map(1..5, fn x ->
|
||||||
|
Stream.flat_map([x], fn x ->
|
||||||
|
x .. x * x
|
||||||
|
end) |> Stream.map(& &1 * 1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert Enum.take(stream, 5) == [1, 2, 3, 4, 3]
|
||||||
|
end
|
||||||
|
|
||||||
test "into/2 and run/1" do
|
test "into/2 and run/1" do
|
||||||
Process.put(:stream_cont, [])
|
Process.put(:stream_cont, [])
|
||||||
Process.put(:stream_done, false)
|
Process.put(:stream_done, false)
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ defmodule String.Chars.ErrorsTest do
|
|||||||
assert_raise Protocol.UndefinedError,
|
assert_raise Protocol.UndefinedError,
|
||||||
"protocol String.Chars not implemented for <<0, 1::size(4)>>, " <>
|
"protocol String.Chars not implemented for <<0, 1::size(4)>>, " <>
|
||||||
"cannot convert a bitstring to a string", fn ->
|
"cannot convert a bitstring to a string", fn ->
|
||||||
to_string(<<1 :: [size(12), integer, signed]>>)
|
to_string(<<1 :: size(12)-integer-signed>>)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,20 +4,20 @@ defmodule SystemTest do
|
|||||||
use ExUnit.Case
|
use ExUnit.Case
|
||||||
import PathHelpers
|
import PathHelpers
|
||||||
|
|
||||||
test "build_info" do
|
test "build_info/0" do
|
||||||
assert is_map System.build_info
|
assert is_map System.build_info
|
||||||
assert not nil?(System.build_info[:version])
|
assert not nil?(System.build_info[:version])
|
||||||
assert not nil?(System.build_info[:tag])
|
assert not nil?(System.build_info[:tag])
|
||||||
assert not nil?(System.build_info[:date])
|
assert not nil?(System.build_info[:date])
|
||||||
end
|
end
|
||||||
|
|
||||||
test "cwd" do
|
test "cwd/0" do
|
||||||
assert is_binary System.cwd
|
assert is_binary System.cwd
|
||||||
assert is_binary System.cwd!
|
assert is_binary System.cwd!
|
||||||
end
|
end
|
||||||
|
|
||||||
if :file.native_name_encoding == :utf8 do
|
if :file.native_name_encoding == :utf8 do
|
||||||
test "cwd_with_utf8" do
|
test "cwd/0 with utf8" do
|
||||||
File.mkdir_p(tmp_path("héllò"))
|
File.mkdir_p(tmp_path("héllò"))
|
||||||
|
|
||||||
File.cd!(tmp_path("héllò"), fn ->
|
File.cd!(tmp_path("héllò"), fn ->
|
||||||
@@ -28,17 +28,17 @@ defmodule SystemTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "user_home" do
|
test "user_home/0" do
|
||||||
assert is_binary System.user_home
|
assert is_binary System.user_home
|
||||||
assert is_binary System.user_home!
|
assert is_binary System.user_home!
|
||||||
end
|
end
|
||||||
|
|
||||||
test "tmp_dir" do
|
test "tmp_dir/0" do
|
||||||
assert is_binary System.tmp_dir
|
assert is_binary System.tmp_dir
|
||||||
assert is_binary System.tmp_dir!
|
assert is_binary System.tmp_dir!
|
||||||
end
|
end
|
||||||
|
|
||||||
test "argv" do
|
test "argv/0" do
|
||||||
list = elixir('-e "IO.inspect System.argv" -- -o opt arg1 arg2 --long-opt 10')
|
list = elixir('-e "IO.inspect System.argv" -- -o opt arg1 arg2 --long-opt 10')
|
||||||
{args, _} = Code.eval_string list, []
|
{args, _} = Code.eval_string list, []
|
||||||
assert args == ["-o", "opt", "arg1", "arg2", "--long-opt", "10"]
|
assert args == ["-o", "opt", "arg1", "arg2", "--long-opt", "10"]
|
||||||
@@ -46,7 +46,7 @@ defmodule SystemTest do
|
|||||||
|
|
||||||
@test_var "SYSTEM_ELIXIR_ENV_TEST_VAR"
|
@test_var "SYSTEM_ELIXIR_ENV_TEST_VAR"
|
||||||
|
|
||||||
test "env" do
|
test "*_env/*" do
|
||||||
assert System.get_env(@test_var) == nil
|
assert System.get_env(@test_var) == nil
|
||||||
System.put_env(@test_var, "SAMPLE")
|
System.put_env(@test_var, "SAMPLE")
|
||||||
assert System.get_env(@test_var) == "SAMPLE"
|
assert System.get_env(@test_var) == "SAMPLE"
|
||||||
@@ -59,20 +59,17 @@ defmodule SystemTest do
|
|||||||
assert System.get_env(@test_var) == "OTHER_SAMPLE"
|
assert System.get_env(@test_var) == "OTHER_SAMPLE"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "cmd" do
|
test "cmd/1" do
|
||||||
assert is_binary(System.cmd "echo hello")
|
assert {"hello\n", 0} = System.cmd "echo", ["hello"]
|
||||||
assert is_list(System.cmd 'echo hello')
|
|
||||||
|
opts = [into: [], cd: System.cwd!, env: %{"foo" => "bar"},
|
||||||
|
arg0: "hecho", stderr_to_stdout: true, parallelism: true]
|
||||||
|
assert {["hello\n"], 0} = System.cmd "echo", ["hello"], opts
|
||||||
end
|
end
|
||||||
|
|
||||||
test "find_executable with binary" do
|
test "find_executable/1" do
|
||||||
assert System.find_executable("erl")
|
assert System.find_executable("erl")
|
||||||
assert is_binary System.find_executable("erl")
|
assert is_binary System.find_executable("erl")
|
||||||
assert !System.find_executable("does-not-really-exist-from-elixir")
|
assert !System.find_executable("does-not-really-exist-from-elixir")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "find_executable with list" do
|
|
||||||
assert System.find_executable('erl')
|
|
||||||
assert is_list System.find_executable('erl')
|
|
||||||
assert !System.find_executable('does-not-really-exist-from-elixir')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -253,13 +253,19 @@ oror_test() ->
|
|||||||
end,
|
end,
|
||||||
test_helper:run_and_remove(F, ['Elixir.Bar']).
|
test_helper:run_and_remove(F, ['Elixir.Bar']).
|
||||||
|
|
||||||
|
cond_line_test() ->
|
||||||
|
{'case',1,_,
|
||||||
|
[{clause,2,_,_,_},
|
||||||
|
{clause,3,_,_,_}]
|
||||||
|
} = to_erl("cond do\n 1 -> :ok\n 2 -> :ok\nend").
|
||||||
|
|
||||||
% Optimized
|
% Optimized
|
||||||
|
|
||||||
optimized_if_test() ->
|
optimized_if_test() ->
|
||||||
{'case', _, _,
|
{'case', _, _,
|
||||||
[{clause,_,[{atom,_,false}],[],[{atom,_,else}]},
|
[{clause,_,[{atom,_,false}],[],[{atom,_,else}]},
|
||||||
{clause,_,[{atom,_,true}],[],[{atom,_,do}]}]
|
{clause,_,[{atom,_,true}],[],[{atom,_,do}]}]
|
||||||
} = to_erl("if is_list([]), do: :do, else: :else").
|
} = to_erl("if is_list([]), do: :do, else: :else").
|
||||||
|
|
||||||
optimized_andand_test() ->
|
optimized_andand_test() ->
|
||||||
{'case', _, _,
|
{'case', _, _,
|
||||||
@@ -268,7 +274,7 @@ optimized_andand_test() ->
|
|||||||
[[{op,_,'orelse',_,_}]],
|
[[{op,_,'orelse',_,_}]],
|
||||||
[{var,_,Var}]},
|
[{var,_,Var}]},
|
||||||
{clause,_,[{var,_,'_'}],[],[{atom,0,done}]}]
|
{clause,_,[{var,_,'_'}],[],[{atom,0,done}]}]
|
||||||
} = to_erl("is_list([]) && :done").
|
} = to_erl("is_list([]) && :done").
|
||||||
|
|
||||||
optimized_oror_test() ->
|
optimized_oror_test() ->
|
||||||
{'case', _, _,
|
{'case', _, _,
|
||||||
@@ -277,7 +283,7 @@ optimized_oror_test() ->
|
|||||||
[[{op,1,'orelse',_,_}]],
|
[[{op,1,'orelse',_,_}]],
|
||||||
[{atom,0,done}]},
|
[{atom,0,done}]},
|
||||||
{clause,1,[{var,1,Var}],[],[{var,1,Var}]}]
|
{clause,1,[{var,1,Var}],[],[{var,1,Var}]}]
|
||||||
} = to_erl("is_list([]) || :done").
|
} = to_erl("is_list([]) || :done").
|
||||||
|
|
||||||
no_after_in_try_test() ->
|
no_after_in_try_test() ->
|
||||||
{'try', _, [_], [_], _, []} = to_erl("try do :foo.bar() else _ -> :ok end").
|
{'try', _, [_], [_], _, []} = to_erl("try do :foo.bar() else _ -> :ok end").
|
||||||
+22
-11
@@ -74,11 +74,18 @@ defmodule ExUnit do
|
|||||||
* `:tags` - the test tags
|
* `:tags` - the test tags
|
||||||
|
|
||||||
"""
|
"""
|
||||||
defstruct name: nil :: atom,
|
defstruct name: nil,
|
||||||
case: nil :: module,
|
case: nil,
|
||||||
state: nil :: ExUnit.state,
|
state: nil,
|
||||||
time: 0 :: non_neg_integer,
|
time: 0,
|
||||||
tags: %{} :: map
|
tags: %{}
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{
|
||||||
|
name: atom,
|
||||||
|
case: module,
|
||||||
|
state: ExUnit.state,
|
||||||
|
time: non_neg_integer,
|
||||||
|
tags: map}
|
||||||
end
|
end
|
||||||
|
|
||||||
defmodule TestCase do
|
defmodule TestCase do
|
||||||
@@ -92,9 +99,14 @@ defmodule ExUnit do
|
|||||||
* `:tests` - all tests for this case
|
* `:tests` - all tests for this case
|
||||||
|
|
||||||
"""
|
"""
|
||||||
defstruct name: nil :: module,
|
defstruct name: nil,
|
||||||
state: nil :: ExUnit.state,
|
state: nil,
|
||||||
tests: [] :: [ExUnit.Test.t]
|
tests: []
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{
|
||||||
|
name: module,
|
||||||
|
state: ExUnit.state,
|
||||||
|
tests: [ExUnit.Test.t]}
|
||||||
end
|
end
|
||||||
|
|
||||||
use Application
|
use Application
|
||||||
@@ -120,8 +132,7 @@ defmodule ExUnit do
|
|||||||
If you want to run tests manually, you can set `:autorun` to `false`.
|
If you want to run tests manually, you can set `:autorun` to `false`.
|
||||||
"""
|
"""
|
||||||
def start(options \\ []) do
|
def start(options \\ []) do
|
||||||
Application.start(:elixir)
|
{:ok, _} = Application.ensure_all_started(:ex_unit)
|
||||||
Application.start(:ex_unit)
|
|
||||||
|
|
||||||
configure(options)
|
configure(options)
|
||||||
|
|
||||||
@@ -132,7 +143,7 @@ defmodule ExUnit do
|
|||||||
0 ->
|
0 ->
|
||||||
%{failures: failures} = ExUnit.run
|
%{failures: failures} = ExUnit.run
|
||||||
System.at_exit fn _ ->
|
System.at_exit fn _ ->
|
||||||
if failures > 0, do: System.halt(1)
|
if failures > 0, do: exit({:shutdown, 1})
|
||||||
end
|
end
|
||||||
_ ->
|
_ ->
|
||||||
:ok
|
:ok
|
||||||
|
|||||||
@@ -339,6 +339,13 @@ defmodule ExUnit.DocTest do
|
|||||||
|
|
||||||
defp extract(module) do
|
defp extract(module) do
|
||||||
all_docs = Code.get_docs(module, :all)
|
all_docs = Code.get_docs(module, :all)
|
||||||
|
|
||||||
|
unless all_docs do
|
||||||
|
raise Error, message:
|
||||||
|
"could not retrieve the documentation for module #{inspect module}. " <>
|
||||||
|
"The module was not compiled with documentation or its beam file cannot be accessed"
|
||||||
|
end
|
||||||
|
|
||||||
moduledocs = extract_from_moduledoc(all_docs[:moduledoc])
|
moduledocs = extract_from_moduledoc(all_docs[:moduledoc])
|
||||||
|
|
||||||
docs = for doc <- all_docs[:docs] do
|
docs = for doc <- all_docs[:docs] do
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ defmodule ExUnit.Runner do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp shuffle(%{seed: seed}, list) do
|
defp shuffle(%{seed: seed}, list) do
|
||||||
:random.seed(3172, 9814, seed)
|
_ = :random.seed(3172, 9814, seed)
|
||||||
Enum.shuffle(list)
|
Enum.shuffle(list)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user