Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a613445f78 | ||
|
|
e4ea0a6e36 | ||
|
|
a03639fe1a | ||
|
|
e7b1242e98 | ||
|
|
d3d83b17f4 | ||
|
|
78eb14edae | ||
|
|
a7d931ce21 | ||
|
|
b1cd1ee7e9 | ||
|
|
1513ca7c14 | ||
|
|
016480bdcc | ||
|
|
857af06aff | ||
|
|
961099746e | ||
|
|
8247382967 | ||
|
|
9b0d972571 | ||
|
|
b19c389cee | ||
|
|
2243235e08 | ||
|
|
a72457112f | ||
|
|
3f3e86d7f4 | ||
|
|
15bf0fb16e | ||
|
|
5184edd432 | ||
|
|
fc9506e28e | ||
|
|
5c01be91ea | ||
|
|
fed406a2e6 | ||
|
|
26904197d5 | ||
|
|
f552d7620f | ||
|
|
ce55936b45 | ||
|
|
3167d78711 | ||
|
|
05eb42612b | ||
|
|
6720522707 | ||
|
|
0520b138f8 | ||
|
|
0ddb31992b | ||
|
|
4a1dfccbec | ||
|
|
c62a7e1001 | ||
|
|
2b8854c63e | ||
|
|
b535f0746e | ||
|
|
4b795a042b | ||
|
|
c80e52ee39 | ||
|
|
520415b013 | ||
|
|
27682320d4 | ||
|
|
98ade3b8fb | ||
|
|
263c76f5e5 | ||
|
|
bd984a545d | ||
|
|
115e022025 | ||
|
|
dc5fe48639 | ||
|
|
155b937327 | ||
|
|
6ef10d4b0d | ||
|
|
4f2c2ceb95 | ||
|
|
078522d014 | ||
|
|
c765a95b91 | ||
|
|
8836405bc5 | ||
|
|
dd2e0f6064 | ||
|
|
23982f1744 | ||
|
|
a6f04a2224 | ||
|
|
7291abb26f | ||
|
|
55cb8ab436 | ||
|
|
32cddf2738 | ||
|
|
7893852f2f | ||
|
|
f1b625ccd2 | ||
|
|
caa7d41e96 | ||
|
|
b96a714240 | ||
|
|
d39a52ae79 | ||
|
|
3e66197318 | ||
|
|
065b7f3f8f | ||
|
|
6004a3614f | ||
|
|
22627c44df | ||
|
|
6e959733dd | ||
|
|
7d2d352236 | ||
|
|
9d61e5886c | ||
|
|
37c8867d9f | ||
|
|
da7e830bf7 | ||
|
|
c78d3b3717 | ||
|
|
cafbd2c2d4 | ||
|
|
ae8def380a | ||
|
|
37cb1801b5 | ||
|
|
dd43e799c8 | ||
|
|
e93d4197ef | ||
|
|
d55a1703bd | ||
|
|
87268e9c78 | ||
|
|
f0ecfbfac9 | ||
|
|
854cab51bc | ||
|
|
ffdf9e9a35 | ||
|
|
6d74511e92 | ||
|
|
75a1373bbb | ||
|
|
d26e4b3dbc | ||
|
|
d636dc0ff0 | ||
|
|
ed0b161992 | ||
|
|
5e640336e0 | ||
|
|
99c7d84f9c | ||
|
|
893a52a95a | ||
|
|
a8d2e45d2f | ||
|
|
ecd3c19c64 | ||
|
|
7f9a2ed17b | ||
|
|
7951f73925 | ||
|
|
a79d965248 | ||
|
|
958550c5cd | ||
|
|
997a798994 | ||
|
|
c0547a15ce | ||
|
|
3918b889f1 | ||
|
|
d1fd5a3c2a | ||
|
|
a3aa1e8de5 | ||
|
|
7c379aa50a | ||
|
|
1fa29be448 | ||
|
|
b9a60b2668 | ||
|
|
ab9735533e | ||
|
|
6b2b928867 | ||
|
|
4b1ea18625 | ||
|
|
7cd98e149c | ||
|
|
036d016053 | ||
|
|
be2aedff98 | ||
|
|
e19d666a14 | ||
|
|
9840ac266b | ||
|
|
bcfc927455 | ||
|
|
014a2ec7a8 | ||
|
|
4846d42101 | ||
|
|
636da42f2a | ||
|
|
7c4516f1ec | ||
|
|
65cc78034d | ||
|
|
f97a384b82 | ||
|
|
7e856e1af0 | ||
|
|
893e333204 | ||
|
|
5e94a69693 | ||
|
|
5775d49c08 | ||
|
|
6397cedec6 | ||
|
|
3dd445bcce | ||
|
|
5e803487ef | ||
|
|
caeea8973c | ||
|
|
6656f7c843 | ||
|
|
e7a872c975 | ||
|
|
ea797815a0 | ||
|
|
453d8835bd | ||
|
|
2272dd1c46 | ||
|
|
41b5f4c826 | ||
|
|
ac0ac7f024 | ||
|
|
5ff3b760d9 | ||
|
|
fe97c4886a | ||
|
|
0b45b61de8 | ||
|
|
406edcb07d | ||
|
|
24c6623a2b | ||
|
|
5ffec637b0 | ||
|
|
bd1633c778 | ||
|
|
bcf9d814dd | ||
|
|
69a4138e8c | ||
|
|
0fa06a2183 | ||
|
|
6c813a140d | ||
|
|
dfc40703f0 | ||
|
|
e208237669 | ||
|
|
3ec014ff77 | ||
|
|
c19e3d0446 | ||
|
|
a459e35537 | ||
|
|
bbaf6b4092 | ||
|
|
99cdb412e3 | ||
|
|
4ce444ad76 | ||
|
|
f06f15184c | ||
|
|
da4565ee88 | ||
|
|
636275c064 | ||
|
|
6137d55d10 | ||
|
|
2fbc8682ca | ||
|
|
2075a9b07a | ||
|
|
abb261443b | ||
|
|
41b0ae4b37 | ||
|
|
4d793f6db1 | ||
|
|
b2d0355ef9 | ||
|
|
c82a8ee0fa | ||
|
|
7044f9c9da | ||
|
|
ec8327967a | ||
|
|
5aa5268c5b | ||
|
|
619f345423 | ||
|
|
99913d7018 | ||
|
|
fce97507de | ||
|
|
3f5e6deb07 | ||
|
|
3c9f486567 | ||
|
|
846048855f | ||
|
|
fed8796d18 | ||
|
|
9a6259d333 | ||
|
|
8c8e8b1193 | ||
|
|
dad7f7bfb7 | ||
|
|
3aaaf5e916 | ||
|
|
a6b81bffd1 | ||
|
|
a9d4cdb7d7 | ||
|
|
64c58f2a3f | ||
|
|
9e56691274 | ||
|
|
3f86e6d555 | ||
|
|
339082053e | ||
|
|
0cbf50c53a | ||
|
|
1d87a778ef | ||
|
|
3b5e06763d | ||
|
|
a7eab9b426 | ||
|
|
1211ed7f0f | ||
|
|
2a08277ef1 | ||
|
|
df94927e09 | ||
|
|
3cd6e54fe9 | ||
|
|
336b9cf126 | ||
|
|
d19f6beca0 | ||
|
|
da84a797b0 | ||
|
|
69df6ea9f9 | ||
|
|
b1fa7d1834 | ||
|
|
6a0244bd88 | ||
|
|
f37e5c6246 | ||
|
|
96a431945d | ||
|
|
fefad03595 | ||
|
|
f76aeba141 | ||
|
|
fbb06f2086 | ||
|
|
ffc91e3004 | ||
|
|
32cc99767b | ||
|
|
c3e7543e48 | ||
|
|
c7e86b0554 | ||
|
|
99ce9250b5 | ||
|
|
adf4075f36 | ||
|
|
3c4b6a636e | ||
|
|
239c41bee2 | ||
|
|
478f9bbb52 | ||
|
|
c3dd4006b0 | ||
|
|
e05448afd7 | ||
|
|
55bc80b4e5 | ||
|
|
ce1b12422d | ||
|
|
bbcc640760 | ||
|
|
e08bcbeef6 | ||
|
|
d757f31ccd | ||
|
|
9a0f8107bd | ||
|
|
d4609c6f4e | ||
|
|
f29c6afe3d | ||
|
|
df51fbe74e | ||
|
|
e6ff35c180 | ||
|
|
c20fe6f396 | ||
|
|
86e7069a0a | ||
|
|
334f3ce78a | ||
|
|
0da2b590ba | ||
|
|
1133581d9e | ||
|
|
d9a10847a6 | ||
|
|
d159e4f592 | ||
|
|
8530694c76 | ||
|
|
a916cf47b4 | ||
|
|
3e9b61855c | ||
|
|
9f862be4eb | ||
|
|
d2c5fa3ad4 | ||
|
|
a8d9ad50d9 | ||
|
|
ffb1863d41 | ||
|
|
156ead2c57 | ||
|
|
7ea8faeada | ||
|
|
82655b61c7 | ||
|
|
088753d901 | ||
|
|
7632c0b17e | ||
|
|
7d594a1953 | ||
|
|
042f5e9c14 | ||
|
|
3864b04ac2 | ||
|
|
5fac52bf12 | ||
|
|
63890ef0b7 | ||
|
|
0dcd5501f0 | ||
|
|
08e9faabe8 | ||
|
|
e5f9dbde1a | ||
|
|
f5b0b7160c | ||
|
|
d0ca92ad4e | ||
|
|
ca25049feb | ||
|
|
76a0a64725 | ||
|
|
c6e64aba18 | ||
|
|
231d65e879 | ||
|
|
91e8e21242 | ||
|
|
d3013aa1d1 | ||
|
|
2c98c0999d | ||
|
|
94f9d4e3f6 | ||
|
|
66476b31d1 | ||
|
|
7f84211bb3 | ||
|
|
474250e6f4 | ||
|
|
65e67cdc27 | ||
|
|
6b91e46702 | ||
|
|
c83f136ad4 | ||
|
|
59a9ddb7d5 | ||
|
|
1ef67c570e | ||
|
|
2403d0c4b9 | ||
|
|
a1ec5f1552 | ||
|
|
45485fba59 | ||
|
|
97ea102a79 | ||
|
|
6e8fb6c630 | ||
|
|
854942d3ee | ||
|
|
d99ec5addf | ||
|
|
3f946d4af0 | ||
|
|
9318f747f4 | ||
|
|
e216f18010 | ||
|
|
ed14ce4bc8 | ||
|
|
0cb936a600 | ||
|
|
328541be87 | ||
|
|
85b76843b0 | ||
|
|
f688899b95 | ||
|
|
e3fb2a5acf | ||
|
|
5a99fbfdcd | ||
|
|
c634a5d232 | ||
|
|
9a6ac4f76c | ||
|
|
6af7e0cc6b | ||
|
|
7ff3e1f833 | ||
|
|
d6d630316e | ||
|
|
4714b54222 | ||
|
|
4d51bd64f0 | ||
|
|
fc1945acff | ||
|
|
5c0ddbb063 | ||
|
|
880ae1116d | ||
|
|
68923112cc | ||
|
|
865608b5de | ||
|
|
26640e3940 |
@@ -14,3 +14,4 @@ erl_crash.dump
|
||||
.dialyzer_plt
|
||||
.dialyzer.base_plt
|
||||
.*.swp
|
||||
docs
|
||||
|
||||
@@ -7,7 +7,3 @@ notifications:
|
||||
- yrashk@gmail.com
|
||||
otp_release:
|
||||
- R16B
|
||||
- R15B03
|
||||
- R15B02
|
||||
- R15B01
|
||||
- R15B
|
||||
|
||||
@@ -1,3 +1,57 @@
|
||||
# v0.9.2 (2013-06-13)
|
||||
|
||||
* enhancements
|
||||
* [ExUnit] `capture_io` now captures prompt by default
|
||||
* [Mix] Automatically import git dependencies from Rebar
|
||||
* [Mix] Support for dependencies directly from the umbrella application
|
||||
* [Regex] Add `Regex.escape`
|
||||
* [String] Add `String.contains?`
|
||||
* [URI] Implement `Binary.Chars` (aka `to_binary`) for `URI.Info`
|
||||
|
||||
* bug fix
|
||||
* [HashDict] Ensure HashDict uses exact match throughout its implementation
|
||||
* [IEx] Do not interpret ANSI codes in IEx results
|
||||
* [IEx] Ensure --cookie is set before accessing remote shell
|
||||
* [Kernel] Do not ignore nil when dispatching protocols to avoid infinite loops
|
||||
* [Mix] Fix usage of shell expressions in `Mix.Shell.cmd`
|
||||
* [Mix] Start the application by default on escripts
|
||||
|
||||
* deprecations
|
||||
* [Regex] `Regex.index/2` is deprecated in favor `Regex.run/3`
|
||||
* [Kernel] `super` no longer supports implicit arguments
|
||||
|
||||
* backwards incompatible changes
|
||||
* [Kernel] The `=~` operator now returns true or false instead of an index
|
||||
|
||||
# v0.9.1 (2013-05-30)
|
||||
|
||||
* enhancements
|
||||
* [IEx] Limit the number of entries kept in history and allow it to be configured
|
||||
* [Kernel] Add `String.start_with?` and `String.end_with?`
|
||||
* [Typespec] Allow keywords, e.g. `[foo: integer, bar: boolean | module]`, in typespecs
|
||||
|
||||
* bug fix
|
||||
* [Dict] `Enum.to_list` and `Dict.to_list` now return the same results for dicts
|
||||
* [IEx] Enable shell customization via the `IEx.Options` module
|
||||
* [Kernel] Fix a bug where `unquote_splicing` did not work on the left side of a stab op
|
||||
* [Kernel] Unused functions with cyclic dependencies are now also warned as unused
|
||||
* [Mix] Fix a bug where `mix deps.get` was not retrieving nested dependencies
|
||||
* [Record] Fix a bug where nested records cannot be defined
|
||||
* [Record] Fix a bug where a record named Record cannot be defined
|
||||
|
||||
# v0.9.0 (2013-05-23)
|
||||
|
||||
* enhancements
|
||||
* [ExUnit] `ExUnit.CaptureIO` now accepts an input to be used during capture
|
||||
* [IEx] Add support for .iex files that are loaded during shell's boot process
|
||||
* [IEx] Add `import_file/1` helper
|
||||
|
||||
* backwards incompatible changes
|
||||
* [Enum] `Enum.Iterator` was replaced by the more composable and functional `Enumerable` protocol which supports reductions
|
||||
* [File] `File.iterator/1` and `File.biniterator/1` have been removed in favor of the safe `File.iterator!/1` and `File.biniterator!/1` ones
|
||||
* [Kernel] Erlang R15 is no longer supported
|
||||
* [Kernel] Elixir modules are now represented as `Elixir.ModuleName` (using `.` instead of `-` as separator)
|
||||
|
||||
# v0.8.3 (2013-05-22)
|
||||
|
||||
* enhancements
|
||||
|
||||
+29
-14
@@ -9,7 +9,7 @@ The issue tracker is the preferred channel for [bug reports](#bugs-reports),
|
||||
[features requests](#feature-requests) and [submitting pull
|
||||
requests](#pull-requests), but please **do not** use the issue tracker for
|
||||
personal support requests. Instead, use
|
||||
[the mailing list](http://groups.google.com/group/elixir-lang-core),
|
||||
[the mailing list](http://groups.google.com/group/elixir-lang-talk),
|
||||
[Stack Overflow](http://stackoverflow.com/questions/ask?tags=elixir), or
|
||||
[#elixir-lang](irc://chat.freenode.net/elixir-lang) on Freenode).
|
||||
|
||||
@@ -95,8 +95,8 @@ Good pull requests - patches, improvements, new features - are a fantastic
|
||||
help. They should remain focused in scope and avoid containing unrelated
|
||||
commits.
|
||||
|
||||
**IMPORTANT**: By submitting a patch, you agree to allow the project owner to
|
||||
license your work under the same license as that used by the project.
|
||||
**IMPORTANT**: By submitting a patch, you agree that your work will be made
|
||||
public forever and will be licensed under the license used by the project.
|
||||
|
||||
If you have any significant pull request in mind (e.g. implementing features,
|
||||
refactoring code, porting to a different language), **please ask first**
|
||||
@@ -123,11 +123,16 @@ in order to craft an excellent pull request:
|
||||
2. If you cloned a while ago, get the latest changes from upstream:
|
||||
|
||||
```bash
|
||||
git checkout <dev-branch>
|
||||
git pull upstream <dev-branch>
|
||||
git checkout master
|
||||
git pull upstream master
|
||||
```
|
||||
|
||||
3. Create a new topic branch (off master) to contain your feature, change, or fix:
|
||||
3. Create a new topic branch (off of `master`) to contain your feature, change,
|
||||
or fix.
|
||||
|
||||
**IMPORTANT**: Making changes in `master` is discouraged. You should always
|
||||
keep your local `master` in sync with upstream `master` and make your
|
||||
changes in topic branches.
|
||||
|
||||
```bash
|
||||
git checkout -b <topic-branch-name>
|
||||
@@ -139,21 +144,29 @@ in order to craft an excellent pull request:
|
||||
[interactive rebase](https://help.github.com/articles/interactive-rebase)
|
||||
feature to tidy up your commits before making them public.
|
||||
|
||||
5. Locally merge (or rebase) the upstream development branch into your topic branch:
|
||||
|
||||
```bash
|
||||
git pull [--rebase] upstream <dev-branch>
|
||||
```
|
||||
|
||||
6. Push your topic branch up to your fork:
|
||||
5. Push your topic branch up to your fork:
|
||||
|
||||
```bash
|
||||
git push origin <topic-branch-name>
|
||||
```
|
||||
|
||||
7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
|
||||
6. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
|
||||
with a clear title and description.
|
||||
|
||||
7. If you haven't updated your pull request for a while, you should consider
|
||||
rebasing on master and resolving any conflicts.
|
||||
|
||||
**IMPORTANT**: _Never ever_ merge upstream `master` into your branches. You
|
||||
should always `git rebase` on `master` to bring your changes up to date when
|
||||
necessary.
|
||||
|
||||
```bash
|
||||
git checkout master
|
||||
git pull upstream master
|
||||
git checkout <your-topic-branch>
|
||||
git rebase master
|
||||
```
|
||||
|
||||
We have saved some excellent pull requests we have received in the past in case
|
||||
you are looking for some examples:
|
||||
|
||||
@@ -161,3 +174,5 @@ you are looking for some examples:
|
||||
* https://github.com/elixir-lang/elixir/pull/1041
|
||||
* https://github.com/elixir-lang/elixir/pull/1058
|
||||
* https://github.com/elixir-lang/elixir/pull/1059
|
||||
|
||||
Thank you for your contributions!
|
||||
|
||||
@@ -11,12 +11,12 @@ INSTALL_PATH := /usr/local
|
||||
#==> Templates
|
||||
|
||||
define APP_TEMPLATE
|
||||
$(1): lib/$(1)/ebin/Elixir-$(2).beam lib/$(1)/ebin/$(1).app
|
||||
$(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
|
||||
|
||||
lib/$(1)/ebin/$(1).app:
|
||||
@ cd lib/$(1) && ../../bin/elixir -e "Mix.Server.start_link(:dev)" -r mix.exs -e "Mix.Task.run('compile.app')"
|
||||
|
||||
lib/$(1)/ebin/Elixir-$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
|
||||
lib/$(1)/ebin/Elixir.$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1)/lib/*/*.ex) $(wildcard lib/$(1)/lib/*/*/*.ex)
|
||||
@ echo "==> $(1) (compile)"
|
||||
@ $$(ELIXIRC) "lib/$(1)/lib/**/*.ex" -o lib/$(1)/ebin
|
||||
|
||||
@@ -27,8 +27,8 @@ endef
|
||||
|
||||
#==> Compilation tasks
|
||||
|
||||
KERNEL:=lib/elixir/ebin/Elixir-Kernel.beam
|
||||
UNICODE:=lib/elixir/ebin/Elixir-String-Unicode.beam
|
||||
KERNEL:=lib/elixir/ebin/Elixir.Kernel.beam
|
||||
UNICODE:=lib/elixir/ebin/Elixir.String.Unicode.beam
|
||||
|
||||
default: compile
|
||||
|
||||
@@ -44,7 +44,7 @@ erlang:
|
||||
# Since Mix depends on EEx and EEx depends on
|
||||
# Mix, we first compile EEx without the .app
|
||||
# file, then mix and then compile eex fully
|
||||
elixir: kernel lib/eex/ebin/Elixir-EEx.beam mix ex_unit eex iex
|
||||
elixir: kernel lib/eex/ebin/Elixir.EEx.beam mix ex_unit eex iex
|
||||
|
||||
kernel: $(KERNEL) VERSION
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex
|
||||
@@ -96,13 +96,17 @@ clean:
|
||||
|
||||
SOURCE_REF = $(shell head="$$(git rev-parse HEAD)" tag="$$(git tag --points-at $$head | tail -1)" ; echo "$${tag:-$$head}\c")
|
||||
|
||||
docs: compile
|
||||
docs: compile ../ex_doc/bin/ex_doc
|
||||
mkdir -p ebin
|
||||
rm -rf docs
|
||||
cp -R -f lib/*/ebin/*.beam ./ebin
|
||||
bin/elixir ../ex_doc/bin/ex_doc "Elixir" "$(VERSION)" -m Kernel -u "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)"
|
||||
rm -rf ebin
|
||||
|
||||
../ex_doc/bin/ex_doc:
|
||||
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
|
||||
@ false
|
||||
|
||||
release_zip: compile
|
||||
rm -rf v$(VERSION).zip
|
||||
zip -9 -r v$(VERSION).zip bin CHANGELOG.md LEGAL lib/*/ebin LICENSE README.md rel VERSION
|
||||
@@ -127,11 +131,11 @@ test_erlang: compile
|
||||
@ $(ERL) -pa lib/elixir/test/ebin -s test_helper test -s erlang halt;
|
||||
@ echo
|
||||
|
||||
test_elixir: test_kernel test_doctest test_mix test_ex_unit test_eex test_iex
|
||||
test_elixir: test_kernel test_ex_unit test_doc_test test_mix test_eex test_iex
|
||||
|
||||
test_doctest: compile
|
||||
test_doc_test: compile
|
||||
@ echo "==> doctest (exunit)"
|
||||
@ cd lib/elixir && ../../bin/elixir -r "test/doctest.exs";
|
||||
@ cd lib/elixir && ../../bin/elixir -r "test/doc_test.exs";
|
||||
|
||||
test_kernel: compile
|
||||
@ echo "==> kernel (exunit)"
|
||||
|
||||
@@ -14,12 +14,21 @@ If you want to contribute to Elixir or run it from source, clone this repository
|
||||
|
||||
If tests pass, you are ready to move on to the [Getting Started guide][1] or to try Interactive Elixir by running: `bin/iex` in your terminal.
|
||||
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R15B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follow:
|
||||
However, if tests fail, it is likely you have an outdated Erlang version (Elixir requires Erlang R16B or later). You can check your Erlang version by calling `erl` in the command line. You will see some information as follow:
|
||||
|
||||
Erlang R15B (erts-5.8.4) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
|
||||
Erlang R16B (erts-5.10.1) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
|
||||
|
||||
If you have the correct version and tests still fail, feel free to [open an issue][2].
|
||||
|
||||
## Building documentation
|
||||
|
||||
Building the documentation requires [ex_doc](https://github.com/elixir-lang/ex_doc) to be installed and built in the same containing folder as elixir.
|
||||
|
||||
# After cloning and compiling Elixir
|
||||
$ git clone git://github.com/elixir-lang/ex_doc.git
|
||||
$ cd ../ex_doc && ../elixir/bin/mix compile
|
||||
$ cd ../elixir && make docs
|
||||
|
||||
## Contributing
|
||||
|
||||
We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CONTRIBUTING.md) guide for more information. We usually keep a list of features and bugs [in the issue tracker][2].
|
||||
@@ -29,11 +38,13 @@ We appreciate any contribution to Elixir, so check out our [CONTRIBUTING.md](CON
|
||||
* #elixir-lang on freenode IRC
|
||||
* [Website][1]
|
||||
* [Issue tracker][2]
|
||||
* [Mailing list][3]
|
||||
* [elixir-talk Mailing list (questions)][3]
|
||||
* [elixir-core Mailing list (development)][4]
|
||||
|
||||
[1]: http://elixir-lang.org
|
||||
[2]: https://github.com/elixir-lang/elixir/issues
|
||||
[3]: http://groups.google.com/group/elixir-lang-core
|
||||
[3]: http://groups.google.com/group/elixir-lang-talk
|
||||
[4]: http://groups.google.com/group/elixir-lang-core
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+8
-3
@@ -26,7 +26,7 @@ fi
|
||||
|
||||
readlink_f () {
|
||||
cd "$(dirname "$1")" > /dev/null
|
||||
local filename="$(basename "$1")"
|
||||
filename="$(basename "$1")"
|
||||
if [ -h "$filename" ]; then
|
||||
readlink_f "$(readlink "$filename")"
|
||||
else
|
||||
@@ -41,14 +41,19 @@ while [ $I -le $# ]; do
|
||||
S=1
|
||||
eval "PEEK=\${$I}"
|
||||
case "$PEEK" in
|
||||
-v|--compile|--no-halt)
|
||||
-v|--compile|--no-halt|+iex|+compile)
|
||||
;;
|
||||
-e|-r|-pr|-pa|-pz|--remsh|--cookie|-S)
|
||||
-e|-r|-pr|-pa|-pz|--remsh|-S)
|
||||
S=2
|
||||
;;
|
||||
--detached|--hidden)
|
||||
ERL="$ERL `echo $PEEK | cut -c 2-`"
|
||||
;;
|
||||
--cookie)
|
||||
I=$(expr $I + 1)
|
||||
eval "VAL=\${$I}"
|
||||
ERL="$ERL -setcookie "$VAL""
|
||||
;;
|
||||
--sname|--name)
|
||||
I=$(expr $I + 1)
|
||||
eval "VAL=\${$I}"
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ fi
|
||||
|
||||
readlink_f () {
|
||||
cd "$(dirname "$1")" > /dev/null
|
||||
local filename="$(basename "$1")"
|
||||
filename="$(basename "$1")"
|
||||
if [ -h "$filename" ]; then
|
||||
readlink_f "$(readlink "$filename")"
|
||||
else
|
||||
|
||||
@@ -14,6 +14,8 @@ if [ $# -gt 0 ] && ([ $1 = "--help" ] || [ $1 = "-h" ]); then
|
||||
--name \"name\" Makes and assigns a name to the distributed node
|
||||
--sname \"name\" Makes and assigns a short name to the distributed node
|
||||
--remsh \"name\" Connects to a node using a remote shell
|
||||
--dot-iex \"path\" Overrides default .iex file and uses path instead;
|
||||
path can be empty, then no file will be loaded
|
||||
|
||||
** Options marked with (*) can be given more than once
|
||||
** Options given after the .exs file or -- are passed down to the executed code
|
||||
@@ -23,7 +25,7 @@ fi
|
||||
|
||||
readlink_f () {
|
||||
cd "$(dirname "$1")" > /dev/null
|
||||
local filename="$(basename "$1")"
|
||||
filename="$(basename "$1")"
|
||||
if [ -h "$filename" ]; then
|
||||
readlink_f "$(readlink "$filename")"
|
||||
else
|
||||
@@ -33,4 +35,4 @@ readlink_f () {
|
||||
|
||||
SELF=$(readlink_f "$0")
|
||||
SCRIPT_PATH=$(dirname "$SELF")
|
||||
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir-IEx-CLI" +iex "$@"
|
||||
ELIXIR_NO_CLI=1 exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user Elixir.IEx.CLI" +iex "$@"
|
||||
|
||||
@@ -76,8 +76,8 @@ defmodule EEx.AssignsEngine do
|
||||
quote(do: Keyword.get var!(assigns), unquote(name))
|
||||
end
|
||||
|
||||
defp transform(_) do
|
||||
super
|
||||
defp transform(arg) do
|
||||
super(arg)
|
||||
end
|
||||
|
||||
defoverridable [transform: 1]
|
||||
@@ -94,4 +94,4 @@ defmodule EEx.SmartEngine do
|
||||
`AssignsEngine` and other conveniences. Read
|
||||
`EEx.AssignsEngine` for examples.
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,7 +11,7 @@ defprotocol Access do
|
||||
following built-in types: keywords, records and functions.
|
||||
"""
|
||||
|
||||
@only [List, Function, Record, Atom]
|
||||
@only [List, Record, Atom]
|
||||
|
||||
@doc """
|
||||
Receives the element being accessed and the access item.
|
||||
@@ -53,20 +53,9 @@ defimpl Access, for: Atom do
|
||||
def access(nil, _) do
|
||||
nil
|
||||
end
|
||||
|
||||
def access(atom, _) do
|
||||
raise "The access protocol can only be invoked for atoms at " <>
|
||||
"compilation time, tried to invoke it for #{inspect atom}"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Access, for: Function do
|
||||
@doc """
|
||||
The Access protocol for functions simply invokes
|
||||
the function passing the item as argument. This
|
||||
is useful because it allows a function to be
|
||||
passed as argument in places a dict would also fit.
|
||||
"""
|
||||
def access(function, item) do
|
||||
function.(item)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -48,7 +48,6 @@ defmodule Application.Behaviour do
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# Starts the given application and all of its dependencies that
|
||||
# have not been started yet recursively.
|
||||
#
|
||||
@@ -95,6 +94,7 @@ defmodule Application.Behaviour do
|
||||
quote location: :keep do
|
||||
@behavior :application
|
||||
|
||||
@doc false
|
||||
def stop(_state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -58,7 +58,15 @@ defimpl Binary.Chars, for: List do
|
||||
|
||||
"""
|
||||
def to_binary(thing) do
|
||||
iolist_to_binary(thing)
|
||||
try do
|
||||
iolist_to_binary(thing)
|
||||
rescue
|
||||
ArgumentError ->
|
||||
raise Protocol.UndefinedError,
|
||||
protocol: __MODULE__,
|
||||
structure: thing,
|
||||
extra: "Only iolists are supported"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -74,20 +82,11 @@ defimpl Binary.Chars, for: Number do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
|
||||
to_binary = :proplists.get_value(:float_to_binary,
|
||||
:proplists.get_value(:exports, :erlang.module_info, []))
|
||||
def to_binary(thing) when thing > @limit do
|
||||
float_to_binary(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
if to_binary == 2 do
|
||||
def to_binary(thing) when thing > @limit do
|
||||
float_to_binary(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
def to_binary(thing) do
|
||||
float_to_binary(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
else
|
||||
def to_binary(thing) do
|
||||
float_to_binary(thing)
|
||||
end
|
||||
def to_binary(thing) do
|
||||
float_to_binary(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -144,7 +144,7 @@ defimpl Binary.Inspect, for: Atom do
|
||||
end
|
||||
end
|
||||
|
||||
# Detect if atom is an atom alias (Elixir-Foo-Bar-Baz)
|
||||
# Detect if atom is an atom alias (Elixir.Foo.Bar.Baz)
|
||||
|
||||
defp valid_ref_identifier?("Elixir" <> rest) do
|
||||
valid_ref_piece?(rest)
|
||||
@@ -152,7 +152,7 @@ defimpl Binary.Inspect, for: Atom do
|
||||
|
||||
defp valid_ref_identifier?(_), do: false
|
||||
|
||||
defp valid_ref_piece?(<<?-, h, t :: binary>>) when h in ?A..?Z do
|
||||
defp valid_ref_piece?(<<?., h, t :: binary>>) when h in ?A..?Z do
|
||||
valid_ref_piece? valid_identifier?(t)
|
||||
end
|
||||
|
||||
@@ -296,7 +296,7 @@ defimpl Binary.Inspect, for: List do
|
||||
|
||||
defp keyword?([{ key, _value } | rest]) when is_atom(key) do
|
||||
case atom_to_list(key) do
|
||||
'Elixir-' ++ _ -> false
|
||||
'Elixir.' ++ _ -> false
|
||||
_ -> keyword?(rest)
|
||||
end
|
||||
end
|
||||
@@ -391,21 +391,12 @@ defimpl Binary.Inspect, for: Number do
|
||||
integer_to_binary(thing)
|
||||
end
|
||||
|
||||
to_binary = :proplists.get_value(:float_to_binary,
|
||||
:proplists.get_value(:exports, :erlang.module_info, []))
|
||||
def inspect(thing, _) when thing > @limit do
|
||||
float_to_binary(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
if to_binary == 2 do
|
||||
def inspect(thing, _) when thing > @limit do
|
||||
float_to_binary(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
def inspect(thing, _) do
|
||||
float_to_binary(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
else
|
||||
def inspect(thing, _) do
|
||||
float_to_binary(thing)
|
||||
end
|
||||
def inspect(thing, _) do
|
||||
float_to_binary(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -7,11 +7,9 @@ defmodule Bitwise do
|
||||
your module:
|
||||
|
||||
iex> use Bitwise
|
||||
...> bnot 1
|
||||
iex> bnot 1
|
||||
-2
|
||||
|
||||
iex> use Bitwise
|
||||
...> 1 &&& 1
|
||||
iex> 1 &&& 1
|
||||
1
|
||||
|
||||
You can select to include only or skip operators by passing options:
|
||||
@@ -127,4 +125,4 @@ defmodule Bitwise do
|
||||
defmacro left >>> right do
|
||||
quote do: __op__ :bsr, unquote(left), unquote(right)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+8
-14
@@ -57,14 +57,8 @@ defmodule Code do
|
||||
:code.del_path(Path.expand to_char_list(path))
|
||||
end
|
||||
|
||||
@doc false
|
||||
def eval(string, binding // [], opts // []) do
|
||||
IO.write "[WARNING] Code.eval is deprecated, please use Code.eval_string instead\n#{Exception.format_stacktrace}"
|
||||
eval_string(string, binding, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evalutes the contents given by string. The second argument is the
|
||||
Evaluates the contents given by string. The second argument is the
|
||||
binding (which should be a keyword) followed by a keyword list of
|
||||
environment options. Those options can be:
|
||||
|
||||
@@ -114,7 +108,7 @@ defmodule Code do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evalutes the quoted contents.
|
||||
Evaluates the quoted contents.
|
||||
|
||||
This function accepts a list of environment options.
|
||||
Check `Code.eval_string` for more information.
|
||||
@@ -196,10 +190,10 @@ defmodule Code do
|
||||
defp unpack_ast(line, forms), do: { :__block__, [line: line], forms }
|
||||
|
||||
@doc """
|
||||
Loads the given `file`. Accepts `relative_to` as an argument
|
||||
to tell where the file is located. If the file was already
|
||||
required/loaded, loads it again. It returns all the modules
|
||||
defined in the file.
|
||||
Loads the given `file`. Accepts `relative_to` as an argument to tell where
|
||||
the file is located. If the file was already required/loaded, loads it again.
|
||||
It returns a list of tuples { ModuleName, <<byte_code>> }, one tuple for each
|
||||
module defined in the file.
|
||||
|
||||
Notice that if `load_file` is invoked by different processes
|
||||
concurrently, the target file will be invoked concurrently
|
||||
@@ -217,8 +211,8 @@ defmodule Code do
|
||||
|
||||
@doc """
|
||||
Requires the given `file`. Accepts `relative_to` as an argument to tell where
|
||||
the file is located. It returns all the modules defined in the file. If the
|
||||
file was already required/loaded, doesn't do anything and returns nil.
|
||||
the file is located. The return value is the same as that of `load_file`. If
|
||||
the file was already required/loaded, doesn't do anything and returns nil.
|
||||
|
||||
Notice that if `require_file` is invoked by different processes concurrently,
|
||||
the first process to invoke `require_file` acquires a lock and the remaining
|
||||
|
||||
+12
-22
@@ -29,7 +29,7 @@ defmodule Dict do
|
||||
...> dict[:hello]
|
||||
:world
|
||||
|
||||
And also the `Enum.Iterator` protocol, allowing one to write:
|
||||
And also the `Enumerable` protocol, allowing one to write:
|
||||
|
||||
Enum.each(dict, fn ({ k, v }) ->
|
||||
IO.puts "#{k}: #{v}"
|
||||
@@ -50,7 +50,6 @@ defmodule Dict do
|
||||
defcallback equal?(t, t) :: boolean
|
||||
defcallback get(t, key) :: value
|
||||
defcallback get(t, key, value) :: value
|
||||
defcallback get!(t, key) :: value | no_return
|
||||
defcallback has_key?(t, key) :: boolean
|
||||
defcallback keys(t) :: list(key)
|
||||
defcallback merge(t, t) :: t
|
||||
@@ -131,11 +130,9 @@ defmodule Dict do
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.has_key?(d, :a)
|
||||
iex> Dict.has_key?(d, :a)
|
||||
true
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.has_key?(d, :b)
|
||||
iex> Dict.has_key?(d, :b)
|
||||
false
|
||||
|
||||
"""
|
||||
@@ -151,15 +148,11 @@ defmodule Dict do
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.get(d, :a)
|
||||
iex> Dict.get(d, :a)
|
||||
1
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.get(d, :b)
|
||||
iex> Dict.get(d, :b)
|
||||
nil
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.get(d, :b, 3)
|
||||
iex> Dict.get(d, :b, 3)
|
||||
3
|
||||
"""
|
||||
@spec get(t, key, value) :: value
|
||||
@@ -179,11 +172,9 @@ defmodule Dict do
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.fetch(d, :a)
|
||||
iex> Dict.fetch(d, :a)
|
||||
{ :ok, 1 }
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.fetch(d, :b)
|
||||
iex> Dict.fetch(d, :b)
|
||||
:error
|
||||
|
||||
"""
|
||||
@@ -199,10 +190,9 @@ defmodule Dict do
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.fetch!(d, :a)
|
||||
iex> Dict.fetch!(d, :a)
|
||||
1
|
||||
iex> d = HashDict.new([a: 1])
|
||||
...> Dict.fetch!(d, :b)
|
||||
iex> Dict.fetch!(d, :b)
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
"""
|
||||
@@ -425,11 +415,11 @@ defmodule Dict do
|
||||
## Examples
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...>
|
||||
...> d = Dict.take(d, [:a, :c, :d])
|
||||
...> Dict.to_list(d)
|
||||
[a: 1]
|
||||
|
||||
iex> d = HashDict.new([a: 1, b: 2])
|
||||
...>
|
||||
...> d = Dict.take(d, [:c, :d])
|
||||
...> Dict.to_list(d)
|
||||
[]
|
||||
|
||||
+340
-834
File diff suppressed because it is too large
Load Diff
@@ -67,9 +67,14 @@ defexception FunctionClauseError, [module: nil, function: nil, arity: nil] do
|
||||
end
|
||||
end
|
||||
|
||||
defexception Protocol.UndefinedError, [protocol: nil, structure: nil] do
|
||||
defexception Protocol.UndefinedError, [protocol: nil, structure: nil, extra: nil] do
|
||||
def message(exception) do
|
||||
"protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
|
||||
msg = "protocol #{inspect exception.protocol} not implemented for #{inspect exception.structure}"
|
||||
if exception.extra do
|
||||
msg <> ". " <> exception.extra
|
||||
else
|
||||
msg
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -150,12 +155,6 @@ defmodule Exception do
|
||||
ErlangError[original: other]
|
||||
end
|
||||
|
||||
@doc false
|
||||
def format_entry(entry, cwd // nil) do
|
||||
IO.write "[WARNING] Exception.format_stacktrace is deprecated, please use Exception.format_stacktrace_entry instead\n#{Exception.format_stacktrace}"
|
||||
format_stacktrace_entry(entry, cwd)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a tuple representing a stacktrace entry and formats it.
|
||||
The current working directory may be given as argument, which
|
||||
|
||||
+48
-77
@@ -930,11 +930,6 @@ defmodule File do
|
||||
`close/1` might return an old write error and not even try to close the file.
|
||||
See `open/2`.
|
||||
"""
|
||||
def close(io_device) when is_function(io_device) do
|
||||
{ iterator, _ } = io_device.()
|
||||
iterator.(:close)
|
||||
end
|
||||
|
||||
def close(io_device) do
|
||||
F.close(io_device)
|
||||
end
|
||||
@@ -962,50 +957,27 @@ defmodule File do
|
||||
end
|
||||
|
||||
"""
|
||||
def iterator(device)
|
||||
|
||||
def iterator(file) when is_binary(file) or is_list(file) do
|
||||
iterator(file, [])
|
||||
end
|
||||
|
||||
def iterator(device) do
|
||||
fn ->
|
||||
function = fn
|
||||
:ok ->
|
||||
case :io.get_line(device, '') do
|
||||
:eof ->
|
||||
close(device)
|
||||
:stop
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
data ->
|
||||
{ data, :ok }
|
||||
end
|
||||
:close ->
|
||||
close(device)
|
||||
end
|
||||
{ function, :ok }
|
||||
fn(fun, acc) ->
|
||||
do_iterator(device, fun, acc)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and
|
||||
returns its iterator. Fails for the same reasons
|
||||
as `File.open`.
|
||||
"""
|
||||
def iterator(file, mode) do
|
||||
case open(file, mode) do
|
||||
{ :ok, device } -> { :ok, iterator(device) }
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `iterator/2` but raises if the file
|
||||
cannot be opened.
|
||||
returns its iterator. The returned iterator will
|
||||
fail for the same reasons as `File.open!`. Note
|
||||
that the file is opened when the iteration begins.
|
||||
"""
|
||||
def iterator!(file, mode // []) do
|
||||
open!(file, mode) |> iterator
|
||||
fn(fun, acc) ->
|
||||
device = open!(file, mode)
|
||||
try do
|
||||
do_iterator(device, fun, acc)
|
||||
after
|
||||
F.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1013,50 +985,27 @@ defmodule File do
|
||||
be passed into `Enum` to iterate line by line as a
|
||||
binary. Check `iterator/1` for more information.
|
||||
"""
|
||||
def biniterator(device)
|
||||
|
||||
def biniterator(file) when is_binary(file) or is_list(file) do
|
||||
biniterator(file, [])
|
||||
end
|
||||
|
||||
def biniterator(device) do
|
||||
fn ->
|
||||
function = fn
|
||||
:ok ->
|
||||
case :file.read_line(device) do
|
||||
:eof ->
|
||||
close(device)
|
||||
:stop
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
{ :ok, data } ->
|
||||
{ data, :ok }
|
||||
end
|
||||
:close ->
|
||||
close(device)
|
||||
end
|
||||
{ function, :ok }
|
||||
fn(fun, acc) ->
|
||||
do_biniterator(device, fun, acc)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Opens the given `file` with the given `mode` and
|
||||
returns its biniterator. Fails for the same reasons
|
||||
as `File.open`.
|
||||
"""
|
||||
def biniterator(file, mode) do
|
||||
case open(file, mode) do
|
||||
{ :ok, device } -> { :ok, biniterator(device) }
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as `biniterator/2` but raises if the file
|
||||
cannot be opened.
|
||||
returns its biniterator. The returned iterator will
|
||||
fail for the same reasons as `File.open!`. Note
|
||||
that the file is opened when the iteration begins.
|
||||
"""
|
||||
def biniterator!(file, mode // []) do
|
||||
open!(file, mode) |> biniterator
|
||||
fn(fun, acc) ->
|
||||
device = open!(file, mode)
|
||||
try do
|
||||
do_biniterator(device, fun, acc)
|
||||
after
|
||||
F.close(device)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
@@ -1075,4 +1024,26 @@ defmodule File do
|
||||
|
||||
defp open_defaults([], true), do: [:binary]
|
||||
defp open_defaults([], false), do: []
|
||||
|
||||
defp do_iterator(device, acc, fun) do
|
||||
case :io.get_line(device, '') do
|
||||
:eof ->
|
||||
acc
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
data ->
|
||||
do_iterator(device, fun.(data, acc), fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_biniterator(device, acc, fun) do
|
||||
case F.read_line(device) do
|
||||
:eof ->
|
||||
acc
|
||||
{ :error, reason } ->
|
||||
raise File.IteratorError, reason: reason
|
||||
{ :ok, data } ->
|
||||
do_iterator(device, fun.(data, acc), fun)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -55,28 +55,34 @@ defmodule GenEvent.Behaviour do
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote location: :keep do
|
||||
@behavior :gen_event
|
||||
@behaviour :gen_event
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{ :ok, args }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event(_event, state) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(_request, state) do
|
||||
{ :ok, :ok, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def terminate(reason, state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
@@ -88,26 +88,32 @@ defmodule GenServer.Behaviour do
|
||||
quote location: :keep do
|
||||
@behavior :gen_server
|
||||
|
||||
@doc false
|
||||
def init(args) do
|
||||
{ :ok, args }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(_request, _from, state) do
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info(_msg, state) do
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_cast(_msg, state) do
|
||||
{ :noreply, state }
|
||||
end
|
||||
|
||||
def terminate(reason, state) do
|
||||
@doc false
|
||||
def terminate(_reason, _state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def code_change(_old, state, _extra) do
|
||||
{ :ok, state }
|
||||
end
|
||||
|
||||
+74
-53
@@ -151,15 +151,6 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get!(dict, key) when is_tuple(dict) do
|
||||
IO.write "[WARNING] Dict.get! and HashDict.get! are deprecated, please use Dict.fetch! and HashDict.fetch! instead\n#{Exception.format_stacktrace}"
|
||||
case dict_get(dict, key) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetches the value under key from the dict
|
||||
and return it in a tagged tuple.
|
||||
@@ -218,24 +209,14 @@ defmodule HashDict do
|
||||
ordered()
|
||||
end
|
||||
|
||||
def equal?(ordered(bucket: a, size: size), ordered(bucket: b, size: ^size)) do
|
||||
a == b
|
||||
end
|
||||
|
||||
def equal?(trie(size: size) = a, trie(size: ^size) = b) do
|
||||
a == b
|
||||
end
|
||||
|
||||
def equal?(ordered() = a, trie() = b) do
|
||||
equal?(b, a)
|
||||
end
|
||||
|
||||
def equal?(trie(size: size) = a, ordered(bucket: b, size: ^size)) do
|
||||
:lists.keysort(1, to_list(a)) == :lists.keysort(1, b)
|
||||
end
|
||||
|
||||
def equal?(_, _) do
|
||||
false
|
||||
def equal?(dict1, dict2) do
|
||||
size = elem(dict1, 1)
|
||||
case elem(dict2, 1) do
|
||||
^size ->
|
||||
dict_equal?(dict1, dict1)
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -246,7 +227,7 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
def to_list(dict) do
|
||||
dict_fold(dict, [], [&1|&2])
|
||||
dict_fold(dict, [], [&1|&2]) |> :lists.reverse
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -333,14 +314,22 @@ defmodule HashDict do
|
||||
drop(delete(dict, key), keys)
|
||||
end
|
||||
|
||||
def reduce(ordered(bucket: bucket), acc, fun) do
|
||||
:lists.foldl(fun, acc, bucket)
|
||||
end
|
||||
|
||||
def reduce(trie() = dict, acc, fun) do
|
||||
dict_fold(dict, acc, fun)
|
||||
end
|
||||
|
||||
## Dict-wide functions
|
||||
|
||||
defp dict_get(ordered(bucket: bucket), key) do
|
||||
:lists.keyfind(key, 1, bucket)
|
||||
bucket_get(bucket, key)
|
||||
end
|
||||
|
||||
defp dict_get(trie(root: root, depth: depth), key) do
|
||||
:lists.keyfind(key, 1, node_bucket(root, depth, bucket_hash(key)))
|
||||
bucket_get(node_bucket(root, depth, bucket_hash(key)), key)
|
||||
end
|
||||
|
||||
defp dict_fold(ordered(bucket: bucket), acc, fun) do
|
||||
@@ -403,28 +392,60 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
defp dict_equal?(dict1, dict2) do
|
||||
try do
|
||||
reduce(dict1, true, fn({ key, value }, acc) ->
|
||||
case fetch(dict2, key) do
|
||||
{ _ok, ^value } ->
|
||||
acc
|
||||
_ ->
|
||||
throw(:error)
|
||||
end
|
||||
end)
|
||||
catch
|
||||
:error -> false
|
||||
end
|
||||
end
|
||||
|
||||
## Bucket helpers
|
||||
|
||||
# Get value from the bucket
|
||||
defp bucket_get([{k,_}|_bucket], key) when k > key do
|
||||
false
|
||||
end
|
||||
|
||||
defp bucket_get([{key,_}=e|_bucket], key) do
|
||||
e
|
||||
end
|
||||
|
||||
defp bucket_get([_e|bucket], key) do
|
||||
bucket_get(bucket, key)
|
||||
end
|
||||
|
||||
defp bucket_get([], _key) do
|
||||
false
|
||||
end
|
||||
|
||||
# Puts a value in the bucket
|
||||
defp bucket_put([{k,_}=e|bucket], key, { :put, value }) when key < k do
|
||||
{ [{key,value},e|bucket], 1 }
|
||||
defp bucket_put([{k,_}|_]=bucket, key, { :put, value }) when k > key do
|
||||
{ [{key, value}|bucket], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put([{k,_}=e|bucket], key, { :update, initial, _fun }) when key < k do
|
||||
{ [{key,initial},e|bucket], 1 }
|
||||
defp bucket_put([{k,_}|_]=bucket, key, { :update, initial, _fun }) when k > key do
|
||||
{ [{key, initial}|bucket], 1 }
|
||||
end
|
||||
|
||||
defp bucket_put([{k,_}=e|bucket], key, value) when key > k do
|
||||
{ rest, count } = bucket_put(bucket, key, value)
|
||||
{ [e|rest], count }
|
||||
end
|
||||
|
||||
defp bucket_put([{_,_}|bucket], key, { :put, value }) do
|
||||
defp bucket_put([{key,_}|bucket], key, { :put, value }) do
|
||||
{ [{key,value}|bucket], 0 }
|
||||
end
|
||||
|
||||
defp bucket_put([{_,value}|bucket], key, { :update, _initial, fun }) do
|
||||
{ [{key,fun.(value)}|bucket], 0 }
|
||||
defp bucket_put([{key,value}|bucket], key, { :update, _initial, fun }) do
|
||||
{ [{key, fun.(value)}|bucket], 0 }
|
||||
end
|
||||
|
||||
defp bucket_put([e|bucket], key, value) do
|
||||
{ rest, count } = bucket_put(bucket, key, value)
|
||||
{ [e|rest], count }
|
||||
end
|
||||
|
||||
defp bucket_put([], key, { :put, value }) do
|
||||
@@ -437,23 +458,23 @@ defmodule HashDict do
|
||||
|
||||
# Puts a value in the bucket without returning
|
||||
# the operation value
|
||||
defp bucket_put!([{k,_}=e|bucket], key, value) when key < k, do: [{key,value},e|bucket]
|
||||
defp bucket_put!([{k,_}=e|bucket], key, value) when key > k, do: [e|bucket_put!(bucket, key, value)]
|
||||
defp bucket_put!([{_,_}|bucket], key, value), do: [{key,value}|bucket]
|
||||
defp bucket_put!([{k,_}|_]=bucket, key, value) when k > key, do: [{key,value}|bucket]
|
||||
defp bucket_put!([{key,_}|bucket], key, value), do: [{key,value}|bucket]
|
||||
defp bucket_put!([{_,_}=e|bucket], key, value), do: [e|bucket_put!(bucket, key, value)]
|
||||
defp bucket_put!([], key, value), do: [{key,value}]
|
||||
|
||||
# Deletes a key from the bucket
|
||||
defp bucket_delete([{k,_}|_] = bucket, key) when key < k do
|
||||
defp bucket_delete([{k,_}|_]=bucket, key) when k > key do
|
||||
{ bucket, nil, 0 }
|
||||
end
|
||||
|
||||
defp bucket_delete([{k,_}=e|bucket], key) when key > k do
|
||||
{ rest, value, count } = bucket_delete(bucket, key)
|
||||
{ [e|rest], value, count }
|
||||
defp bucket_delete([{key,value}|bucket], key) do
|
||||
{ bucket, value, -1 }
|
||||
end
|
||||
|
||||
defp bucket_delete([{_,value}|bucket], _key) do
|
||||
{ bucket, value, -1 }
|
||||
defp bucket_delete([e|bucket], key) do
|
||||
{ rest, value, count } = bucket_delete(bucket, key)
|
||||
{ [e|rest], value, count }
|
||||
end
|
||||
|
||||
defp bucket_delete([], _key) do
|
||||
@@ -578,8 +599,8 @@ defmodule HashDict do
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enum.Iterator, for: HashDict do
|
||||
def iterator(dict), do: HashDict.to_list(dict)
|
||||
defimpl Enumerable, for: HashDict do
|
||||
def reduce(dict, acc, fun), do: HashDict.reduce(dict, acc, fun)
|
||||
def member?(dict, { k, v }), do: match?({ :ok, ^v }, HashDict.fetch(dict, k))
|
||||
def member?(_dict, _), do: false
|
||||
def count(dict), do: HashDict.size(dict)
|
||||
|
||||
@@ -180,25 +180,6 @@ defmodule IO do
|
||||
:io.get_chars(map_dev(device), to_iodata(prompt), count)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def getb(prompt, count // 1)
|
||||
|
||||
def getb(prompt, count) when is_integer(count) do
|
||||
IO.write "[WARNING] IO.getb is deprecated, please use IO.getn instead\n#{Exception.format_stacktrace}"
|
||||
getn(prompt, count)
|
||||
end
|
||||
|
||||
def getb(device, prompt) do
|
||||
IO.write "[WARNING] IO.getb is deprecated, please use IO.getn instead\n#{Exception.format_stacktrace}"
|
||||
getn(device, prompt)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def getb(device, prompt, count) do
|
||||
IO.write "[WARNING] IO.getb is deprecated, please use IO.getn instead\n#{Exception.format_stacktrace}"
|
||||
getn(device, prompt, count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads a line from the IO device. It returns:
|
||||
|
||||
|
||||
+13
-26
@@ -24,9 +24,6 @@ defmodule IO.ANSI do
|
||||
(http://en.wikipedia.org/wiki/ANSI_escape_code) — characters embedded
|
||||
in the text used to control formatting, color, and other output options
|
||||
on video text terminals.
|
||||
|
||||
Please be aware that in Erlang/OTP versions prior to R16, you will not
|
||||
be able to render ANSI escape sequences in iex or erlang shell
|
||||
"""
|
||||
|
||||
import IO.ANSI.Sequence
|
||||
@@ -36,23 +33,11 @@ defmodule IO.ANSI do
|
||||
|
||||
Used to identify whether printing ANSI escape sequences will likely
|
||||
be printed as intended.
|
||||
|
||||
Please note that invoked while in shell (iex) in Erlang/OTP
|
||||
prior to R16, terminal?/0 will always return false because
|
||||
Erlang shell did not support ANSI escape sequences up until
|
||||
R16.
|
||||
"""
|
||||
@spec terminal? :: boolean
|
||||
@spec terminal?(:io.device) :: boolean
|
||||
def terminal?(device // :erlang.group_leader) do
|
||||
if :erlang.system_info(:otp_release) < 'R16' and
|
||||
Process.whereis(:user) != device do
|
||||
# Shell prior to R16 doesn't support ANSI escape
|
||||
# sequences
|
||||
false
|
||||
else
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
end
|
||||
match?({:ok, _}, :io.columns(device))
|
||||
end
|
||||
|
||||
@doc "Resets all attributes"
|
||||
@@ -151,13 +136,13 @@ defmodule IO.ANSI do
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Escapes a string coverting named ANSI sequences into actual ANSI codes.
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
|
||||
The format for referring sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences)
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences).
|
||||
|
||||
It will also force a %{reset} to get appended to every string. If you don't
|
||||
want this behaviour, use `escape_fragment/1` and `escape_fragment/2`.
|
||||
It will also append a %{reset} to the string. If you don't want this
|
||||
behaviour, use `escape_fragment/1` and `escape_fragment/2`.
|
||||
|
||||
An optional boolean parameter can be passed to enable or disable
|
||||
emitting actual ANSI codes. When false, no ANSI codes will emitted.
|
||||
@@ -180,10 +165,10 @@ defmodule IO.ANSI do
|
||||
end
|
||||
|
||||
@doc %B"""
|
||||
Escapes a string coverting named ANSI sequences into actual ANSI codes.
|
||||
Escapes a string by converting named ANSI sequences into actual ANSI codes.
|
||||
|
||||
The format for referring sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences)
|
||||
The format for referring to sequences is `%{red}` and `%{red,bright}` (for
|
||||
multiple sequences).
|
||||
|
||||
An optional boolean parameter can be passed to enable or disable
|
||||
emitting actual ANSI codes. When false, no ANSI codes will emitted.
|
||||
@@ -192,8 +177,10 @@ defmodule IO.ANSI do
|
||||
|
||||
## Example
|
||||
|
||||
iex> IO.ANSI.escape("Hello %{red,bright,green}yes")
|
||||
"Hello \e[31m\e[1m\e[32myes\e[0m"
|
||||
iex> IO.ANSI.escape_fragment("Hello %{red,bright,green}yes")
|
||||
"Hello \e[31m\e[1m\e[32myes"
|
||||
iex> IO.ANSI.escape_fragment("%{reset}bye")
|
||||
"\e[0mbye"
|
||||
|
||||
"""
|
||||
@spec escape_fragment(String.t, emit :: boolean) :: String.t
|
||||
|
||||
+134
-273
@@ -1362,148 +1362,16 @@ defmodule Kernel do
|
||||
@doc %B"""
|
||||
Defines a record.
|
||||
|
||||
A record is a tagged tuple which contains one or more elements
|
||||
and the first element is a module. This macro defines a module
|
||||
that generates accessors to manipulate the record at both
|
||||
compilation and runtime.
|
||||
This macro defines a module that generates accessors to manipulate the record
|
||||
at both compilation and runtime.
|
||||
|
||||
See the `Record` module's documentation for a detailed description of records
|
||||
in Elixir.
|
||||
|
||||
## Examples
|
||||
|
||||
defrecord FileInfo, atime: nil, accesses: 0
|
||||
|
||||
The line above will define a module named `FileInfo` which
|
||||
contains a function named `new` that returns a new record
|
||||
and other functions to read and set the values in the
|
||||
record:
|
||||
|
||||
file_info = FileInfo.new(atime: now())
|
||||
file_info.atime #=> Returns the value of atime
|
||||
file_info.atime(now()) #=> Updates the value of atime
|
||||
|
||||
A record is simply a tuple where the first element is the record
|
||||
module name. We can get the record raw representation as follow:
|
||||
|
||||
inspect FileInfo.new, raw: true
|
||||
#=> { FileInfo, nil, nil }
|
||||
|
||||
Besides defining readers and writers for each attribute, Elixir also
|
||||
defines an `update_#{attribute}` function to update the value. Such
|
||||
functions expect a function as argument that receives the current
|
||||
value and must return the new one. For example, every time the file
|
||||
is accessed, the accesses counter can be incremented with:
|
||||
|
||||
file_info.update_accesses(fn(old) -> old + 1 end)
|
||||
|
||||
Which can be also written as:
|
||||
|
||||
file_info.update_accesses(&1 + 1)
|
||||
|
||||
## Access syntax
|
||||
|
||||
Records in Elixir can be expanded at compilation time to provide
|
||||
pattern matching and faster operations. For example, the clause
|
||||
below will only match if a `FileInfo` is given and the number of
|
||||
accesses is zero:
|
||||
|
||||
def enforce_no_access(FileInfo[accesses: 0]), do: :ok
|
||||
|
||||
The clause above will expand to:
|
||||
|
||||
def enforce_no_access({ FileInfo, _, 0 }), do: :ok
|
||||
|
||||
The downside of using such syntax is that, every time the record
|
||||
changes, your code now needs to be recompiled (which is usually
|
||||
not a concern since Elixir build tools by default recompiles the
|
||||
whole project whenever there is a change).
|
||||
|
||||
Finally, keep in mind that Elixir triggers some optimizations whenever
|
||||
the access syntax is used. For example:
|
||||
|
||||
def no_access?(FileInfo[] = file_info) do
|
||||
file_info.accesses == 0
|
||||
end
|
||||
|
||||
Is translated to:
|
||||
|
||||
def no_access?({ FileInfo, _, _ } = file_info) do
|
||||
elem(file_info, 1) == 0
|
||||
end
|
||||
|
||||
Which provides faster get and set times for record operations.
|
||||
|
||||
## Runtime introspection
|
||||
|
||||
At runtime, developers can use `__record__` to get information
|
||||
about the given record:
|
||||
|
||||
FileInfo.__record__(:name)
|
||||
#=> FileInfo
|
||||
|
||||
FileInfo.__record__(:fields)
|
||||
#=> [atime: nil, accesses: 0]
|
||||
|
||||
In order to quickly access the index of a field, one can use
|
||||
the `__index__` function:
|
||||
|
||||
FileInfo.__index__(:atime)
|
||||
#=> 0
|
||||
|
||||
FileInfo.__index__(:unknown)
|
||||
#=> nil
|
||||
|
||||
## Compile-time introspection
|
||||
|
||||
At the compile time, one can access following information about the record
|
||||
from within the record module:
|
||||
|
||||
* `@record_fields` — a keyword list of record fields with defaults
|
||||
* `@record_types` — a keyword list of record fields with types
|
||||
|
||||
defrecord Foo, bar: nil do
|
||||
record_type bar: nil | integer
|
||||
IO.inspect @record_fields
|
||||
IO.inspect @record_types
|
||||
end
|
||||
|
||||
prints out
|
||||
|
||||
[bar: nil]
|
||||
[bar: {:|,[line: ...],[nil,{:integer,[line: ...],nil}]}]
|
||||
|
||||
where the last line is a quoted representation of
|
||||
|
||||
[bar: nil | integer]
|
||||
|
||||
## Documentation
|
||||
|
||||
By default records are not documented and have `@moduledoc` set to false.
|
||||
|
||||
## Types
|
||||
|
||||
Every record defines a type named `t` that can be accessed in typespecs.
|
||||
For example, assuming the `Config` record defined above, it could be used
|
||||
in typespecs as follow:
|
||||
|
||||
@spec handle_config(Config.t) :: boolean()
|
||||
|
||||
Inside the record definition, a developer can define his own types too:
|
||||
|
||||
defrecord Config, counter: 0, failures: [] do
|
||||
@type kind :: term
|
||||
record_type counter: integer, failures: [kind]
|
||||
end
|
||||
|
||||
When defining a type, all the fields not mentioned in the type are
|
||||
assumed to have type `term`.
|
||||
|
||||
## Importing records
|
||||
|
||||
It is also possible to import a public record (a record, defined using
|
||||
`defrecord`) as a set of private macros (as if it was defined using `defrecordp`):
|
||||
|
||||
Record.import Config, as: :config
|
||||
|
||||
See `Record.import/2` and `defrecordp/2` documentation for more information
|
||||
"""
|
||||
|
||||
defmacro defrecord(name, fields, do_block // [])
|
||||
@@ -1515,18 +1383,18 @@ defmodule Kernel do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@doc %B"""
|
||||
Defines a record with a set of private macros to manipulate it.
|
||||
|
||||
A record is a tagged tuple which contains one or more elements
|
||||
and the first element is a module. This macro defines a set of
|
||||
macros private to the current module to manipulate the record
|
||||
exclusively at compilation time.
|
||||
This macro defines a set of macros private to the current module to
|
||||
manipulate the record exclusively at compilation time.
|
||||
|
||||
`defrecordp` must be used instead of `defrecord` when there is
|
||||
no interest in exposing the record as a whole. In many ways,
|
||||
it is similar to Erlang records, since it is only available at
|
||||
compilation time.
|
||||
`defrecordp` must be used instead of `defrecord` when there is no interest in
|
||||
exposing the record outside of the module it's defined in. In many ways, it
|
||||
is similar to an Erlang record, since it is only available at compilation time.
|
||||
|
||||
See the `Record` module's documentation for a detailed description of records
|
||||
in Elixir.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1571,8 +1439,8 @@ defmodule Kernel do
|
||||
|
||||
1) Differently from records, exceptions are documented by default;
|
||||
|
||||
2) Exceptions **must** implement `message/1` as API that return a
|
||||
binary as result;
|
||||
2) Exceptions **must** implement `message/1` -- a function that returns a
|
||||
string;
|
||||
|
||||
"""
|
||||
defmacro defexception(name, fields, opts // [], do_block // []) do
|
||||
@@ -1828,9 +1696,33 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Makes the given functions in the current module overridable.
|
||||
An overridable function is lazily defined, allowing a
|
||||
developer to customize it.
|
||||
Makes the given functions in the current module overridable. An overridable
|
||||
function is lazily defined, allowing a developer to customize it.
|
||||
|
||||
## Example
|
||||
|
||||
defmodule DefaultMod do
|
||||
defmacro __using__(_opts) do
|
||||
quote do
|
||||
def test(x, y) do
|
||||
x + y
|
||||
end
|
||||
|
||||
defoverridable [test: 2]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule InheritMod do
|
||||
use DefaultMod
|
||||
|
||||
def test(x, y) do
|
||||
x * y + super(x, y)
|
||||
end
|
||||
end
|
||||
|
||||
As seen as in the example `super` can be used to call the default
|
||||
implementation.
|
||||
"""
|
||||
defmacro defoverridable(tuples) do
|
||||
quote do
|
||||
@@ -1892,17 +1784,26 @@ defmodule Kernel do
|
||||
|
||||
The following options are supported:
|
||||
|
||||
* :raw - tuples are not formatted as the inspect protocol, they are
|
||||
always shown as tuples, defaults to false;
|
||||
* raw -- when true, record tuples are not formatted by the inspect protocol,
|
||||
but are printed as just tuples; default: false
|
||||
|
||||
* :limit - the limit of items that are shown in tuples, bitstrings and
|
||||
lists. Does not apply to strings;
|
||||
* limit -- limits the number of items that are printed for tuples, bitstrings,
|
||||
and lists; does not apply to strings
|
||||
|
||||
## Examples
|
||||
|
||||
iex> inspect(:foo)
|
||||
":foo"
|
||||
|
||||
iex> inspect [1,2,3,4,5], limit: 3
|
||||
"[1,2,3,...]"
|
||||
|
||||
inspect(ArgumentError[])
|
||||
#=> "ArgumentError[message: \"argument error\"]"
|
||||
|
||||
inspect(ArgumentError[], raw: true)
|
||||
#=> "{ArgumentError,:__exception__,\"argument error\"}"
|
||||
|
||||
Note that the inspect protocol does not necessarily return a valid
|
||||
representation of an Elixir term. In such cases, the inspected result must
|
||||
start with `#`. For example, inspecting a function will return:
|
||||
@@ -1989,21 +1890,11 @@ defmodule Kernel do
|
||||
quote do: :erlang.setelement(unquote(index) + 1, unquote(tuple), unquote(value))
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro setelem(tuple, index, value) do
|
||||
IO.puts "setelem is deprecated, please use set_elem instead\n#{Exception.format_stacktrace(__CALLER__.stacktrace)}"
|
||||
quote do: :erlang.setelement(unquote(index) + 1, unquote(tuple), unquote(value))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Define insert_elem to insert element into a tuple according to
|
||||
Elixir conventions (i.e. it expects the tuple as first argument,
|
||||
zero-index based).
|
||||
|
||||
Please note that in versions of Erlang prior to R16B there is no BIF
|
||||
for this operation and it is emulated by converting the tuple to a list
|
||||
and back and is, therefore, inefficient.
|
||||
|
||||
## Example
|
||||
|
||||
iex> tuple = { :bar, :baz }
|
||||
@@ -2011,30 +1902,11 @@ defmodule Kernel do
|
||||
{ :foo, :bar, :baz }
|
||||
"""
|
||||
defmacro insert_elem(tuple, index, value) when is_integer(index) do
|
||||
case :proplists.get_value(:insert_element,
|
||||
:proplists.get_value(:exports, :erlang.module_info,[])) do
|
||||
3 ->
|
||||
quote do: :erlang.insert_element(unquote(index + 1), unquote(tuple), unquote(value))
|
||||
:undefined ->
|
||||
do_insert_elem(tuple, index, value)
|
||||
end
|
||||
end
|
||||
defmacro insert_elem(tuple, index, value) do
|
||||
case :proplists.get_value(:insert_element,
|
||||
:proplists.get_value(:exports, :erlang.module_info,[])) do
|
||||
3 ->
|
||||
quote do: :erlang.insert_element(unquote(index) + 1, unquote(tuple), unquote(value))
|
||||
:undefined ->
|
||||
do_insert_elem(tuple, index, value)
|
||||
end
|
||||
quote do: :erlang.insert_element(unquote(index + 1), unquote(tuple), unquote(value))
|
||||
end
|
||||
|
||||
defp do_insert_elem(tuple, index, value) do
|
||||
quote do
|
||||
{h, t} = :lists.split(unquote(index),
|
||||
tuple_to_list(unquote(tuple)))
|
||||
list_to_tuple(h ++ [unquote(value)|t])
|
||||
end
|
||||
defmacro insert_elem(tuple, index, value) do
|
||||
quote do: :erlang.insert_element(unquote(index) + 1, unquote(tuple), unquote(value))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -2053,30 +1925,11 @@ defmodule Kernel do
|
||||
{ :bar, :baz }
|
||||
"""
|
||||
defmacro delete_elem(tuple, index) when is_integer(index) do
|
||||
case :proplists.get_value(:delete_element,
|
||||
:proplists.get_value(:exports, :erlang.module_info,[])) do
|
||||
2 ->
|
||||
quote do: :erlang.delete_element(unquote(index + 1), unquote(tuple))
|
||||
:undefined ->
|
||||
do_delete_elem(tuple, index)
|
||||
end
|
||||
end
|
||||
defmacro delete_elem(tuple, index) do
|
||||
case :proplists.get_value(:delete_element,
|
||||
:proplists.get_value(:exports, :erlang.module_info,[])) do
|
||||
2 ->
|
||||
quote do: :erlang.delete_element(unquote(index) + 1, unquote(tuple))
|
||||
:undefined ->
|
||||
do_delete_elem(tuple, index)
|
||||
end
|
||||
quote do: :erlang.delete_element(unquote(index + 1), unquote(tuple))
|
||||
end
|
||||
|
||||
defp do_delete_elem(tuple, index) do
|
||||
quote do
|
||||
{h, [_|t]} = :lists.split(unquote(index),
|
||||
tuple_to_list(unquote(tuple)))
|
||||
list_to_tuple(h ++ t)
|
||||
end
|
||||
defmacro delete_elem(tuple, index) do
|
||||
quote do: :erlang.delete_element(unquote(index) + 1, unquote(tuple))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -2654,14 +2507,8 @@ defmodule Kernel do
|
||||
123
|
||||
|
||||
"""
|
||||
defmacro binary_to_integer(some_binary) do
|
||||
case :proplists.get_value(:binary_to_integer,
|
||||
:proplists.get_value(:exports, :erlang.module_info, [])) do
|
||||
2 ->
|
||||
quote do: :erlang.binary_to_integer(unquote(some_binary))
|
||||
:undefined ->
|
||||
quote do: list_to_integer(binary_to_list(unquote(some_binary)))
|
||||
end
|
||||
def binary_to_integer(some_binary) do
|
||||
:erlang.binary_to_integer(some_binary)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -2674,14 +2521,8 @@ defmodule Kernel do
|
||||
1023
|
||||
|
||||
"""
|
||||
defmacro binary_to_integer(some_binary, base) do
|
||||
case :proplists.get_value(:binary_to_integer,
|
||||
:proplists.get_value(:exports, :erlang.module_info, [])) do
|
||||
2 ->
|
||||
quote do: :erlang.binary_to_integer(unquote(some_binary), unquote(base))
|
||||
:undefined ->
|
||||
quote do: list_to_integer(binary_to_list(unquote(some_binary)), unquote(base))
|
||||
end
|
||||
def binary_to_integer(some_binary, base) do
|
||||
:erlang.binary_to_integer(some_binary, base)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -2693,14 +2534,8 @@ defmodule Kernel do
|
||||
2.2017764
|
||||
|
||||
"""
|
||||
defmacro binary_to_float(some_binary) do
|
||||
case :proplists.get_value(:binary_to_float,
|
||||
:proplists.get_value(:exports, :erlang.module_info, [])) do
|
||||
1 ->
|
||||
quote do: :erlang.binary_to_float(unquote(some_binary))
|
||||
:undefined ->
|
||||
quote do: list_to_float(binary_to_list(unquote(some_binary)))
|
||||
end
|
||||
def binary_to_float(some_binary) do
|
||||
:erlang.binary_to_float(some_binary)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -2713,14 +2548,8 @@ defmodule Kernel do
|
||||
"123"
|
||||
|
||||
"""
|
||||
defmacro integer_to_binary(some_integer) do
|
||||
case :proplists.get_value(:integer_to_binary,
|
||||
:proplists.get_value(:exports, :erlang.module_info, [])) do
|
||||
2 ->
|
||||
quote do: :erlang.integer_to_binary(unquote(some_integer))
|
||||
:undefined ->
|
||||
quote do: list_to_binary(integer_to_list(unquote(some_integer)))
|
||||
end
|
||||
def integer_to_binary(some_integer) do
|
||||
:erlang.integer_to_binary(some_integer)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -2729,18 +2558,12 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> integer_to_binary(77)
|
||||
"77"
|
||||
iex> integer_to_binary(100, 16)
|
||||
"64"
|
||||
|
||||
"""
|
||||
defmacro integer_to_binary(some_integer, base) do
|
||||
case :proplists.get_value(:integer_to_binary,
|
||||
:proplists.get_value(:exports, :erlang.module_info, [])) do
|
||||
2 ->
|
||||
quote do: :erlang.integer_to_binary(unquote(some_integer), unquote(base))
|
||||
:undefined ->
|
||||
quote do: list_to_binary(integer_to_list(unquote(some_integer), unquote(base)))
|
||||
end
|
||||
def integer_to_binary(some_integer, base) do
|
||||
:erlang.integer_to_binary(some_integer, base)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -2753,14 +2576,8 @@ defmodule Kernel do
|
||||
"7.00000000000000000000e+00"
|
||||
|
||||
"""
|
||||
defmacro float_to_binary(some_float) do
|
||||
case :proplists.get_value(:float_to_binary,
|
||||
:proplists.get_value(:exports, :erlang.module_info, [])) do
|
||||
2 ->
|
||||
quote do: :erlang.float_to_binary(unquote(some_float))
|
||||
:undefined ->
|
||||
quote do: list_to_binary(float_to_list(unquote(some_float)))
|
||||
end
|
||||
def float_to_binary(some_float) do
|
||||
:erlang.float_to_binary(some_float)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -2996,29 +2813,59 @@ defmodule Kernel do
|
||||
defmacro left in right
|
||||
|
||||
@doc """
|
||||
Matches the term on the left against the regular expression
|
||||
on the right. It returns nil if not match happened or the
|
||||
first match otherwise.
|
||||
Matches the term on the left against the regular expression or string on the
|
||||
right. Returns true if `left` matches `right` (if it's a regular expression)
|
||||
or contains `right` (if it's a string).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> "abcd" =~ %r/c(d)/
|
||||
2
|
||||
true
|
||||
|
||||
iex> "abcd" =~ %r/e/
|
||||
nil
|
||||
false
|
||||
|
||||
iex> "abcd" =~ "bc"
|
||||
true
|
||||
|
||||
iex> "abcd" =~ "ad"
|
||||
false
|
||||
|
||||
"""
|
||||
# fast path for literal binaries
|
||||
defmacro left =~ right when is_binary(right) do
|
||||
quote do
|
||||
String.contains?(unquote(left), unquote(right))
|
||||
end
|
||||
end
|
||||
|
||||
# fast path for literal binaries
|
||||
defmacro left =~ ({:<<>>, _, [_bin]} = right) do
|
||||
quote do
|
||||
String.contains?(unquote(left), unquote(right))
|
||||
end
|
||||
end
|
||||
|
||||
# slow path for everything else
|
||||
defmacro left =~ right do
|
||||
quote do
|
||||
Regex.index(unquote(right), unquote(left))
|
||||
str = unquote(left)
|
||||
case unquote(right) do
|
||||
bin when is_binary(bin) ->
|
||||
String.contains?(str, bin)
|
||||
re when is_regex(re) ->
|
||||
Regex.match?(re, str)
|
||||
other ->
|
||||
raise ArgumentError, message: "bad argument on the right side of =~: #{inspect other}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
`|>` is called the pipeline operator as it is useful
|
||||
to write pipeline style expressions. This operator
|
||||
tntroduces the expression on the left as the first
|
||||
argument to the expression on the right.
|
||||
introduces the expression on the left as the first
|
||||
argument to the function call on the right.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -3029,8 +2876,8 @@ defmodule Kernel do
|
||||
|
||||
Enum.map(List.flatten([1,[2],3]), &1 * 2)
|
||||
|
||||
Please be aware of operator precendence, when using
|
||||
this operator. For example, the following expression:
|
||||
Be aware of operator precendence when using this operator.
|
||||
For example, the following expression:
|
||||
|
||||
String.graphemes "Hello" |> Enum.reverse
|
||||
|
||||
@@ -3038,9 +2885,9 @@ defmodule Kernel do
|
||||
|
||||
String.graphemes("Hello" |> Enum.reverse)
|
||||
|
||||
Which will result in an error as Enum.Iterator protocol
|
||||
Which will result in an error as Enumerable protocol
|
||||
is not defined for binaries. Adding explicit parenthesis
|
||||
is recommended:
|
||||
resolves the ambiguity:
|
||||
|
||||
String.graphemes("Hello") |> Enum.reverse
|
||||
|
||||
@@ -3057,7 +2904,11 @@ defmodule Kernel do
|
||||
{ call, line, [left] }
|
||||
end
|
||||
|
||||
defp pipeline_op(left, { call, line, args }) when is_list(args) do
|
||||
defp pipeline_op(left, { call, line, args }=right) when is_list(args) do
|
||||
case validate_pipeline_args(args) do
|
||||
:error -> pipeline_error(right)
|
||||
_ -> nil
|
||||
end
|
||||
{ call, line, [left|args] }
|
||||
end
|
||||
|
||||
@@ -3066,7 +2917,17 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
defp pipeline_op(_, other) do
|
||||
raise ArgumentError, message: "Unsupported expression in pipeline |> operator: #{inspect other}"
|
||||
pipeline_error(other)
|
||||
end
|
||||
|
||||
defp validate_pipeline_args([]), do: nil
|
||||
defp validate_pipeline_args([ {:&,_,_ } | _ ]), do: :error
|
||||
defp validate_pipeline_args([_|t]) do
|
||||
validate_pipeline_args(t)
|
||||
end
|
||||
|
||||
defp pipeline_error(arg) do
|
||||
raise ArgumentError, message: "Unsupported expression in pipeline |> operator: #{Macro.to_binary arg}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -102,9 +102,9 @@ defmodule Kernel.CLI do
|
||||
|
||||
# Process shared options
|
||||
|
||||
defp process_shared(["-v"|t], config) do
|
||||
defp process_shared([opt|_t], _config) when opt in ["-v", "--version"] do
|
||||
IO.puts "Elixir #{System.version}"
|
||||
process_shared t, config
|
||||
System.halt 0
|
||||
end
|
||||
|
||||
defp process_shared(["-pa",h|t], config) do
|
||||
@@ -137,11 +137,7 @@ defmodule Kernel.CLI do
|
||||
process_shared t, config.update_commands [{:parallel_require,h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared(["--cookie",h|t], config) do
|
||||
process_shared t, config.update_commands [{:cookie,h}|&1]
|
||||
end
|
||||
|
||||
defp process_shared([erl,_|t], config) when erl in ["--erl", "--sname", "--name"] do
|
||||
defp process_shared([erl,_|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
|
||||
process_shared t, config
|
||||
end
|
||||
|
||||
@@ -207,7 +203,7 @@ defmodule Kernel.CLI do
|
||||
"-" <> _ ->
|
||||
shared_option? list, config, process_compiler(&1, &2)
|
||||
_ ->
|
||||
pattern = if File.dir?(h), do: "#{h}/**/*.ex", else: h
|
||||
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
|
||||
process_compiler t, config.update_compile [pattern|&1]
|
||||
end
|
||||
end
|
||||
@@ -222,6 +218,12 @@ defmodule Kernel.CLI do
|
||||
{ config, t }
|
||||
end
|
||||
|
||||
# This clause is here so that Kernel.CLI does not error out with "unknown
|
||||
# option"
|
||||
defp process_iex(["--dot-iex",_|t], config) do
|
||||
process_iex t, config
|
||||
end
|
||||
|
||||
defp process_iex([opt,_|t], config) when opt in ["--remsh"] do
|
||||
process_iex t, config
|
||||
end
|
||||
@@ -269,7 +271,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:script, file}, _config) when is_binary(file) do
|
||||
if exec = System.find_executable(file) do
|
||||
if exec = find_elixir_executable(file) do
|
||||
Code.require_file(exec)
|
||||
:ok
|
||||
else
|
||||
@@ -278,7 +280,7 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:file, file}, _config) when is_binary(file) do
|
||||
if File.regular?(file) do
|
||||
if :filelib.is_regular(file) do
|
||||
Code.require_file(file)
|
||||
:ok
|
||||
else
|
||||
@@ -289,7 +291,7 @@ defmodule Kernel.CLI do
|
||||
defp process_command({:require, pattern}, _config) when is_binary(pattern) do
|
||||
files = Path.wildcard(pattern)
|
||||
files = Enum.uniq(files)
|
||||
files = Enum.filter files, File.regular?(&1)
|
||||
files = Enum.filter files, :filelib.is_regular(&1)
|
||||
|
||||
if files != [] do
|
||||
Enum.map files, Code.require_file(&1)
|
||||
@@ -302,7 +304,7 @@ defmodule Kernel.CLI do
|
||||
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
|
||||
files = Path.wildcard(pattern)
|
||||
files = Enum.uniq(files)
|
||||
files = Enum.filter files, File.regular?(&1)
|
||||
files = Enum.filter files, :filelib.is_regular(&1)
|
||||
|
||||
if files != [] do
|
||||
Kernel.ParallelRequire.files(files)
|
||||
@@ -313,11 +315,11 @@ defmodule Kernel.CLI do
|
||||
end
|
||||
|
||||
defp process_command({:compile, patterns}, config) do
|
||||
File.mkdir_p(config.output)
|
||||
:filelib.ensure_dir(:filename.join(config.output, "."))
|
||||
|
||||
files = Enum.map patterns, Path.wildcard(&1)
|
||||
files = Enum.uniq(List.concat(files))
|
||||
files = Enum.filter files, File.regular?(&1)
|
||||
files = Enum.filter files, :filelib.is_regular(&1)
|
||||
|
||||
if files != [] do
|
||||
Code.compiler_options(config.compiler_options)
|
||||
@@ -328,4 +330,19 @@ defmodule Kernel.CLI do
|
||||
{ :error, "--compile : No files matched patterns #{Enum.join(patterns, ",")}" }
|
||||
end
|
||||
end
|
||||
|
||||
defp find_elixir_executable(file) do
|
||||
if exec = System.find_executable(file) do
|
||||
# If we are on Windows, the executable is going to be
|
||||
# a .bat file that must be in the same directory as
|
||||
# the actual Elixir executable.
|
||||
case :os.type() do
|
||||
{ :win32, _ } ->
|
||||
exec = Path.rootname(exec)
|
||||
if File.regular?(exec), do: exec
|
||||
_ ->
|
||||
exec
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -24,7 +24,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Examples
|
||||
|
||||
:{}.(1,2,3)
|
||||
iex> { 1, 2, 3 }
|
||||
{ 1, 2, 3 }
|
||||
|
||||
"""
|
||||
@@ -35,7 +35,7 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
## Examples
|
||||
|
||||
:[].(1,2,3)
|
||||
iex> [ 1, 2, 3 ]
|
||||
[ 1, 2, 3 ]
|
||||
|
||||
"""
|
||||
@@ -424,6 +424,9 @@ defmodule Kernel.SpecialForms do
|
||||
module name. This means that the variable is associated to the
|
||||
ContextSample module and only code generated by this module
|
||||
will be able to access that particular `world` variable.
|
||||
While this means macros from the same module could have
|
||||
conflicting variables, it also allows different quotes from
|
||||
the same module to access them.
|
||||
|
||||
The context can be disabled or changed by explicitly setting
|
||||
the context option. All hygiene mechanisms are based on such
|
||||
@@ -807,4 +810,10 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
"""
|
||||
defmacro __aliases__(args)
|
||||
|
||||
@doc """
|
||||
Calls the overriden function when overriding it with `defoverridable`.
|
||||
See `Kernel.defoverridable` for more information and documentation.
|
||||
"""
|
||||
defmacro super(args)
|
||||
end
|
||||
|
||||
@@ -422,6 +422,17 @@ defmodule Kernel.Typespec do
|
||||
{ :{}, [line: line], args }
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, _line, :list, [arg] }) do
|
||||
case unpack_typespec_kw(arg, []) do
|
||||
{ :ok, ast } -> ast
|
||||
:error -> [typespec_to_ast(arg)]
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, _line, :list, args }) do
|
||||
lc arg inlist args, do: typespec_to_ast(arg)
|
||||
end
|
||||
|
||||
defp typespec_to_ast({ :type, line, :binary, [arg1, arg2] }) do
|
||||
[arg1, arg2] = lc arg inlist [arg1, arg2], do: typespec_to_ast(arg)
|
||||
cond do
|
||||
@@ -473,7 +484,7 @@ defmodule Kernel.Typespec do
|
||||
{ var, line, nil }
|
||||
end
|
||||
|
||||
# special shortcut(s)
|
||||
# Special shortcut(s)
|
||||
defp typespec_to_ast({ :remote_type, line, [{:atom, _, :elixir}, {:atom, _, :char_list}, []] }) do
|
||||
typespec_to_ast({:type, line, :char_list, []})
|
||||
end
|
||||
@@ -482,7 +493,6 @@ defmodule Kernel.Typespec do
|
||||
typespec_to_ast({:type, line, :as_boolean, [arg]})
|
||||
end
|
||||
|
||||
|
||||
defp typespec_to_ast({ :remote_type, line, [mod, name, args] }) do
|
||||
args = lc arg inlist args, do: typespec_to_ast(arg)
|
||||
dot = { :., [line: line], [typespec_to_ast(mod), typespec_to_ast(name)] }
|
||||
@@ -524,7 +534,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle unions
|
||||
defp typespec({ :|, meta, [_,_] } = exprs, vars, caller) do
|
||||
exprs = :lists.reverse(collect_union(exprs))
|
||||
exprs = Enum.reverse(collect_union(exprs))
|
||||
union = lc e inlist exprs, do: typespec(e, vars, caller)
|
||||
{ :type, line(meta), :union, union }
|
||||
end
|
||||
@@ -558,7 +568,6 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle funs
|
||||
|
||||
defp typespec({:->, meta, [{[{:fun, _, arguments}], return}]}, vars, caller) when is_list(arguments) do
|
||||
typespec({:->, meta, [{arguments, return}]}, vars, caller)
|
||||
end
|
||||
@@ -609,7 +618,6 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle blocks
|
||||
|
||||
defp typespec({:__block__, _meta, [arg]}, vars, caller) do
|
||||
typespec(arg, vars, caller)
|
||||
end
|
||||
@@ -665,8 +673,11 @@ defmodule Kernel.Typespec do
|
||||
typespec({ :nonempty_list, [], [spec] }, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(l, _, _) when is_list(l) do
|
||||
raise ArgumentError, message: "Unexpected list #{inspect l}"
|
||||
defp typespec([h|t] = l, vars, caller) do
|
||||
union = Enum.reduce(t, validate_kw(h, l), fn(x, acc) ->
|
||||
{ :|, [], [acc, validate_kw(x, l)] }
|
||||
end)
|
||||
typespec({ :list, [], [union] }, vars, caller)
|
||||
end
|
||||
|
||||
defp typespec(t, vars, caller) when is_tuple(t) do
|
||||
@@ -684,6 +695,11 @@ defmodule Kernel.Typespec do
|
||||
defp collect_union({ :|, _, [a, b] }), do: [b|collect_union(a)]
|
||||
defp collect_union(v), do: [v]
|
||||
|
||||
defp validate_kw({ key, _ } = t, _) when is_atom(key), do: t
|
||||
defp validate_kw(_, original) do
|
||||
raise ArgumentError, message: "unexpected list #{inspect original} in typespec"
|
||||
end
|
||||
|
||||
defp fn_args(meta, args, return, vars, caller) do
|
||||
case [fn_args(meta, args, vars, caller), typespec(return, vars, caller)] do
|
||||
[{:type,_,:any},{:type,_,:any,[]}] -> []
|
||||
@@ -703,4 +719,19 @@ defmodule Kernel.Typespec do
|
||||
defp variable({name, meta, _}) do
|
||||
{:var, line(meta), name}
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw({ :type, _, :union, [
|
||||
next,
|
||||
{ :type, _, :tuple, [{ :atom, _, atom }, type] }
|
||||
] }, acc) do
|
||||
unpack_typespec_kw(next, [{atom,typespec_to_ast(type)}|acc])
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw({ :type, _, :tuple, [{ :atom, _, atom }, type] }, acc) do
|
||||
{ :ok, [{atom,typespec_to_ast(type)}|acc] }
|
||||
end
|
||||
|
||||
defp unpack_typespec_kw(_, _acc) do
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@@ -114,15 +114,6 @@ defmodule Keyword do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get!(keywords, key) when is_atom(key) do
|
||||
IO.write "[WARNING] Keyword.get! is deprecated, please use Keyword.fetch! instead\n#{Exception.format_stacktrace}"
|
||||
case :lists.keyfind(key, 1, keywords) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetchs the value for specific key and return it in a tuple.
|
||||
If the key does not exist, returns `:error`.
|
||||
@@ -152,6 +143,7 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.fetch!([a: 1], :a)
|
||||
1
|
||||
|
||||
iex> Keyword.fetch!([a: 1], :b)
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
@@ -357,6 +349,7 @@ defmodule Keyword do
|
||||
|
||||
iex> Keyword.update([a: 1], :a, &1 * 2)
|
||||
[a: 2]
|
||||
|
||||
iex> Keyword.update([a: 1], :b, &1 * 2)
|
||||
** (KeyError) key not found: :b
|
||||
|
||||
|
||||
@@ -142,12 +142,6 @@ defmodule List do
|
||||
:lists.last(list)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def member?(list, term) do
|
||||
IO.write "[WARNING] List.member? is deprecated, please use Enum.member? instead\n#{Exception.format_stacktrace}"
|
||||
:lists.member(term, list)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and returns the first tuple
|
||||
where the item at position `position` matches with the
|
||||
@@ -171,7 +165,7 @@ defmodule List do
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and returns true if there is
|
||||
a tuple where the item at position `posistion` matches
|
||||
a tuple where the item at position `position` matches
|
||||
with the given `item`.
|
||||
|
||||
## Examples
|
||||
@@ -221,7 +215,7 @@ defmodule List do
|
||||
|
||||
@doc """
|
||||
Receives a list of tuples and deletes the first tuple
|
||||
where the item at position `posistion` matches with the
|
||||
where the item at position `position` matches with the
|
||||
given `item`. Returns the new tuple.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -34,20 +34,11 @@ defimpl List.Chars, for: Number do
|
||||
integer_to_list(thing)
|
||||
end
|
||||
|
||||
to_list = :proplists.get_value(:float_to_list,
|
||||
:proplists.get_value(:exports, :erlang.module_info, []))
|
||||
|
||||
if to_list == 2 do
|
||||
def to_char_list(thing) when thing > @limit do
|
||||
float_to_list(thing, scientific: @digits)
|
||||
end
|
||||
|
||||
def to_char_list(thing) do
|
||||
float_to_list(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
else
|
||||
def to_char_list(thing) do
|
||||
float_to_list(thing)
|
||||
end
|
||||
def to_char_list(thing) when thing > @limit do
|
||||
float_to_list(thing, scientific: @digits)
|
||||
end
|
||||
end
|
||||
|
||||
def to_char_list(thing) do
|
||||
float_to_list(thing, compact: true, decimals: @digits)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -65,15 +65,6 @@ defmodule ListDict do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def get!(dict, key) do
|
||||
IO.write "[WARNING] Dict.get! and ListDict.get! are deprecated, please use Dict.fetch! and ListDict.fetch! instead\n#{Exception.format_stacktrace}"
|
||||
case :lists.keyfind(key, 1, dict) do
|
||||
{ ^key, value } -> value
|
||||
false -> raise(KeyError, key: key)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the value under key from the given
|
||||
dict in a tagged tuple, otherwise `:error`.
|
||||
|
||||
@@ -444,7 +444,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
defp expand({ :__aliases__, _, _ } = original, env, cache) do
|
||||
case :elixir_aliases.expand(original, env.aliases, []) do
|
||||
case :elixir_aliases.expand(original, env.aliases, env.macro_aliases) do
|
||||
atom when is_atom(atom) -> atom
|
||||
aliases ->
|
||||
aliases = lc alias inlist aliases, do: expand(alias, env, cache)
|
||||
|
||||
@@ -20,6 +20,7 @@ defmodule Macro.Env do
|
||||
* `functions` - a list of functions imported from each module
|
||||
* `macros` - a list of macros imported from each module
|
||||
* `context_modules` - a list of modules defined in the current context
|
||||
* `macro_aliases` - a list of aliases defined inside the current macro
|
||||
"""
|
||||
|
||||
@type name_arity :: { atom, non_neg_integer }
|
||||
@@ -32,12 +33,13 @@ defmodule Macro.Env do
|
||||
@type macros :: [{ module, [name_arity] }]
|
||||
@type context_modules :: [module]
|
||||
|
||||
fields = [:module, :file, :line, :function, :aliases,
|
||||
:context, :requires, :functions, :macros, :context_modules]
|
||||
fields = [:module, :file, :line, :function, :aliases, :context, :requires,
|
||||
:functions, :macros, :context_modules, :macro_aliases]
|
||||
|
||||
types = quote do: [module: module, file: file, line: line,
|
||||
function: name_arity, aliases: aliases, requires: requires,
|
||||
functions: functions, macros: macros, context_modules: context_modules]
|
||||
functions: functions, macros: macros, context_modules: context_modules,
|
||||
macro_aliases: aliases]
|
||||
|
||||
Record.deffunctions(fields, __MODULE__)
|
||||
Record.deftypes(fields, types, __MODULE__)
|
||||
|
||||
+297
-21
@@ -7,16 +7,296 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
@moduledoc """
|
||||
@moduledoc %B'''
|
||||
This module provides many functions to deal with modules during
|
||||
compilation time. It allows a developer to dynamically attach
|
||||
documentation, add, delete and register attributes and so forth.
|
||||
|
||||
After the module is compiled, using many of the functions in
|
||||
After a module is compiled, using many of the functions in
|
||||
this module will raise errors, since it is out of their purpose
|
||||
to inspect runtime data. Most of the runtime data can be inspected
|
||||
via the `__info__(attr)` function attached to each compiled module.
|
||||
|
||||
## Module attributes
|
||||
|
||||
Each module can be decorated with one or more attributes. The following ones
|
||||
are currently defined by Elixir:
|
||||
|
||||
* `@after_compile`
|
||||
|
||||
A hook that will be invoked right after the current module is compiled.
|
||||
|
||||
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
|
||||
must take two arguments: the module environment and its bytecode.
|
||||
When just a module is provided, the function is assumed to be
|
||||
`__after_compile__/2`.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@after_compile __MODULE__
|
||||
|
||||
def __after_compile__(env, _bytecode) do
|
||||
IO.inspect env
|
||||
end
|
||||
end
|
||||
|
||||
* `@before_compile`
|
||||
|
||||
A hook that will be invoked before the module is compiled.
|
||||
|
||||
Accepts a module or a tuple `{ <module>, <function/macro atom> }`. The
|
||||
function/macro must take one argument: the module environment. If it's a
|
||||
macro, its returned value will be injected at the end of the module definition
|
||||
before the compilation starts.
|
||||
|
||||
When just a module is provided, the function/macro is assumed to be
|
||||
`__before_compile__/1`.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@before_compile __MODULE__
|
||||
|
||||
defmacro __before_compile__(_env) do
|
||||
quote do
|
||||
def hello, do: "world"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
* `@behaviour` (notice the british spelling)
|
||||
|
||||
Specify an OTP or user-defined behaviour.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@behaviour gen_event
|
||||
|
||||
# ...
|
||||
end
|
||||
|
||||
* `@compile`
|
||||
|
||||
Define options for module compilation that are passed to the Erlang
|
||||
compiler.
|
||||
|
||||
Accepts an atom, a tuple, or a list of atoms and tuples.
|
||||
|
||||
See http://www.erlang.org/doc/man/compile.html for the list of supported
|
||||
options.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@compile { :inline, myfun: 1 }
|
||||
|
||||
def myfun(arg) do
|
||||
to_binary(arg)
|
||||
end
|
||||
end
|
||||
|
||||
* `@doc`
|
||||
|
||||
Provide documentation for the function or macro that follows the
|
||||
attribute.
|
||||
|
||||
Accepts a string (often a heredoc) or `false` where `@doc false` will
|
||||
make the function/macro invisible to the documentation extraction tools
|
||||
like ExDoc.
|
||||
|
||||
Can be invoked more than once.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@doc "Hello world"
|
||||
def hello do
|
||||
"world"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sum.
|
||||
"""
|
||||
def sum(a, b) do
|
||||
a + b
|
||||
end
|
||||
end
|
||||
|
||||
* `@file`
|
||||
|
||||
Change the filename used in stacktraces for the function or macro that
|
||||
follows the attribute.
|
||||
|
||||
Accepts a string. Can be used more than once.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@doc "Hello world"
|
||||
@file "hello.ex"
|
||||
def hello do
|
||||
"world"
|
||||
end
|
||||
end
|
||||
|
||||
* `@moduledoc`
|
||||
|
||||
Provide documentation for the current module.
|
||||
|
||||
Accepts a string (which is often a heredoc) or `false` where
|
||||
`@moduledoc false` will make the module invisible to the
|
||||
documentation extraction tools like ExDoc.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@moduledoc """
|
||||
A very useful module
|
||||
"""
|
||||
end
|
||||
|
||||
|
||||
* `@on_definition`
|
||||
|
||||
A hook that will be invoked after each function or macro in the current
|
||||
module is defined. This makes it easy to annotate and customize
|
||||
functions.
|
||||
|
||||
Accepts a module or a tuple `{ <module>, <function atom> }`. The function
|
||||
must take 6 arguments:
|
||||
|
||||
- the module environment
|
||||
- kind: `:def`, `:defp`, `:defmacro`, or `:defmacrop`
|
||||
- function/macro name
|
||||
- list of quoted arguments
|
||||
- list of quoted guards
|
||||
- quoted function body
|
||||
|
||||
If the function/macro being defined has multiple clauses, the hook will
|
||||
be called for each clause.
|
||||
|
||||
When just a module is provided, the function is assumed to be
|
||||
`__on_definition__/6`.
|
||||
|
||||
Note that you can't provide the current module to `@on_definition`
|
||||
because the hook function will not be defined in time.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule H do
|
||||
def on_def(_env, kind, name, args, guards, body) do
|
||||
IO.puts "Defining #{kind} named #{name} with args:"
|
||||
IO.inspect args
|
||||
IO.puts "and guards"
|
||||
IO.inspect guards
|
||||
IO.puts "and body"
|
||||
IO.puts Macro.to_binary(body)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule M do
|
||||
@on_definition { H, :on_def }
|
||||
|
||||
def hello(arg) when is_binary(arg) or is_list(arg) do
|
||||
"Hello" <> to_binary(arg)
|
||||
end
|
||||
|
||||
def hello(_) do
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
* `@on_load`
|
||||
|
||||
A hook that will be invoked whenever the module is loaded.
|
||||
|
||||
Accepts a function atom of a function in the current module. The function
|
||||
must have arity 0 (no arguments) and has to return `:ok`, otherwise the
|
||||
loading of the module will be aborted.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@on_load :load_check
|
||||
|
||||
def load_check do
|
||||
if some_condition() do
|
||||
:ok
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def some_condition do
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
* `@vsn`
|
||||
|
||||
Specify the module version. Accepts any valid Elixir value.
|
||||
|
||||
**Example**
|
||||
|
||||
defmodule M do
|
||||
@vsn "1.0"
|
||||
end
|
||||
|
||||
The following attributes are part of typespecs and are also reserved by
|
||||
Elixir (see `Kernel.Typespec` for more information about typespecs):
|
||||
|
||||
* `@type` - defines a type to be used in `@spec`
|
||||
* `@typep` - defines a private type to be used in `@spec`
|
||||
* `@opaque` - defines an opaque type to be used in `@spec`
|
||||
* `@spec` - provides a specification for a function
|
||||
* `@callback` - provides a specification for the behavior callback
|
||||
|
||||
In addition to the built-in attributes outlined above, custom attributes may
|
||||
also be added. A custom attribute is any valid identifier prefixed with an
|
||||
`@` and followed by a valid Elixir value:
|
||||
|
||||
defmodule M do
|
||||
@custom_attr [some: "stuff"]
|
||||
end
|
||||
|
||||
For more advanced options available when defining custom attributes, see
|
||||
`register_attribute/3`.
|
||||
|
||||
## Runtime information about a module
|
||||
|
||||
It is possible to query a module at runtime to find out which functions and
|
||||
macros it defines, extract its docstrings, etc. See `__info__/1`.
|
||||
|
||||
'''
|
||||
|
||||
@doc """
|
||||
Provides runtime information about functions and macros defined by the
|
||||
module, enables docstring extraction, etc.
|
||||
|
||||
Each module gets an `__info__/1` function when it's compiled. The function
|
||||
takes one of the following atoms:
|
||||
|
||||
* `:functions` - keyword list of public functions along with their arities
|
||||
|
||||
* `:macros` - keyword list of public macros along with their arities
|
||||
|
||||
* `:docs` - list of all docstrings attached to functions and macros
|
||||
using the `@doc` attribute
|
||||
|
||||
* `:moduledoc` - tuple `{ <line>, <doc> }` where `line` is the line on
|
||||
which module definition starts and `doc` is the string
|
||||
attached to the module using the `@moduledoc` attribute
|
||||
|
||||
* `:module` - module name (`Module == Module.__info__(:module)`)
|
||||
|
||||
In addition to the above, you may also pass to `__info__/1` any atom supported
|
||||
by Erlang's `module_info` function which also gets defined for each compiled
|
||||
module. See http://erlang.org/doc/reference_manual/modules.html#id74571 for
|
||||
more information.
|
||||
"""
|
||||
def __info__(kind)
|
||||
|
||||
@doc """
|
||||
Check if a module is open, i.e. it is currently being defined
|
||||
@@ -28,10 +308,10 @@ defmodule Module do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evalutes the quotes contents in the given module context.
|
||||
Evaluates the quoted contents in the given module's context.
|
||||
|
||||
A list of environment options can also be given as argument.
|
||||
Check `Code.eval_string` for more information.
|
||||
See `Code.eval_string` for more information.
|
||||
|
||||
Raises an error if the module was already compiled.
|
||||
|
||||
@@ -216,17 +496,15 @@ defmodule Module do
|
||||
[] ->
|
||||
ETS.insert(table, { tuple, line, kind, signature, doc })
|
||||
:ok
|
||||
[{ tuple, line, _old_kind, old_sign, old_doc }] when old_doc == nil or doc == nil or old_doc == doc ->
|
||||
[{ tuple, line, _old_kind, old_sign, old_doc }] ->
|
||||
ETS.insert(table, {
|
||||
tuple,
|
||||
line,
|
||||
kind,
|
||||
merge_signatures(old_sign, signature, 1),
|
||||
doc || old_doc
|
||||
if(nil?(doc), do: old_doc, else: doc)
|
||||
})
|
||||
:ok
|
||||
_ ->
|
||||
{ :error, :existing_doc }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -364,7 +642,8 @@ defmodule Module do
|
||||
@doc """
|
||||
Makes the given functions in the given module overridable.
|
||||
An overridable function is lazily defined, allowing a
|
||||
developer to customize it.
|
||||
developer to customize it. See `Kernel.defoverridable` for
|
||||
more information and documentation.
|
||||
"""
|
||||
def make_overridable(module, tuples) do
|
||||
assert_not_compiled!(:make_overridable, module)
|
||||
@@ -376,10 +655,12 @@ defmodule Module do
|
||||
raise "Cannot make function #{name}/#{arity} overridable because it was not defined"
|
||||
clause ->
|
||||
:elixir_def.delete_definition(module, tuple)
|
||||
neighbours = Module.DispatchTracker.yank(module, tuple)
|
||||
|
||||
old = get_attribute(module, :__overridable)
|
||||
new = [ { tuple, { 1, clause, false } } ]
|
||||
merged = :orddict.merge(fn(_k, { count, _, _ }, _v2) -> { count + 1, clause, false } end, old, new)
|
||||
merged = :orddict.update(tuple, fn({ count, _, _, _ }) ->
|
||||
{ count + 1, clause, neighbours, false }
|
||||
end, { 1, clause, neighbours, false }, old)
|
||||
|
||||
put_attribute(module, :__overridable, merged)
|
||||
end
|
||||
@@ -528,7 +809,7 @@ defmodule Module do
|
||||
|
||||
"""
|
||||
def split(module) do
|
||||
tl(String.split(Binary.Chars.to_binary(module), "-"))
|
||||
tl(String.split(Binary.Chars.to_binary(module), "."))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -537,13 +818,10 @@ defmodule Module do
|
||||
def to_binary(Elixir), do: "Elixir"
|
||||
|
||||
def to_binary(module) do
|
||||
"Elixir-" <> rest = Binary.Chars.to_binary(module)
|
||||
bc <<r>> inbits rest, do: <<to_dot(r)>>
|
||||
"Elixir." <> rest = Binary.Chars.to_binary(module)
|
||||
rest
|
||||
end
|
||||
|
||||
defp to_dot(?-), do: ?.
|
||||
defp to_dot(l), do: l
|
||||
|
||||
@doc false
|
||||
# Used internally to compile documentation. This function
|
||||
# is private and must be used only internally.
|
||||
@@ -559,8 +837,6 @@ defmodule Module do
|
||||
:ok
|
||||
{ :error, :private_doc } ->
|
||||
IO.puts "#{env.file}:#{line} function #{name}/#{arity} is private, @doc's are always discarded for private functions"
|
||||
{ :error, :existing_doc } ->
|
||||
IO.puts "#{env.file}:#{line} @doc's for function #{name}/#{arity} have been given more than once, the first version is being kept"
|
||||
end
|
||||
|
||||
delete_attribute(module, :doc)
|
||||
@@ -616,11 +892,11 @@ defmodule Module do
|
||||
end
|
||||
|
||||
defp function_table_for(module) do
|
||||
list_to_atom :lists.concat([:f, module])
|
||||
:elixir_def.table(module)
|
||||
end
|
||||
|
||||
defp docs_table_for(module) do
|
||||
list_to_atom :lists.concat([:o, module])
|
||||
:elixir_module.docs_table(module)
|
||||
end
|
||||
|
||||
defp assert_not_compiled!(fun, module) do
|
||||
|
||||
@@ -0,0 +1,436 @@
|
||||
# This is a module Elixir responsible for tracking
|
||||
# calls in order to extract Elixir modules' behaviour
|
||||
# during compilation time.
|
||||
#
|
||||
# ## Implementation
|
||||
#
|
||||
# The implementation uses the digraph module to track
|
||||
# all dependencies. The graph starts with three main
|
||||
# vertices:
|
||||
#
|
||||
# * `:local` - points to local functions
|
||||
# * `:import` - points to imported modules
|
||||
# * `:warn` - points to imported modules that should be warned
|
||||
# * `:remote` - points to remote modules
|
||||
#
|
||||
# Besides those, we have can the following vertices:
|
||||
#
|
||||
# * `Module` - a module that was invoked via an import or remotely
|
||||
# * `{ name, arity }` - a local function/arity pair
|
||||
# * `{ :import, name, arity }` - an invoked function/arity import
|
||||
# * `{ :remote, name, arity }` - an remotely invoked function/arity
|
||||
#
|
||||
# Each of those vertices can associate to other vertices
|
||||
# as described below:
|
||||
#
|
||||
# * `Module`
|
||||
# * in neighbours: `:import`, `:remote`, `:warn`,
|
||||
# `{ :import, name, arity }` and `{ :remote, name arity }`
|
||||
# * out neighbours: `:warn`
|
||||
#
|
||||
# * `{ name, arity }`
|
||||
# * in neighbours: `:local`, `{ name, arity }`
|
||||
# * out neighbours: `{ :import, name, arity }` and `{ :remote, name arity }`
|
||||
#
|
||||
# * `{ :import, name, arity }`
|
||||
# * in neighbours: `{ name, arity }`
|
||||
# * out neighbours: `Module`
|
||||
#
|
||||
# * `{ :remote, name, arity }`
|
||||
# * in neighbours: `{ name, arity }`
|
||||
# * out neighbours: `Module`
|
||||
#
|
||||
# Note that since this is required for bootstrap, we can't use
|
||||
# any of the `GenServer.Behaviour` conveniences.
|
||||
defmodule Module.DispatchTracker do
|
||||
@moduledoc false
|
||||
|
||||
@timeout 30_000
|
||||
@behavior :gen_server
|
||||
|
||||
@type ref :: pid | module
|
||||
@type name :: atom
|
||||
@type name_arity :: { name, arity }
|
||||
|
||||
@type local :: { name, arity }
|
||||
@type import :: { :import, name, arity }
|
||||
@type remote :: { :remote, name, arity }
|
||||
|
||||
# Public API
|
||||
|
||||
@doc """
|
||||
Receives a dispatch or a module and returns all dispatches
|
||||
that calls it.
|
||||
|
||||
In case the argument is a module, the response will be
|
||||
made by import and remote dispatches.
|
||||
|
||||
In case the argument is another dispatch, the response
|
||||
will be made by local dispatches.
|
||||
|
||||
This function is not recursive, so if A dispatches to
|
||||
B which dispatches to C, A does not appear in the result,
|
||||
only B.
|
||||
"""
|
||||
@spec dispatches_to(ref, module) :: [import | remote]
|
||||
@spec dispatches_to(ref, local | import | remote) :: [local]
|
||||
def dispatches_to(ref, dispatch) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.in_neighbours(d, dispatch) |> only_tuples
|
||||
end
|
||||
|
||||
@doc """
|
||||
Receives a local and returns all dispatches from that local.
|
||||
|
||||
This function is not recursive, so if A dispatches to
|
||||
B which dispatches to C, C does not appear in the result,
|
||||
only B.
|
||||
"""
|
||||
@spec dispatches_from(ref, local) :: [local | import | remote]
|
||||
def dispatches_from(ref, { name, arity }) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, { name, arity }) |> only_tuples
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all the modules which were imported.
|
||||
All external dependencies to a module is the sum
|
||||
of imports and remotes.
|
||||
"""
|
||||
@spec imports(ref) :: [module]
|
||||
def imports(ref) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, :import)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all imported modules that had the given
|
||||
`{ name, arity }` invoked.
|
||||
"""
|
||||
@spec imports_with_dispatch(ref, name_arity) :: [module]
|
||||
def imports_with_dispatch(ref, { name, arity }) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, { :import, name, arity })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all the modules which were remotely dispatched
|
||||
to. All external dependencies to a module is the sum
|
||||
of imports and remotes.
|
||||
"""
|
||||
@spec remotes(ref) :: [module]
|
||||
def remotes(ref) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, :remote)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all modules that had the given `{ name, arity }`
|
||||
invoked remotely.
|
||||
"""
|
||||
@spec remotes_with_dispatch(ref, name_arity) :: [module]
|
||||
def remotes_with_dispatch(ref, { name, arity }) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
:digraph.out_neighbours(d, { :remote, name, arity })
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns all locals that are reachable.
|
||||
|
||||
By default, all public functions are reachable.
|
||||
A private function is only reachable if it has
|
||||
a public function that it invokes directly.
|
||||
"""
|
||||
@spec reachable(ref) :: [local]
|
||||
def reachable(ref) do
|
||||
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
|
||||
reduce_reachable(d, :local, [])
|
||||
end
|
||||
|
||||
defp reduce_reachable(d, vertex, vertices) do
|
||||
neighbours = :digraph.out_neighbours(d, vertex)
|
||||
neighbours = (lc { _, _ } = t inlist neighbours, do: t) |> :ordsets.from_list
|
||||
remaining = :ordsets.subtract(neighbours, vertices)
|
||||
vertices = :ordsets.union(neighbours, vertices)
|
||||
:lists.foldl(reduce_reachable(d, &1, &2), vertices, remaining)
|
||||
end
|
||||
|
||||
defp to_pid(pid) when is_pid(pid), do: pid
|
||||
defp to_pid(mod) when is_atom(mod), do: Module.get_attribute(mod, :__dispatch_tracker)
|
||||
|
||||
defp only_tuples(list) do
|
||||
lc x inlist list, is_tuple(x), do: x
|
||||
end
|
||||
|
||||
# Internal API
|
||||
|
||||
# Starts the tracker and returns its pid.
|
||||
@doc false
|
||||
def start_link do
|
||||
{ :ok, pid } = :gen_server.start_link(__MODULE__, [], [])
|
||||
pid
|
||||
end
|
||||
|
||||
# Adds a definition into the tracker. A public
|
||||
# definition is connected with the :local node
|
||||
# while a private one is left unreachable until
|
||||
# a call is made to.
|
||||
@doc false
|
||||
def add_definition(pid, kind, tuple) when kind in [:def, :defp, :defmacro, :defmacrop] do
|
||||
:gen_server.cast(pid, { :add_definition, kind, tuple })
|
||||
end
|
||||
|
||||
# Adds and tracks defaults for a definition into the tracker.
|
||||
@doc false
|
||||
def add_defaults(pid, kind, tuple, defaults) when kind in [:def, :defp, :defmacro, :defmacrop] do
|
||||
:gen_server.cast(pid, { :add_defaults, kind, tuple, defaults })
|
||||
end
|
||||
|
||||
# Adds a local dispatch to the given target.
|
||||
def add_local(pid, to) when is_tuple(to) do
|
||||
:gen_server.cast(pid, { :add_local, :local, to })
|
||||
end
|
||||
|
||||
# Adds a local dispatch from-to the given target.
|
||||
@doc false
|
||||
def add_local(pid, from, to) when is_tuple(from) and is_tuple(to) do
|
||||
:gen_server.cast(pid, { :add_local, from, to })
|
||||
end
|
||||
|
||||
# Adds a remote dispatch to the given target.
|
||||
@doc false
|
||||
def add_remote(pid, function, module, target) when is_atom(module) and is_tuple(target) do
|
||||
:gen_server.cast(pid, { :add_remote, function, module, target })
|
||||
end
|
||||
|
||||
# Adds a import dispatch to the given target.
|
||||
@doc false
|
||||
def add_import(pid, function, module, target) when is_atom(module) and is_tuple(target) do
|
||||
:gen_server.cast(pid, { :add_import, function, module, target })
|
||||
end
|
||||
|
||||
# Associates a module with a warn. This adds the given
|
||||
# module and associates it with the `:import` vertex
|
||||
# permanently, even if warn is false.
|
||||
@doc false
|
||||
def add_warnable(pid, module, warn, line) when is_atom(module) and is_boolean(warn) do
|
||||
:gen_server.cast(pid, { :add_warnable, module, warn, line })
|
||||
end
|
||||
|
||||
# Collect all unused imports where warn has been set to true.
|
||||
def collect_unused_imports(pid) do
|
||||
d = :gen_server.call(pid, :digraph, @timeout)
|
||||
warnable = :digraph.out_neighbours(d, :warn)
|
||||
|
||||
lc mod inlist warnable, not has_imports?(d, mod), line = get_warn_line(d, mod) do
|
||||
{ mod, line }
|
||||
end
|
||||
end
|
||||
|
||||
defp get_warn_line(d, mod) do
|
||||
[edge] = :digraph.out_edges(d, mod)
|
||||
{ ^edge, ^mod, :warn, line } = :digraph.edge(d, edge)
|
||||
line
|
||||
end
|
||||
|
||||
defp has_imports?(d, mod) do
|
||||
Enum.any?(:digraph.in_neighbours(d, mod), match?({ :import, _, _ }, &1))
|
||||
end
|
||||
|
||||
# Yanks a local node. Returns its in and out vertices in a tuple.
|
||||
@doc false
|
||||
def yank(pid, local) do
|
||||
:gen_server.call(to_pid(pid), { :yank, local }, @timeout)
|
||||
end
|
||||
|
||||
def reattach(pid, kind, tuple, neighbours) do
|
||||
pid = to_pid(pid)
|
||||
add_definition(pid, kind, tuple)
|
||||
:gen_server.cast(pid, { :reattach, tuple, neighbours })
|
||||
end
|
||||
|
||||
# Collecting all conflicting imports with the given functions
|
||||
@doc false
|
||||
def collect_imports_conflicts(pid, all_defined) do
|
||||
d = :gen_server.call(pid, :digraph, @timeout)
|
||||
|
||||
lc { name, arity } inlist all_defined,
|
||||
n = :digraph.out_neighbours(d, { :import, name, arity }),
|
||||
n != [] do
|
||||
{ n, name, arity }
|
||||
end
|
||||
end
|
||||
|
||||
# Collect all unused definitions based on the private
|
||||
# given also accounting the expected amount of default
|
||||
# clauses a private function have.
|
||||
@doc false
|
||||
def collect_unused_locals(pid, private) do
|
||||
reachable = reachable(pid)
|
||||
:lists.foldl(collect_unused_locals(&1, &2, reachable), [], private)
|
||||
end
|
||||
|
||||
defp collect_unused_locals({ tuple, kind, 0 }, acc, reachable) do
|
||||
if :lists.member(tuple, reachable) do
|
||||
acc
|
||||
else
|
||||
[{ :unused_def, tuple, kind }|acc]
|
||||
end
|
||||
end
|
||||
|
||||
defp collect_unused_locals({ tuple, kind, default }, acc, reachable) when default > 0 do
|
||||
{ name, arity } = tuple
|
||||
min = arity - default
|
||||
max = arity
|
||||
|
||||
invoked = lc { n, a } inlist reachable, n == name, a in min..max, do: a
|
||||
|
||||
if invoked == [] do
|
||||
[{ :unused_def, tuple, kind }|acc]
|
||||
else
|
||||
case :lists.min(invoked) - min do
|
||||
0 -> acc
|
||||
^default -> [{ :unused_args, tuple }|acc]
|
||||
unused_args -> [{ :unused_args, tuple, unused_args }|acc]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Stops the gen server
|
||||
@doc false
|
||||
def stop(pid) do
|
||||
:gen_server.cast(pid, :stop)
|
||||
end
|
||||
|
||||
# Callbacks
|
||||
|
||||
def init([]) do
|
||||
d = :digraph.new([:protected])
|
||||
:digraph.add_vertex(d, :local)
|
||||
:digraph.add_vertex(d, :import)
|
||||
:digraph.add_vertex(d, :remote)
|
||||
:digraph.add_vertex(d, :warn)
|
||||
{ :ok, d }
|
||||
end
|
||||
|
||||
def handle_call({ :yank, local }, _from, d) do
|
||||
in_vertices = :digraph.in_neighbours(d, local)
|
||||
out_vertices = :digraph.out_neighbours(d, local)
|
||||
:digraph.del_vertex(d, local)
|
||||
{ :reply, { in_vertices, out_vertices }, d }
|
||||
end
|
||||
|
||||
def handle_call(:digraph, _from, d) do
|
||||
{ :reply, d, d }
|
||||
end
|
||||
|
||||
def handle_call(_request, _from, d) do
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_info(_msg, d) do
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_local, from, to }, d) do
|
||||
handle_add_local(d, from, to)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_remote, function, module, { name, arity } }, d) do
|
||||
handle_import_or_remote(d, :remote, function, module, name, arity)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_import, function, module, { name, arity } }, d) do
|
||||
handle_import_or_remote(d, :import, function, module, name, arity)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_warnable, module, warn, line }, d) do
|
||||
:digraph.add_vertex(d, module)
|
||||
replace_edge!(d, :import, module)
|
||||
|
||||
if warn do
|
||||
:digraph.add_edge(d, :warn, module, line)
|
||||
:digraph.add_edge(d, module, :warn, line)
|
||||
else
|
||||
:digraph.del_path(d, :warn, module)
|
||||
end
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_definition, kind, tuple }, d) do
|
||||
handle_add_definition(d, kind, tuple)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :add_defaults, kind, { name, arity }, defaults }, d) do
|
||||
lc i inlist :lists.seq(arity - defaults, arity - 1) do
|
||||
handle_add_definition(d, kind, { name, i })
|
||||
handle_add_local(d, { name, i }, { name, i + 1 })
|
||||
end
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast({ :reattach, tuple, { in_neigh, out_neigh } }, d) do
|
||||
lc from inlist in_neigh, do: replace_edge(d, from, tuple)
|
||||
lc to inlist out_neigh, do: replace_edge(d, tuple, to)
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def handle_cast(:stop, d) do
|
||||
{ :stop, :normal, d }
|
||||
end
|
||||
|
||||
def handle_cast(_msg, d) do
|
||||
{ :noreply, d }
|
||||
end
|
||||
|
||||
def terminate(_reason, _d) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def code_change(_old, d, _extra) do
|
||||
{ :ok, d }
|
||||
end
|
||||
|
||||
defp handle_import_or_remote(d, kind, function, module, name, arity) do
|
||||
:digraph.add_vertex(d, module)
|
||||
replace_edge!(d, kind, module)
|
||||
|
||||
tuple = { kind, name, arity }
|
||||
:digraph.add_vertex(d, tuple)
|
||||
replace_edge!(d, tuple, module)
|
||||
|
||||
if function != nil do
|
||||
replace_edge!(d, function, tuple)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_add_local(d, from, to) do
|
||||
:digraph.add_vertex(d, to)
|
||||
replace_edge!(d, from, to)
|
||||
end
|
||||
|
||||
defp handle_add_definition(d, public, tuple) when public in [:def, :defmacro] do
|
||||
:digraph.add_vertex(d, tuple)
|
||||
replace_edge!(d, :local, tuple)
|
||||
end
|
||||
|
||||
defp handle_add_definition(d, private, tuple) when private in [:defp, :defmacrop] do
|
||||
:digraph.add_vertex(d, tuple)
|
||||
end
|
||||
|
||||
defp replace_edge!(d, from, to) do
|
||||
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
|
||||
[:"$e"|_] = :digraph.add_edge(d, from, to)
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
@@ -385,10 +385,7 @@ defmodule Path do
|
||||
join(left, atom_to_binary(right))
|
||||
|
||||
defp major_os_type do
|
||||
case :os.type do
|
||||
{ maj, _ } -> maj
|
||||
maj -> maj
|
||||
end
|
||||
:os.type |> elem(0)
|
||||
end
|
||||
|
||||
defp do_join(<<uc_letter, ?:, rest :: binary>>, relativename, [], :win32) when uc_letter in ?A..?Z, do:
|
||||
@@ -426,13 +423,16 @@ defmodule Path do
|
||||
## Examples
|
||||
|
||||
iex> Path.split("")
|
||||
["/"]
|
||||
[]
|
||||
iex> Path.split("foo")
|
||||
["foo"]
|
||||
iex> Path.split("/foo/bar")
|
||||
["/", "foo", "bar"]
|
||||
|
||||
"""
|
||||
# Work around a bug in Erlang on UNIX
|
||||
def split(""), do: []
|
||||
|
||||
def split(path) do
|
||||
FN.split(path)
|
||||
end
|
||||
@@ -471,12 +471,12 @@ defmodule Path do
|
||||
|
||||
"""
|
||||
def wildcard(glob) when is_binary(glob) do
|
||||
paths = :elixir_glob.wildcard :unicode.characters_to_list(glob)
|
||||
paths = :filelib.wildcard :unicode.characters_to_list(glob)
|
||||
Enum.map paths, :unicode.characters_to_binary(&1)
|
||||
end
|
||||
|
||||
def wildcard(glob) when is_list(glob) do
|
||||
:elixir_glob.wildcard glob
|
||||
:filelib.wildcard glob
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@@ -360,6 +360,7 @@ defmodule Process do
|
||||
:erlang.process_info(pid, spec)
|
||||
end
|
||||
|
||||
@compile { :inline, nillify: 1 }
|
||||
defp nillify(:undefined), do: nil
|
||||
defp nillify(other), do: other
|
||||
end
|
||||
|
||||
@@ -267,7 +267,7 @@ defmodule Protocol do
|
||||
true -> unquote(fallback)
|
||||
false ->
|
||||
case atom_to_list(first) do
|
||||
'Elixir-' ++ _ -> __MODULE__.Record
|
||||
'Elixir.' ++ _ -> __MODULE__.Record
|
||||
_ -> unquote(fallback)
|
||||
end
|
||||
end
|
||||
|
||||
+24
-20
@@ -5,14 +5,7 @@ defrecord Range, [:first, :last] do
|
||||
end
|
||||
|
||||
defprotocol Range.Iterator do
|
||||
@doc """
|
||||
How to iterate the range, receives the first
|
||||
and range as arguments. It needs to return a
|
||||
function that receives an item and returns
|
||||
a tuple with two elements: the given item
|
||||
and the next item in the iteration.
|
||||
"""
|
||||
def iterator(first, range)
|
||||
def reduce(first, range, acc, fun)
|
||||
|
||||
@doc """
|
||||
Count how many items are in the range.
|
||||
@@ -20,12 +13,10 @@ defprotocol Range.Iterator do
|
||||
def count(first, range)
|
||||
end
|
||||
|
||||
defimpl Enum.Iterator, for: Range do
|
||||
def iterator(Range[first: first] = range) do
|
||||
iterator = Range.Iterator.iterator(first, range)
|
||||
{ iterator, iterator.(first) }
|
||||
defimpl Enumerable, for: Range do
|
||||
def reduce(Range[first: first] = range, acc, fun) do
|
||||
Range.Iterator.reduce(first, range, acc, fun)
|
||||
end
|
||||
|
||||
def member?(Range[first: first, last: last], value) do
|
||||
value in first..last
|
||||
end
|
||||
@@ -36,16 +27,29 @@ defimpl Enum.Iterator, for: Range do
|
||||
end
|
||||
|
||||
defimpl Range.Iterator, for: Number do
|
||||
def iterator(first, Range[last: last]) when is_integer(first) and is_integer(last) and last >= first do
|
||||
fn(current) ->
|
||||
if current > last, do: :stop, else: { current, current + 1 }
|
||||
def reduce(first, Range[last: last], acc, fun) when is_integer(first) and is_integer(last) do
|
||||
reducer = if last >= first do
|
||||
fn(acc, fun) -> do_reducer_up(first, last, acc, fun) end
|
||||
else
|
||||
fn(acc, fun) -> do_reducer_down(first, last, acc, fun) end
|
||||
end
|
||||
Enumerable.Function.reduce(reducer, acc, fun)
|
||||
end
|
||||
|
||||
def iterator(first, Range[last: last]) when is_integer(first) and is_integer(last) do
|
||||
fn(current) ->
|
||||
if current < last, do: :stop, else: { current, current - 1 }
|
||||
end
|
||||
defp do_reducer_up(counter, last, acc, _fun) when counter > last do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_reducer_up(counter, last, acc, fun) do
|
||||
do_reducer_up(counter + 1, last, fun.(counter, acc), fun)
|
||||
end
|
||||
|
||||
defp do_reducer_down(counter, last, acc, _fun) when counter < last do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_reducer_down(counter, last, acc, fun) do
|
||||
do_reducer_down(counter - 1, last, fun.(counter, acc), fun)
|
||||
end
|
||||
|
||||
def count(first, Range[last: last]) when is_integer(first) and is_integer(last) and last >= first do
|
||||
|
||||
+158
-5
@@ -1,6 +1,155 @@
|
||||
defmodule Record do
|
||||
@moduledoc """
|
||||
@moduledoc %B"""
|
||||
Functions to define Elixir records
|
||||
|
||||
A record is a tagged tuple which contains one or more elements and the first
|
||||
element is a module. One creates a record by calling `defrecord` or
|
||||
`defrecordp` which are documented in `Kernel`.
|
||||
|
||||
## Examples
|
||||
|
||||
defrecord FileInfo, atime: nil, accesses: 0
|
||||
|
||||
The line above will define a module named `FileInfo` which
|
||||
contains a function named `new` that returns a new record
|
||||
and other functions to read and set the values in the
|
||||
record:
|
||||
|
||||
file_info = FileInfo.new(atime: now())
|
||||
file_info.atime #=> Returns the value of atime
|
||||
file_info.atime(now()) #=> Updates the value of atime
|
||||
|
||||
# Update multiple attributes at once:
|
||||
file_info.update(atime: now(), accesses: 1)
|
||||
|
||||
# Obtain the keywords representation of a record:
|
||||
file_info.to_keywords #=> [accesses: 1, atime: {1370,7171,911705}]
|
||||
|
||||
|
||||
A record is simply a tuple where the first element is the record
|
||||
module name. We can get the record raw representation as follow:
|
||||
|
||||
inspect FileInfo.new, raw: true
|
||||
#=> { FileInfo, nil, nil }
|
||||
|
||||
Besides defining readers and writers for each attribute, Elixir also
|
||||
defines an `update_#{attribute}` function to update the value. Such
|
||||
functions expect a function as argument that receives the current
|
||||
value and must return the new one. For example, every time the file
|
||||
is accessed, the accesses counter can be incremented with:
|
||||
|
||||
file_info.update_accesses(fn(old) -> old + 1 end)
|
||||
|
||||
Which can be also written as:
|
||||
|
||||
file_info.update_accesses(&1 + 1)
|
||||
|
||||
## Access syntax
|
||||
|
||||
Records in Elixir can be expanded at compilation time to provide
|
||||
pattern matching and faster operations. For example, the clause
|
||||
below will only match if a `FileInfo` is given and the number of
|
||||
accesses is zero:
|
||||
|
||||
def enforce_no_access(FileInfo[accesses: 0]), do: :ok
|
||||
|
||||
The clause above will expand to:
|
||||
|
||||
def enforce_no_access({ FileInfo, _, 0 }), do: :ok
|
||||
|
||||
The downside of using such syntax is that, every time the record
|
||||
changes, your code now needs to be recompiled (which is usually
|
||||
not a concern since Elixir build tools by default recompiles the
|
||||
whole project whenever there is a change).
|
||||
|
||||
Finally, keep in mind that Elixir triggers some optimizations whenever
|
||||
the access syntax is used. For example:
|
||||
|
||||
def no_access?(FileInfo[] = file_info) do
|
||||
file_info.accesses == 0
|
||||
end
|
||||
|
||||
Is translated to:
|
||||
|
||||
def no_access?({ FileInfo, _, _ } = file_info) do
|
||||
elem(file_info, 1) == 0
|
||||
end
|
||||
|
||||
Which provides faster get and set times for record operations.
|
||||
|
||||
## Runtime introspection
|
||||
|
||||
At runtime, developers can use `__record__` to get information
|
||||
about the given record:
|
||||
|
||||
FileInfo.__record__(:name)
|
||||
#=> FileInfo
|
||||
|
||||
FileInfo.__record__(:fields)
|
||||
#=> [atime: nil, accesses: 0]
|
||||
|
||||
In order to quickly access the index of a field, one can use
|
||||
the `__index__` function:
|
||||
|
||||
FileInfo.__index__(:atime)
|
||||
#=> 0
|
||||
|
||||
FileInfo.__index__(:unknown)
|
||||
#=> nil
|
||||
|
||||
## Compile-time introspection
|
||||
|
||||
At the compile time, one can access following information about the record
|
||||
from within the record module:
|
||||
|
||||
* `@record_fields` — a keyword list of record fields with defaults
|
||||
* `@record_types` — a keyword list of record fields with types
|
||||
|
||||
defrecord Foo, bar: nil do
|
||||
record_type bar: nil | integer
|
||||
IO.inspect @record_fields
|
||||
IO.inspect @record_types
|
||||
end
|
||||
|
||||
prints out
|
||||
|
||||
[bar: nil]
|
||||
[bar: {:|,[line: ...],[nil,{:integer,[line: ...],nil}]}]
|
||||
|
||||
where the last line is a quoted representation of
|
||||
|
||||
[bar: nil | integer]
|
||||
|
||||
## Documentation
|
||||
|
||||
By default records are not documented and have `@moduledoc` set to false.
|
||||
|
||||
## Types
|
||||
|
||||
Every record defines a type named `t` that can be accessed in typespecs.
|
||||
For example, assuming the `Config` record defined above, it could be used
|
||||
in typespecs as follow:
|
||||
|
||||
@spec handle_config(Config.t) :: boolean()
|
||||
|
||||
Inside the record definition, a developer can define his own types too:
|
||||
|
||||
defrecord Config, counter: 0, failures: [] do
|
||||
@type kind :: term
|
||||
record_type counter: integer, failures: [kind]
|
||||
end
|
||||
|
||||
When defining a type, all the fields not mentioned in the type are
|
||||
assumed to have type `term`.
|
||||
|
||||
## Importing records
|
||||
|
||||
It is also possible to import a public record (a record, defined using
|
||||
`defrecord`) as a set of private macros (as if it was defined using `defrecordp`):
|
||||
|
||||
Record.import Config, as: :config
|
||||
|
||||
See `Record.import/2` and `defrecordp/2` documentation for more information
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@@ -26,18 +175,22 @@ defmodule Record do
|
||||
block = Keyword.get(opts, :do, nil)
|
||||
|
||||
quote do
|
||||
values = unquote(values)
|
||||
unquoted_values = unquote(values)
|
||||
|
||||
defmodule unquote(name) do
|
||||
@moduledoc false
|
||||
import Record.DSL
|
||||
import Elixir.Record.DSL
|
||||
|
||||
@record_fields []
|
||||
@record_types []
|
||||
|
||||
Record.deffunctions(values, __ENV__)
|
||||
# Reassign values to inner scope to
|
||||
# avoid conflicts in nested records
|
||||
values = unquoted_values
|
||||
|
||||
Elixir.Record.deffunctions(values, __ENV__)
|
||||
value = unquote(block)
|
||||
Record.deftypes(values, @record_types, __ENV__)
|
||||
Elixir.Record.deftypes(values, @record_types, __ENV__)
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
+30
-15
@@ -79,20 +79,12 @@ defmodule Regex do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Runs the regular expression against the given string
|
||||
and returns the index (zero indexes) where the first
|
||||
match occurs, nil otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.index(%r/c(d)/, "abcd")
|
||||
2
|
||||
iex> Regex.index(%r/e/, "abcd")
|
||||
nil
|
||||
|
||||
"""
|
||||
@doc false
|
||||
def index(regex(re_pattern: compiled), string) do
|
||||
IO.puts "Regex.index(re, string) is deprecated. Please use " <>
|
||||
"Regex.run(re, string, return: :index) instead."
|
||||
Exception.print_stacktrace
|
||||
|
||||
case :re.run(string, compiled, [{ :capture, :first, :index }]) do
|
||||
:nomatch -> nil
|
||||
{ :match, [{index,_}] } -> index
|
||||
@@ -125,6 +117,8 @@ defmodule Regex do
|
||||
["cd", "d"]
|
||||
iex> Regex.run(%r/e/, "abcd")
|
||||
nil
|
||||
iex> Regex.run(%r/c(d)/, "abcd", return: :index)
|
||||
[{2,2},{3,1}]
|
||||
|
||||
"""
|
||||
def run(regex, string, options // [])
|
||||
@@ -291,6 +285,25 @@ defmodule Regex do
|
||||
:re.replace(string, compiled, replacement, opts)
|
||||
end
|
||||
|
||||
{ :ok, pattern } = :re.compile(%B"[.^$*+?()[{\\\|\s#]", [:unicode])
|
||||
@escape_pattern pattern
|
||||
|
||||
@doc %B"""
|
||||
Escapes a string to be literally matched in a regex.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.escape(".")
|
||||
"\\."
|
||||
iex> Regex.escape("\\what if")
|
||||
"\\\\what\\ if"
|
||||
|
||||
"""
|
||||
@spec escape(String.t | char_list) :: String.t | char_list
|
||||
def escape(string) do
|
||||
:re.replace(string, @escape_pattern, "\\\\&", [:global, { :return, return_for(string) }])
|
||||
end
|
||||
|
||||
# Helpers
|
||||
|
||||
@doc false
|
||||
@@ -327,10 +340,12 @@ defmodule Regex do
|
||||
end
|
||||
end
|
||||
|
||||
{ :ok, pattern } = :re.compile(%B"\(\?<(?<G>[^>]*)>")
|
||||
@groups_pattern pattern
|
||||
|
||||
defp parse_groups(source) do
|
||||
options = [:global, {:capture, ['G'], :binary}]
|
||||
{:ok, pattern} = :re.compile(%B"\(\?<(?<G>[^>]*)>")
|
||||
case :re.run(source, pattern, options) do
|
||||
case :re.run(source, @groups_pattern, options) do
|
||||
:nomatch -> []
|
||||
{ :match, results } ->
|
||||
lc [group] inlist results, do: binary_to_atom(group)
|
||||
|
||||
+112
-2
@@ -752,7 +752,7 @@ defmodule String do
|
||||
|
||||
"""
|
||||
@spec to_integer(t) :: {integer, t} | :error
|
||||
|
||||
|
||||
def to_integer(string) do
|
||||
{result, remainder} = :string.to_integer(binary_to_list(string))
|
||||
case result do
|
||||
@@ -785,7 +785,7 @@ defmodule String do
|
||||
charlist = binary_to_list(string)
|
||||
{result, remainder} = :string.to_float(charlist)
|
||||
case result do
|
||||
:error ->
|
||||
:error ->
|
||||
{int_result, int_remainder} = :string.to_integer(charlist)
|
||||
case int_result do
|
||||
:error -> :error
|
||||
@@ -795,4 +795,114 @@ defmodule String do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if `string` starts with any of the prefixes given, otherwise
|
||||
false. `prefixes` can be either a single prefix or a list of prefixes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.starts_with? "elixir", "eli"
|
||||
true
|
||||
iex> String.starts_with? "elixir", ["erlang", "elixir"]
|
||||
true
|
||||
iex> String.starts_with? "elixir", ["erlang", "ruby"]
|
||||
false
|
||||
|
||||
"""
|
||||
@spec starts_with?(t, t | [t]) :: boolean
|
||||
|
||||
def starts_with?(string, prefixes) when is_list(prefixes) do
|
||||
Enum.any?(prefixes, do_starts_with(string, &1))
|
||||
end
|
||||
|
||||
def starts_with?(string, prefix) do
|
||||
do_starts_with(string, prefix)
|
||||
end
|
||||
|
||||
defp do_starts_with(_, "") do
|
||||
true
|
||||
end
|
||||
|
||||
defp do_starts_with(string, prefix) when is_binary(prefix) do
|
||||
match?({0,_}, :binary.match(string, prefix))
|
||||
end
|
||||
|
||||
defp do_starts_with(_, _) do
|
||||
raise ArgumentError
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if `string` ends with any of the suffixes given, otherwise
|
||||
false. `suffixes` can be either a single suffix or a list of suffixes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.ends_with? "language", "age"
|
||||
true
|
||||
iex> String.ends_with? "language", ["youth", "age"]
|
||||
true
|
||||
iex> String.ends_with? "language", ["youth", "elixir"]
|
||||
false
|
||||
|
||||
"""
|
||||
@spec ends_with?(t, t | [t]) :: boolean
|
||||
|
||||
def ends_with?(string, suffixes) when is_list(suffixes) do
|
||||
Enum.any?(suffixes, do_ends_with(string, &1))
|
||||
end
|
||||
|
||||
def ends_with?(string, suffix) do
|
||||
do_ends_with(string, suffix)
|
||||
end
|
||||
|
||||
defp do_ends_with(_, "") do
|
||||
true
|
||||
end
|
||||
|
||||
defp do_ends_with(string, suffix) when is_binary(suffix) do
|
||||
string_size = size(string)
|
||||
suffix_size = size(suffix)
|
||||
scope = {string_size - suffix_size, suffix_size}
|
||||
(suffix_size <= string_size) and (:nomatch != :binary.match(string, suffix, [scope: scope]))
|
||||
end
|
||||
|
||||
defp do_ends_with(_, _) do
|
||||
raise ArgumentError
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns true if `string` contains match, otherwise false.
|
||||
`matches` can be either a single string or a list of strings.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> String.contains? "elixir of life", "of"
|
||||
true
|
||||
iex> String.contains? "elixir of life", ["life", "death"]
|
||||
true
|
||||
iex> String.contains? "elixir of life", ["death", "mercury"]
|
||||
false
|
||||
|
||||
"""
|
||||
@spec contains?(t, t | [t]) :: boolean
|
||||
|
||||
def contains?(string, matches) when is_list(matches) do
|
||||
Enum.any?(matches, do_contains(string, &1))
|
||||
end
|
||||
|
||||
def contains?(string, match) do
|
||||
do_contains(string, match)
|
||||
end
|
||||
|
||||
defp do_contains(_, "") do
|
||||
true
|
||||
end
|
||||
|
||||
defp do_contains(string, match) when is_binary(match) do
|
||||
:nomatch != :binary.match(string, match)
|
||||
end
|
||||
|
||||
defp do_contains(_, _) do
|
||||
raise ArgumentError
|
||||
end
|
||||
end
|
||||
|
||||
+52
-31
@@ -14,20 +14,40 @@ defmodule System do
|
||||
with the VM or the host system.
|
||||
"""
|
||||
|
||||
# Read and strip the version from the `VERSION` file.
|
||||
defmacrop get_version do
|
||||
Regex.replace %r/^\s+|\s+$/, File.read!("VERSION"), ""
|
||||
defp strip_re(iodata, pattern) do
|
||||
:re.replace(iodata, pattern, "", [return: :binary])
|
||||
end
|
||||
|
||||
# Tries to run `git describe --always --tags`. In case of success
|
||||
# returns the most recent tag, otherwise returns an empty string.
|
||||
defp read_stripped(path) do
|
||||
case :file.read_file(path) do
|
||||
{ :ok, binary } ->
|
||||
strip_re(binary, "^\s+|\s+$")
|
||||
_ -> ""
|
||||
end
|
||||
end
|
||||
|
||||
# Read and strip the version from the `VERSION` file.
|
||||
defmacrop get_version do
|
||||
case read_stripped("VERSION") do
|
||||
"" -> raise CompileError, message: "could not read the version number from VERSION"
|
||||
data -> data
|
||||
end
|
||||
end
|
||||
|
||||
# Tries to run `git describe --always --tags`. In the case of success returns
|
||||
# the most recent tag. If that is not available, tries to read the commit hash
|
||||
# from .git/HEAD. If that fails, returns an empty string.
|
||||
defmacrop get_describe do
|
||||
dotgit = Path.join(File.cwd!, ".git")
|
||||
if :os.find_executable('git') && File.exists?(dotgit) do
|
||||
data = :os.cmd('git describe --always --tags')
|
||||
Regex.replace %r/\n/, to_binary(data), ""
|
||||
else
|
||||
""
|
||||
dirpath = ".git"
|
||||
case :file.read_file_info(dirpath) do
|
||||
{ :ok, _ } ->
|
||||
if :os.find_executable('git') do
|
||||
data = :os.cmd('git describe --always --tags')
|
||||
strip_re(data, "\n")
|
||||
else
|
||||
read_stripped(:filename.join(".git", "HEAD"))
|
||||
end
|
||||
_ -> ""
|
||||
end
|
||||
end
|
||||
|
||||
@@ -133,15 +153,22 @@ defmodule System do
|
||||
end
|
||||
|
||||
defp write_env_tmp_dir(env) do
|
||||
case System.get_env(env) do
|
||||
case get_env(env) do
|
||||
nil -> nil
|
||||
tmp -> write_tmp_dir tmp
|
||||
end
|
||||
end
|
||||
|
||||
defp write_tmp_dir(dir) do
|
||||
case File.stat(dir) do
|
||||
{ :ok, File.Stat[type: :directory, access: access] } when access in [:read_write, :write] -> dir
|
||||
case :file.read_file_info(dir) do
|
||||
{:ok, info} ->
|
||||
type_index = File.Stat.__index__ :type
|
||||
access_index = File.Stat.__index__ :access
|
||||
case { elem(info, type_index), elem(info, access_index) } do
|
||||
{ :directory, access } when access in [:read_write, :write] ->
|
||||
dir
|
||||
_ -> nil
|
||||
end
|
||||
{ :error, _ } -> nil
|
||||
end
|
||||
end
|
||||
@@ -177,21 +204,23 @@ defmodule System do
|
||||
end
|
||||
|
||||
@doc """
|
||||
This functions looks up an executable program given
|
||||
This function looks up an executable program given
|
||||
its name using the environment variable PATH on Unix
|
||||
and Windows.
|
||||
and Windows. It also considers the proper executable
|
||||
extension for each OS, so for Windows it will try to
|
||||
lookup files with `.com`, `.cmd` or similar extensions.
|
||||
|
||||
If `command` is a char list, a char list is returned.
|
||||
If `program` is a char list, a char list is returned.
|
||||
Returns a binary otherwise.
|
||||
"""
|
||||
@spec find_executable(char_list) :: char_list | nil
|
||||
@spec find_executable(String.t) :: String.t | nil
|
||||
def find_executable(command) when is_list(command) do
|
||||
:os.find_executable(command) || nil
|
||||
def find_executable(program) when is_list(program) do
|
||||
:os.find_executable(program) || nil
|
||||
end
|
||||
|
||||
def find_executable(command) do
|
||||
case :os.find_executable(to_char_list(command)) do
|
||||
def find_executable(program) do
|
||||
case :os.find_executable(to_char_list(program)) do
|
||||
false -> nil
|
||||
other -> list_to_binary(other)
|
||||
end
|
||||
@@ -294,19 +323,11 @@ defmodule System do
|
||||
def halt(status // 0, options // [])
|
||||
|
||||
def halt(status, options) when is_integer(status) or status == :abort do
|
||||
do_halt(status, options)
|
||||
:erlang.halt(status, options)
|
||||
end
|
||||
|
||||
def halt(status, options) do
|
||||
do_halt(to_char_list(status), options)
|
||||
end
|
||||
|
||||
# Support R15B
|
||||
if { :halt, 2 } in :erlang.module_info(:exports) do
|
||||
defp do_halt(status, options), do: :erlang.halt(status, options)
|
||||
else
|
||||
IO.puts "Using limited halt support. Upgrade to R15B01 or later is recommended."
|
||||
defp do_halt(status, _options), do: :erlang.halt(status)
|
||||
:erlang.halt(to_char_list(status), options)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
+36
-9
@@ -37,14 +37,16 @@ defmodule URI do
|
||||
the query string in steps.
|
||||
"""
|
||||
def query_decoder(q) when is_binary(q) do
|
||||
fn -> { do_decoder(&1), q } end
|
||||
fn(acc, fun) ->
|
||||
do_decoder(q, acc, fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_decoder("") do
|
||||
:stop
|
||||
defp do_decoder("", acc, _fun) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp do_decoder(q) do
|
||||
defp do_decoder(q, acc, fun) do
|
||||
next =
|
||||
case :binary.split(q, "&") do
|
||||
[first, rest] -> rest
|
||||
@@ -57,7 +59,7 @@ defmodule URI do
|
||||
[ key ] -> { decode(key), nil }
|
||||
end
|
||||
|
||||
{ current, next }
|
||||
do_decoder(next, fun.(current, acc), fun)
|
||||
end
|
||||
|
||||
defp pair({k, v}) do
|
||||
@@ -148,7 +150,8 @@ defmodule URI do
|
||||
scheme_specific(scheme, info)
|
||||
end
|
||||
|
||||
defp scheme_specific(scheme, info) do
|
||||
@doc false
|
||||
def scheme_module(scheme) do
|
||||
if scheme do
|
||||
module =
|
||||
try do
|
||||
@@ -158,10 +161,14 @@ defmodule URI do
|
||||
end
|
||||
|
||||
if module && Code.ensure_loaded?(module) do
|
||||
module.parse(default_port(info, module))
|
||||
else
|
||||
info
|
||||
module
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp scheme_specific(scheme, info) do
|
||||
if module = scheme_module(scheme) do
|
||||
module.parse(default_port(info, module))
|
||||
else
|
||||
info
|
||||
end
|
||||
@@ -200,3 +207,23 @@ defmodule URI do
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Binary.Chars, for: URI.Info do
|
||||
def to_binary(uri) do
|
||||
result = ""
|
||||
|
||||
if module = URI.scheme_module(uri.scheme) do
|
||||
if module.default_port == uri.port, do: uri = uri.port(nil)
|
||||
end
|
||||
|
||||
if uri.scheme, do: result = result <> uri.scheme <> "://"
|
||||
if uri.userinfo, do: result = result <> uri.userinfo <> "@"
|
||||
if uri.host, do: result = result <> uri.host
|
||||
if uri.port, do: result = result <> ":" <> integer_to_binary(uri.port)
|
||||
if uri.path, do: result = result <> uri.path
|
||||
if uri.query, do: result = result <> "?" <> uri.query
|
||||
if uri.fragment, do: result = result <> "#" <> uri.fragment
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
+26
-18
@@ -57,32 +57,36 @@ defmodule String.Unicode do
|
||||
|
||||
# Downcase
|
||||
|
||||
def downcase(""), do: ""
|
||||
def downcase(string), do: do_downcase(string) |> list_to_binary
|
||||
|
||||
lc { codepoint, _upper, lower, _title } inlist codes, lower && lower != codepoint do
|
||||
def downcase(unquote(codepoint) <> rest) do
|
||||
unquote(lower) <> downcase(rest)
|
||||
defp do_downcase(unquote(codepoint) <> rest) do
|
||||
unquote(binary_to_list(lower)) ++ downcase(rest)
|
||||
end
|
||||
end
|
||||
|
||||
def downcase(<< char, rest :: binary >>) do
|
||||
<< char >> <> downcase(rest)
|
||||
defp do_downcase(<< char, rest :: binary >>) do
|
||||
[char|do_downcase(rest)]
|
||||
end
|
||||
|
||||
defp do_downcase(""), do: []
|
||||
|
||||
# Upcase
|
||||
|
||||
def upcase(""), do: ""
|
||||
def upcase(string), do: do_upcase(string) |> list_to_binary
|
||||
|
||||
lc { codepoint, upper, _lower, _title } inlist codes, upper && upper != codepoint do
|
||||
def upcase(unquote(codepoint) <> rest) do
|
||||
unquote(upper) <> upcase(rest)
|
||||
defp do_upcase(unquote(codepoint) <> rest) do
|
||||
unquote(binary_to_list(upper)) ++ do_upcase(rest)
|
||||
end
|
||||
end
|
||||
|
||||
def upcase(<< char, rest :: binary >>) do
|
||||
<< char >> <> upcase(rest)
|
||||
defp do_upcase(<< char, rest :: binary >>) do
|
||||
[char|do_upcase(rest)]
|
||||
end
|
||||
|
||||
defp do_upcase(""), do: []
|
||||
|
||||
# Titlecase once
|
||||
|
||||
def titlecase_once(""), do: { "", "" }
|
||||
@@ -109,23 +113,27 @@ defmodule String.Unicode do
|
||||
|
||||
def lstrip(other) when is_binary(other), do: other
|
||||
|
||||
def rstrip(""), do: ""
|
||||
|
||||
def rstrip(string) when is_binary(string) do
|
||||
do_rstrip(string, "")
|
||||
do_rstrip(string, [], [])
|
||||
end
|
||||
|
||||
lc codepoint inlist whitespace do
|
||||
defp do_rstrip(unquote(codepoint) <> rest, buffer) do
|
||||
do_rstrip(rest, unquote(codepoint) <> buffer)
|
||||
c = binary_to_list(codepoint) |> :lists.reverse
|
||||
|
||||
defp do_rstrip(unquote(codepoint) <> rest, acc1, acc2) do
|
||||
do_rstrip(rest, unquote(c) ++ (acc1 || acc2), acc2)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_rstrip(<< char, rest :: binary >>, buffer) do
|
||||
<< buffer :: binary, char, do_rstrip(rest, "") :: binary >>
|
||||
defp do_rstrip(<< char, rest :: binary >>, nil, acc2) do
|
||||
do_rstrip(rest, nil, [char|acc2])
|
||||
end
|
||||
|
||||
defp do_rstrip(<<>>, _), do: <<>>
|
||||
defp do_rstrip(<< char, rest :: binary >>, acc1, _acc2) do
|
||||
do_rstrip(rest, nil, [char|acc1])
|
||||
end
|
||||
|
||||
defp do_rstrip(<<>>, _acc1, acc2), do: acc2 |> :lists.reverse |> list_to_binary
|
||||
|
||||
# Split
|
||||
|
||||
|
||||
@@ -24,4 +24,4 @@
|
||||
{verbose, false}
|
||||
]}.
|
||||
|
||||
{require_otp_vsn,"(R15|R16).*"}.
|
||||
{require_otp_vsn,"(R16).*"}.
|
||||
|
||||
+31
-21
@@ -1,11 +1,11 @@
|
||||
-module(elixir).
|
||||
-behaviour(application).
|
||||
-export([main/1, start_cli/0,
|
||||
scope_for_eval/1, eval/2, eval/3, eval/4,
|
||||
scope_for_eval/1, scope_for_eval/2,
|
||||
eval/2, eval/3, eval/4,
|
||||
eval_quoted/2, eval_quoted/3, eval_quoted/4,
|
||||
eval_forms/3, translate_forms/3]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Top level types
|
||||
-export_type([char_list/0, as_boolean/1]).
|
||||
@@ -46,37 +46,47 @@ start_cli() ->
|
||||
%% EVAL HOOKS
|
||||
|
||||
scope_for_eval(Opts) ->
|
||||
case lists:keyfind(file, 1, Opts) of
|
||||
{ file, RawFile } -> File = to_binary(RawFile);
|
||||
false -> File = <<"nofile">>
|
||||
scope_for_eval(#elixir_scope{
|
||||
file = <<"nofile">>,
|
||||
local = nil,
|
||||
aliases = [],
|
||||
requires = elixir_dispatch:default_requires(),
|
||||
functions = elixir_dispatch:default_functions(),
|
||||
macros = elixir_dispatch:default_macros()
|
||||
}, Opts).
|
||||
|
||||
scope_for_eval(Scope, Opts) ->
|
||||
File = case lists:keyfind(file, 1, Opts) of
|
||||
{ file, RawFile } -> to_binary(RawFile);
|
||||
false -> Scope#elixir_scope.file
|
||||
end,
|
||||
|
||||
case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{ delegate_locals_to, Local } -> Local;
|
||||
false -> Local = nil
|
||||
Local = case lists:keyfind(delegate_locals_to, 1, Opts) of
|
||||
{ delegate_locals_to, LocalOpt } -> LocalOpt;
|
||||
false -> Scope#elixir_scope.local
|
||||
end,
|
||||
|
||||
case lists:keyfind(aliases, 1, Opts) of
|
||||
{ aliases, Aliases } -> Aliases;
|
||||
false -> Aliases = []
|
||||
Aliases = case lists:keyfind(aliases, 1, Opts) of
|
||||
{ aliases, AliasesOpt } -> AliasesOpt;
|
||||
false -> Scope#elixir_scope.aliases
|
||||
end,
|
||||
|
||||
case lists:keyfind(requires, 1, Opts) of
|
||||
{ requires, List } -> Requires = ordsets:from_list(List);
|
||||
false -> Requires = elixir_dispatch:default_requires()
|
||||
Requires = case lists:keyfind(requires, 1, Opts) of
|
||||
{ requires, List } -> ordsets:from_list(List);
|
||||
false -> Scope#elixir_scope.requires
|
||||
end,
|
||||
|
||||
case lists:keyfind(functions, 1, Opts) of
|
||||
{ functions, Functions } -> Functions;
|
||||
false -> Functions = elixir_dispatch:default_functions()
|
||||
Functions = case lists:keyfind(functions, 1, Opts) of
|
||||
{ functions, FunctionsOpt } -> FunctionsOpt;
|
||||
false -> Scope#elixir_scope.functions
|
||||
end,
|
||||
|
||||
case lists:keyfind(macros, 1, Opts) of
|
||||
{ macros, Macros } -> Macros;
|
||||
false -> Macros = elixir_dispatch:default_macros()
|
||||
Macros = case lists:keyfind(macros, 1, Opts) of
|
||||
{ macros, MacrosOpt } -> MacrosOpt;
|
||||
false -> Scope#elixir_scope.macros
|
||||
end,
|
||||
|
||||
#elixir_scope{
|
||||
Scope#elixir_scope{
|
||||
file=File, local=Local,
|
||||
macros=Macros, functions=Functions,
|
||||
requires=Requires, aliases=Aliases }.
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
-export([nesting_alias/2, last/1, concat/1, safe_concat/1,
|
||||
format_error/1, ensure_loaded/3, ensure_loaded/4, expand/3, store/4]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Store an alias in the given scope
|
||||
store(_Meta, New, New, S) -> S;
|
||||
@@ -57,7 +56,7 @@ expand({ '__aliases__', _Meta, List }, _Aliases) ->
|
||||
List.
|
||||
|
||||
expand_one(H, Aliases) ->
|
||||
Lookup = list_to_atom("Elixir-" ++ atom_to_list(H)),
|
||||
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
|
||||
case lookup(Lookup, Aliases) of
|
||||
Lookup -> false;
|
||||
Else -> Else
|
||||
@@ -87,9 +86,9 @@ ensure_loaded(Line, File, Ref, FileModules) ->
|
||||
|
||||
last(Atom) ->
|
||||
Last = last(lists:reverse(atom_to_list(Atom)), []),
|
||||
list_to_atom("Elixir-" ++ Last).
|
||||
list_to_atom("Elixir." ++ Last).
|
||||
|
||||
last([$-|_], Acc) -> Acc;
|
||||
last([$.|_], Acc) -> Acc;
|
||||
last([H|T], Acc) -> last(T, [H|Acc]);
|
||||
last([], Acc) -> Acc.
|
||||
|
||||
@@ -118,12 +117,12 @@ nesting_alias(Prefix, Full) ->
|
||||
do_nesting([X|PreTail], [X|Tail], Acc) ->
|
||||
do_nesting(PreTail, Tail, [X|Acc]);
|
||||
do_nesting([], [H|_], Acc) ->
|
||||
{ list_to_atom("Elixir-" ++ H), concat(lists:reverse([H|Acc])) };
|
||||
{ list_to_atom("Elixir." ++ H), concat(lists:reverse([H|Acc])) };
|
||||
do_nesting(_, _, _Acc) ->
|
||||
false.
|
||||
|
||||
list_nesting(Atom) ->
|
||||
case string:tokens(atom_to_list(Atom), "-") of
|
||||
case string:tokens(atom_to_list(Atom), ".") of
|
||||
["Elixir"|T] -> T;
|
||||
_ -> []
|
||||
end.
|
||||
@@ -138,20 +137,14 @@ raw_concat(['Elixir'|Args]) -> do_concat(Args);
|
||||
raw_concat(Args) -> do_concat(Args).
|
||||
|
||||
do_concat(Args) ->
|
||||
Aliases = [to_partial(Arg) || Arg <- Args, Arg /= nil],
|
||||
Aliases = [to_partial(Arg) || Arg <- Args],
|
||||
"Elixir" ++ lists:concat(Aliases).
|
||||
|
||||
to_partial(Arg) when is_binary(Arg) -> to_partial(binary_to_list(Arg));
|
||||
to_partial(Arg) when is_atom(Arg) -> to_partial(atom_to_list(Arg));
|
||||
to_partial("Elixir-" ++ Arg) -> dot_to_dash([$-|Arg]);
|
||||
to_partial([$-|_] = Arg) -> dot_to_dash(Arg);
|
||||
to_partial(Arg) when is_list(Arg) -> [$-|dot_to_dash(Arg)].
|
||||
|
||||
dot_to_dash(List) ->
|
||||
[case X of
|
||||
$. -> $-;
|
||||
_ -> X
|
||||
end || X <- List].
|
||||
to_partial("Elixir." ++ Arg) -> [$.|Arg];
|
||||
to_partial([$.|_] = Arg) -> Arg;
|
||||
to_partial(Arg) when is_list(Arg) -> [$.|Arg].
|
||||
|
||||
%% Lookup an alias in the current scope.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
%% Handle code related to rocket args, guard and -> matching
|
||||
%% for case, fn, receive and friends. try is handled in elixir_try.
|
||||
%% Handle code related to args, guard and -> matching for case,
|
||||
%% fn, receive and friends. try is handled in elixir_try.
|
||||
-module(elixir_clauses).
|
||||
-export([
|
||||
assigns/3, assigns_block/5, assigns_block/6, extract_last_guards/1,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
-module(elixir_compiler).
|
||||
-export([get_opts/0, get_opt/1, get_opt/2, string/2, file/1, file_to_path/2]).
|
||||
-export([get_opts/0, get_opt/1, get_opt/2, string/2, quoted/2, file/1, file_to_path/2]).
|
||||
-export([core/0, module/3, eval_forms/4, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Public API
|
||||
|
||||
@@ -213,7 +212,6 @@ core_main() ->
|
||||
"lib/elixir/lib/kernel/typespec.ex",
|
||||
"lib/elixir/lib/module.ex",
|
||||
"lib/elixir/lib/record.ex",
|
||||
"lib/elixir/lib/record/extractor.ex",
|
||||
"lib/elixir/lib/macro.ex",
|
||||
"lib/elixir/lib/macro/env.ex",
|
||||
"lib/elixir/lib/exception.ex",
|
||||
@@ -222,18 +220,14 @@ core_main() ->
|
||||
"lib/elixir/lib/enum.ex",
|
||||
"lib/elixir/lib/binary/inspect.ex",
|
||||
"lib/elixir/lib/binary/chars.ex",
|
||||
"lib/elixir/lib/list/chars.ex",
|
||||
"lib/elixir/lib/io.ex",
|
||||
"lib/elixir/lib/file.ex",
|
||||
"lib/elixir/lib/path.ex",
|
||||
"lib/elixir/lib/access.ex",
|
||||
"lib/elixir/lib/regex.ex",
|
||||
"lib/elixir/lib/system.ex",
|
||||
"lib/elixir/lib/process.ex",
|
||||
"lib/elixir/lib/kernel/cli.ex",
|
||||
"lib/elixir/lib/kernel/error_handler.ex",
|
||||
"lib/elixir/lib/kernel/parallel_compiler.ex",
|
||||
"lib/elixir/lib/kernel/record_rewriter.ex"
|
||||
"lib/elixir/lib/kernel/record_rewriter.ex",
|
||||
"lib/elixir/lib/module/dispatch_tracker.ex"
|
||||
].
|
||||
|
||||
%% ERROR HANDLING
|
||||
@@ -253,4 +247,4 @@ format_errors(File, Errors) ->
|
||||
format_warnings(Bootstrap, File, Warnings) ->
|
||||
lists:foreach(fun ({_, Each}) ->
|
||||
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, File, Warning) end, Each)
|
||||
end, Warnings).
|
||||
end, Warnings).
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
-module(elixir_def).
|
||||
-export([table/1,
|
||||
clauses_table/1,
|
||||
build_table/1,
|
||||
delete_table/1,
|
||||
setup/1,
|
||||
cleanup/1,
|
||||
reset_last/1,
|
||||
lookup_definition/2,
|
||||
delete_definition/2,
|
||||
@@ -15,22 +15,25 @@
|
||||
unwrap_stored_definitions/2,
|
||||
format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
-define(attr, '__def_table').
|
||||
-define(clauses_attr, '__clauses_table').
|
||||
|
||||
%% Table management functions. Called internally.
|
||||
|
||||
table(Module) -> ?atom_concat([f, Module]).
|
||||
clauses_table(Module) -> ?atom_concat([c, Module]).
|
||||
table(Module) ->
|
||||
ets:lookup_element(Module, ?attr, 2).
|
||||
|
||||
build_table(Module) ->
|
||||
FunctionsTable = table(Module),
|
||||
ClausesTable = clauses_table(Module),
|
||||
ets:new(FunctionsTable, [set, named_table, public]),
|
||||
ets:new(ClausesTable, [bag, named_table, public]),
|
||||
clauses_table(Module) ->
|
||||
ets:lookup_element(Module, ?clauses_attr, 2).
|
||||
|
||||
setup(Module) ->
|
||||
ets:insert(Module, { ?attr, ets:new(Module, [set, public]) }),
|
||||
ets:insert(Module, { ?clauses_attr, ets:new(Module, [bag, public]) }),
|
||||
reset_last(Module),
|
||||
{ FunctionsTable, ClausesTable }.
|
||||
ok.
|
||||
|
||||
delete_table(Module) ->
|
||||
cleanup(Module) ->
|
||||
ets:delete(table(Module)),
|
||||
ets:delete(clauses_table(Module)).
|
||||
|
||||
@@ -106,12 +109,14 @@ store_definition(Kind, Line, CheckClauses, Module, Call, Body, RawS) ->
|
||||
assert_no_aliases_name(Line, Name, Args, S),
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, S).
|
||||
|
||||
store_definition(Kind, Line, _CheckClauses, nil, _Name, _Args, _Guards, _Body, #elixir_scope{} = S) ->
|
||||
elixir_errors:syntax_error(Line, S#elixir_scope.file, "cannot define function outside module, invalid scope for ~ts", [Kind]);
|
||||
store_definition(Kind, Line, _CheckClauses, nil, _Name, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE, { no_module, Kind });
|
||||
|
||||
store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #elixir_scope{} = DS) ->
|
||||
Arity = length(Args),
|
||||
S = DS#elixir_scope{function={Name,Arity}, module=Module},
|
||||
Tuple = { Name, Arity },
|
||||
S = DS#elixir_scope{module=Module, function=Tuple},
|
||||
elixir_tracker:record_definition(Tuple, Kind, Module),
|
||||
|
||||
CO = elixir_compiler:get_opts(),
|
||||
Location = retrieve_file(Line, Module, S, CO),
|
||||
@@ -119,13 +124,16 @@ store_definition(Kind, Line, CheckClauses, Module, Name, Args, Guards, Body, #el
|
||||
|
||||
{ Function, Defaults, TS } = translate_definition(Kind, Line, Name, Args, Guards, Body, S),
|
||||
|
||||
DefaultsLength = length(Defaults),
|
||||
elixir_tracker:record_defaults(Tuple, Kind, Module, DefaultsLength),
|
||||
|
||||
File = TS#elixir_scope.file,
|
||||
Table = table(Module),
|
||||
CTable = clauses_table(Module),
|
||||
|
||||
compile_super(Module, TS),
|
||||
store_each(CheckClauses, Kind, File, Location,
|
||||
Table, CTable, length(Defaults), Function),
|
||||
Table, CTable, DefaultsLength, Function),
|
||||
|
||||
[store_each(false, Kind, File, Location, Table, CTable, 0,
|
||||
default_function_for(Kind, Name, Default)) || Default <- Defaults],
|
||||
@@ -191,9 +199,12 @@ translate_definition(Kind, Line, Name, RawArgs, RawGuards, RawBody, S) when is_i
|
||||
Function = { function, Line, Name, Arity, Clauses },
|
||||
{ Function, Defaults, TS }.
|
||||
|
||||
translate_clause(_Line, _Kind, _Unpacked, _Guards, nil, S) ->
|
||||
translate_clause(_Line, _Kind, _Unpacked, [], nil, S) ->
|
||||
{ [], S };
|
||||
|
||||
translate_clause(Line, Kind, _Unpacked, _Guards, nil, #elixir_scope{file=File}) ->
|
||||
elixir_errors:syntax_error(Line, File, "missing keyword do in ~ts", [Kind]);
|
||||
|
||||
translate_clause(Line, Kind, Unpacked, Guards, Body, S) ->
|
||||
Expr = expr_from_body(Line, Body),
|
||||
|
||||
@@ -306,8 +317,8 @@ store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line
|
||||
FinalLocation = StoredLocation,
|
||||
FinalDefaults = max(Defaults, StoredDefaults),
|
||||
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
|
||||
check_valid_defaults(Line, File, Name, Arity, Defaults, StoredDefaults),
|
||||
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Table);
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults),
|
||||
(Check and StoredCheck) andalso check_valid_clause(Line, File, Name, Arity, Kind, Table);
|
||||
[] ->
|
||||
FinalLine = Line,
|
||||
FinalLocation = Location,
|
||||
@@ -324,38 +335,41 @@ check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
|
||||
elixir_errors:form_error(Line, File, ?MODULE,
|
||||
{ changed_kind, { Name, Arity, StoredKind, Kind } }).
|
||||
|
||||
check_valid_clause(Line, File, Name, Arity, Table) ->
|
||||
check_valid_clause(Line, File, Name, Arity, Kind, Table) ->
|
||||
case ets:lookup_element(Table, last, 2) of
|
||||
{Name,Arity} -> [];
|
||||
[] -> [];
|
||||
_ ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE,
|
||||
{ override_function, { Name, Arity } } })
|
||||
{ override_function, { Kind, Name, Arity } } })
|
||||
end.
|
||||
|
||||
check_valid_defaults(_Line, _File, _Name, _Arity, 0, _) -> [];
|
||||
check_valid_defaults(Line, File, Name, Arity, _, 0) ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { out_of_order_defaults, { Name, Arity } } });
|
||||
check_valid_defaults(Line, File, Name, Arity, _, _) ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { clauses_with_defaults, { Name, Arity } } }).
|
||||
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _) -> [];
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, 0) ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { out_of_order_defaults, { Kind, Name, Arity } } });
|
||||
check_valid_defaults(Line, File, Name, Arity, Kind, _, _) ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { clauses_with_defaults, { Kind, Name, Arity } } }).
|
||||
|
||||
assert_no_aliases_name(Line, '__aliases__', [Atom], #elixir_scope{file=File}) when is_atom(Atom) ->
|
||||
Message = "function names should start with lowercase characters or underscore, invalid name ~ts",
|
||||
elixir_errors:syntax_error(Line, File, Message, [atom_to_binary(Atom, utf8)]);
|
||||
elixir_errors:syntax_error(Line, File, Message, [Atom]);
|
||||
|
||||
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
|
||||
ok.
|
||||
|
||||
%% Format errors
|
||||
|
||||
format_error({clauses_with_defaults,{Name,Arity}}) ->
|
||||
io_lib:format("function ~ts/~B has default values and multiple clauses, use a separate clause for declaring defaults", [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]);
|
||||
|
||||
format_error({out_of_order_defaults,{Name,Arity}}) ->
|
||||
io_lib:format("clause with defaults should be the first clause in function ~ts/~B", [Name, Arity]);
|
||||
format_error({clauses_with_defaults,{Kind,Name,Arity}}) ->
|
||||
io_lib:format("~ts ~ts/~B has default values and multiple clauses, use a separate clause for declaring defaults", [Kind, Name, Arity]);
|
||||
|
||||
format_error({override_function,{Name,Arity}}) ->
|
||||
io_lib:format("trying to override previously defined function ~ts/~B", [Name, Arity]);
|
||||
format_error({out_of_order_defaults,{Kind,Name,Arity}}) ->
|
||||
io_lib:format("clause with defaults should be the first clause in ~ts ~ts/~B", [Kind, Name, Arity]);
|
||||
|
||||
format_error({override_function,{Kind,Name,Arity}}) ->
|
||||
io_lib:format("trying to override previously defined ~ts ~ts/~B", [Kind, Name, Arity]);
|
||||
|
||||
format_error({changed_kind,{Name,Arity,Previous,Current}}) ->
|
||||
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]).
|
||||
@@ -1,20 +1,14 @@
|
||||
%% Module responsible for local invocation of macros and functions.
|
||||
-module(elixir_def_local).
|
||||
-export([
|
||||
macro_for/3,
|
||||
function_for/3,
|
||||
format_error/1,
|
||||
check_unused_local/3
|
||||
]).
|
||||
-export([macro_for/3, function_for/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Used by elixir_dispatch, returns false if no macro is found
|
||||
macro_for(_Tuple, _All, nil) -> false;
|
||||
macro_for(_Tuple, _All, #elixir_scope{module=nil}) -> false;
|
||||
|
||||
macro_for(Tuple, All, Module) ->
|
||||
macro_for(Tuple, All, #elixir_scope{module=Module,function=Function}) ->
|
||||
try elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, Kind, Line, _, _, _, _ }, Clauses } when Kind == defmacro; All, Kind == defmacrop ->
|
||||
elixir_import:record(Tuple, Module, Module),
|
||||
elixir_tracker:record_local(Tuple, Module, Function),
|
||||
get_function(Line, Module, Clauses);
|
||||
_ ->
|
||||
false
|
||||
@@ -22,20 +16,19 @@ macro_for(Tuple, All, Module) ->
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
%% Used on runtime by rewritten clauses, raises an error if function is not found
|
||||
function_for(Module, Name, Arity) ->
|
||||
Tuple = { Name, Arity },
|
||||
case elixir_def:lookup_definition(Module, Tuple) of
|
||||
{ { Tuple, _, Line, _, _, _, _ }, Clauses } ->
|
||||
elixir_import:record(Tuple, Module, Module),
|
||||
%% There is no need to record such calls
|
||||
%% since they come from funtions that were
|
||||
%% already analyzed at compilation time.
|
||||
get_function(Line, Module, Clauses);
|
||||
_ ->
|
||||
[_|T] = erlang:get_stacktrace(),
|
||||
erlang:raise(error, undef, [{Module,Name,Arity,[]}|T])
|
||||
end.
|
||||
|
||||
%% Helpers
|
||||
|
||||
get_function(Line, Module, Clauses) ->
|
||||
RewrittenClauses = [rewrite_clause(Clause, Module) || Clause <- Clauses],
|
||||
Fun = { 'fun', Line, {clauses, RewrittenClauses } },
|
||||
@@ -66,53 +59,4 @@ rewrite_clause(Tuple, Module) when is_tuple(Tuple) ->
|
||||
rewrite_clause(List, Module) when is_list(List) ->
|
||||
[rewrite_clause(Item, Module) || Item <- List];
|
||||
|
||||
rewrite_clause(Else, _) -> Else.
|
||||
|
||||
%% Error handling
|
||||
|
||||
check_unused_local(File, Recorded, Private) ->
|
||||
[check_unused_local(Fun, Kind, Line, File, Defaults, Recorded) ||
|
||||
{ Fun, Kind, Line, true, Defaults } <- Private].
|
||||
|
||||
check_unused_local(Fun, Kind, Line, File, 0, Recorded) ->
|
||||
not(lists:member(Fun, Recorded)) andalso
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { unused_def, Kind, Fun } });
|
||||
|
||||
check_unused_local({ _, Arity } = Fun, Kind, Line, File, Defaults, Recorded) when Defaults > 0 ->
|
||||
Min = Arity - Defaults,
|
||||
Max = Arity,
|
||||
|
||||
Invoked = [A || { _, A } <- Recorded, A >= Min, A =< Max],
|
||||
|
||||
case Invoked of
|
||||
[] ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { unused_def, Kind, Fun } });
|
||||
_ ->
|
||||
UnusedArgs = lists:min(Invoked) - Min,
|
||||
if
|
||||
UnusedArgs == 0 ->
|
||||
ok;
|
||||
UnusedArgs == Defaults ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { unused_args, Fun } });
|
||||
true ->
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, { unused_args, Fun, UnusedArgs } })
|
||||
end
|
||||
end;
|
||||
|
||||
check_unused_local(_Fun, _Kind, _Line, _File, _Defaults, _Recorded) ->
|
||||
ok.
|
||||
|
||||
format_error({unused_def,defp,{Name, Arity}}) ->
|
||||
io_lib:format("function ~ts/~B is unused", [Name, Arity]);
|
||||
|
||||
format_error({unused_args,{Name, Arity}}) ->
|
||||
io_lib:format("default arguments in ~ts/~B are never used", [Name, Arity]);
|
||||
|
||||
format_error({unused_args,{Name, Arity},1}) ->
|
||||
io_lib:format("the first default argument in ~ts/~B is never used", [Name, Arity]);
|
||||
|
||||
format_error({unused_args,{Name, Arity},Count}) ->
|
||||
io_lib:format("the first ~B default arguments in ~ts/~B are never used", [Count, Name, Arity]);
|
||||
|
||||
format_error({unused_def,defmacrop,{Name, Arity}}) ->
|
||||
io_lib:format("macro ~ts/~B is unused", [Name, Arity]).
|
||||
rewrite_clause(Else, _) -> Else.
|
||||
@@ -3,7 +3,6 @@
|
||||
-export([store_pending/1, is_defined/2, ensure_defined/4,
|
||||
assign_args/3, retrieve_args/3, name/2, store/3, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
overridable(Module) ->
|
||||
ets:lookup_element(elixir_module:data_table(Module), '__overridable', 2).
|
||||
@@ -16,13 +15,13 @@ overridable(Module, Value) ->
|
||||
is_defined(Module, Tuple) ->
|
||||
Overridable = overridable(Module),
|
||||
case orddict:find(Tuple, Overridable) of
|
||||
{ ok, { _, _, _ } } -> true;
|
||||
{ ok, { _, _, _, _ } } -> true;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
ensure_defined(Meta, Module, Tuple, S) ->
|
||||
case is_defined(Module, Tuple) of
|
||||
true -> [];
|
||||
true -> ok;
|
||||
_ -> elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, { no_super, Module, Tuple })
|
||||
end.
|
||||
|
||||
@@ -54,7 +53,7 @@ name(Module, Function) ->
|
||||
name(Module, Function, overridable(Module)).
|
||||
|
||||
name(_Module, { Name, _ } = Function, Overridable) ->
|
||||
{ Count, _, _ } = orddict:fetch(Function, Overridable),
|
||||
{ Count, _, _, _ } = orddict:fetch(Function, Overridable),
|
||||
?atom_concat([Name, " (overridable ", Count, ")"]).
|
||||
|
||||
%% Store
|
||||
@@ -62,9 +61,9 @@ name(_Module, { Name, _ } = Function, Overridable) ->
|
||||
store(Module, Function, GenerateName) ->
|
||||
Overridable = overridable(Module),
|
||||
case orddict:fetch(Function, Overridable) of
|
||||
{ _Count, _Clause, true } -> ok;
|
||||
{ Count, Clause, false } ->
|
||||
overridable(Module, orddict:store(Function, { Count, Clause, true }, Overridable)),
|
||||
{ _Count, _Clause, _Neighbours, true } -> ok;
|
||||
{ Count, Clause, Neighbours, false } ->
|
||||
overridable(Module, orddict:store(Function, { Count, Clause, Neighbours, true }, Overridable)),
|
||||
{ { { Name, Arity }, Kind, Line, File, _Check, Location, Defaults }, Clauses } = Clause,
|
||||
|
||||
{ FinalKind, FinalName } = case GenerateName of
|
||||
@@ -72,6 +71,8 @@ store(Module, Function, GenerateName) ->
|
||||
false -> { Kind, Name }
|
||||
end,
|
||||
|
||||
'Elixir.Module.DispatchTracker':reattach(Module, Kind, { Name, Arity }, Neighbours),
|
||||
|
||||
Def = { function, Line, FinalName, Arity, Clauses },
|
||||
elixir_def:store_each(false, FinalKind, File, Location,
|
||||
elixir_def:table(Module), elixir_def:clauses_table(Module), Defaults, Def)
|
||||
@@ -80,13 +81,13 @@ store(Module, Function, GenerateName) ->
|
||||
%% Store pending declarations that were not manually made concrete.
|
||||
|
||||
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)].
|
||||
|
||||
%% Error handling
|
||||
|
||||
format_error({ no_super, Module, { Name, Arity } }) ->
|
||||
Bins = [ format_fa(X) || { X, { _, _, _ } } <- overridable(Module)],
|
||||
Bins = [format_fa(X) || { X, { _, _, _, _ } } <- overridable(Module)],
|
||||
Joined = 'Elixir.Enum':join(Bins, <<", ">>),
|
||||
io_lib:format("no super defined for ~ts/~B in module ~ts. Overridable functions available are: ~ts",
|
||||
[Name, Arity, elixir_errors:inspect(Module), Joined]).
|
||||
|
||||
@@ -8,17 +8,16 @@
|
||||
expand_import/6, expand_require/6, find_import/4,
|
||||
format_error/1, in_erlang_functions/0, in_erlang_macros/0]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
-import(ordsets, [is_element/2]).
|
||||
-define(BUILTIN, 'Elixir.Kernel').
|
||||
-define(builtin, 'Elixir.Kernel').
|
||||
|
||||
default_functions() ->
|
||||
[ { ?BUILTIN, ordsets:union(in_elixir_functions(), in_erlang_functions()) } ].
|
||||
[ { ?builtin, ordsets:union(in_elixir_functions(), in_erlang_functions()) } ].
|
||||
default_macros() ->
|
||||
[ { ?BUILTIN, ordsets:union(in_elixir_macros(), in_erlang_macros()) } ].
|
||||
[ { ?builtin, ordsets:union(in_elixir_macros(), in_erlang_macros()) } ].
|
||||
default_requires() ->
|
||||
[ ?BUILTIN, 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
|
||||
[ ?builtin, 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
|
||||
|
||||
find_import(Meta, Name, Arity, S) ->
|
||||
Tuple = { Name, Arity },
|
||||
@@ -35,15 +34,14 @@ import_function(Meta, Name, Arity, S) ->
|
||||
Tuple = { Name, Arity },
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_import:record(Tuple, Receiver, S#elixir_scope.module),
|
||||
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
remote_function(Meta, Receiver, Name, Arity, S);
|
||||
{ macro, _Receiver } ->
|
||||
false;
|
||||
{ import, Receiver } ->
|
||||
require_function(Meta, Receiver, Name, Arity, S);
|
||||
nomatch ->
|
||||
Module = S#elixir_scope.module,
|
||||
elixir_import:record({ Name, Arity }, Module, Module),
|
||||
elixir_tracker:record_local(Tuple, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
|
||||
end.
|
||||
|
||||
@@ -52,7 +50,9 @@ require_function(Meta, Receiver, Name, Arity, S) ->
|
||||
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true -> false;
|
||||
false -> remote_function(Meta, Receiver, Name, Arity, S)
|
||||
false ->
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
remote_function(Meta, Receiver, Name, Arity, S)
|
||||
end.
|
||||
|
||||
%% Function dispatch
|
||||
@@ -64,20 +64,21 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
|
||||
|
||||
case find_dispatch(Meta, Tuple, S) of
|
||||
{ function, Receiver } ->
|
||||
elixir_import:record(Tuple, Receiver, Module),
|
||||
Endpoint = case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
|
||||
Endpoint = case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
elixir_translator:translate_each({ { '.', Meta, [Endpoint, Name] }, Meta, Args }, S);
|
||||
{ import, Receiver } ->
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_translator:translate_each({ { '.', Meta, [Receiver, Name] }, [{require,false}|Meta], Args }, S);
|
||||
Result ->
|
||||
case do_expand_import(Meta, Tuple, Args, Module, S, Result) of
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_import:record(Tuple, ?BUILTIN, Module),
|
||||
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
|
||||
elixir_macros:translate({ Name, Meta, Args }, S);
|
||||
{ ok, _Receiver, Tree } ->
|
||||
translate_expansion(Meta, Tree, S)
|
||||
@@ -89,8 +90,9 @@ dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
|
||||
Arity = length(Args),
|
||||
Tuple = { Name, Arity },
|
||||
|
||||
case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
|
||||
true ->
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ TArgs, SA } = elixir_translator:translate_args(Args, S),
|
||||
{ ?wrap_call(?line(Meta), erlang, Name, TArgs), SA };
|
||||
false ->
|
||||
@@ -98,6 +100,7 @@ dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
|
||||
{ error, noexpansion } ->
|
||||
Callback();
|
||||
{ error, internal } ->
|
||||
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
|
||||
elixir_macros:translate({ Name, Meta, Args }, S);
|
||||
{ ok, _Receiver, Tree } ->
|
||||
translate_expansion(Meta, Tree, S)
|
||||
@@ -112,21 +115,21 @@ expand_import(Meta, Tuple, Args, Module, Extra, S) ->
|
||||
|
||||
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
||||
case Result of
|
||||
{ macro, ?BUILTIN } ->
|
||||
{ macro, ?builtin } ->
|
||||
case is_element(Tuple, in_erlang_macros()) of
|
||||
true -> { error, internal };
|
||||
false ->
|
||||
elixir_import:record(Tuple, ?BUILTIN, Module),
|
||||
{ ok, ?BUILTIN, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, S) }
|
||||
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
|
||||
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) }
|
||||
end;
|
||||
{ macro, Receiver } ->
|
||||
elixir_import:record(Tuple, Receiver, Module),
|
||||
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
{ import, Receiver } ->
|
||||
expand_require(Meta, Receiver, Tuple, Args, Module, S);
|
||||
_ ->
|
||||
Fun = (S#elixir_scope.function /= Tuple) andalso
|
||||
elixir_def_local:macro_for(Tuple, true, Module),
|
||||
elixir_def_local:macro_for(Tuple, true, S),
|
||||
case Fun of
|
||||
false -> { error, noexpansion };
|
||||
_ ->
|
||||
@@ -134,24 +137,29 @@ do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
||||
end
|
||||
end.
|
||||
|
||||
expand_require(Meta, ?BUILTIN, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
expand_require(Meta, ?builtin, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
case is_element(Tuple, in_erlang_macros()) of
|
||||
true -> { error, internal };
|
||||
false ->
|
||||
case is_element(Tuple, in_elixir_macros()) of
|
||||
true -> { ok, ?BUILTIN, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, S) };
|
||||
false -> { error, noexpansion }
|
||||
true ->
|
||||
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) };
|
||||
false ->
|
||||
{ error, noexpansion }
|
||||
end
|
||||
end;
|
||||
|
||||
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, S) ->
|
||||
Fun = (Module == Receiver) andalso (S#elixir_scope.function /= Tuple) andalso
|
||||
elixir_def_local:macro_for(Tuple, false, Module),
|
||||
elixir_def_local:macro_for(Tuple, false, S),
|
||||
|
||||
case Fun of
|
||||
false ->
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
true -> { ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
true ->
|
||||
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
||||
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
||||
false -> { error, noexpansion }
|
||||
end;
|
||||
_ ->
|
||||
@@ -261,6 +269,22 @@ munge_stacktrace(Info, [H|T], S) ->
|
||||
munge_stacktrace(_, [], _) ->
|
||||
[].
|
||||
|
||||
remote_function(Meta, Receiver, Name, Arity, S) ->
|
||||
Line = ?line(Meta),
|
||||
|
||||
Final =
|
||||
case (Receiver == ?builtin) andalso is_element({ Name, Arity }, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
|
||||
{ { 'fun', Line, { function,
|
||||
{ atom, Line, Final },
|
||||
{ atom, Line, Name },
|
||||
{ integer, Line, Arity}
|
||||
} }, S }.
|
||||
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({ unrequired_module, { Receiver, Name, Arity, Required }}) ->
|
||||
@@ -274,21 +298,6 @@ format_error({ ambiguous_call, { Mod1, Mod2, Name, Arity }}) ->
|
||||
|
||||
%% INTROSPECTION
|
||||
|
||||
remote_function(Meta, Receiver, Name, Arity, S) ->
|
||||
Line = ?line(Meta),
|
||||
|
||||
Final =
|
||||
case (Receiver == ?BUILTIN) andalso is_element({ Name, Arity }, in_erlang_functions()) of
|
||||
true -> erlang;
|
||||
false -> Receiver
|
||||
end,
|
||||
|
||||
{ { 'fun', Line, { function,
|
||||
{ atom, Line, Final },
|
||||
{ atom, Line, Name },
|
||||
{ integer, Line, Arity}
|
||||
} }, S }.
|
||||
|
||||
%% Do not try to get macros from Erlang. Speeds up compilation a bit.
|
||||
get_optional_macros(erlang) -> [];
|
||||
|
||||
@@ -307,7 +316,7 @@ get_optional_macros(Receiver) ->
|
||||
|
||||
in_elixir_functions() ->
|
||||
try
|
||||
?BUILTIN:'__info__'(functions)
|
||||
?builtin:'__info__'(functions)
|
||||
catch
|
||||
error:undef -> []
|
||||
end.
|
||||
@@ -316,7 +325,7 @@ in_elixir_functions() ->
|
||||
|
||||
in_elixir_macros() ->
|
||||
try
|
||||
?BUILTIN:'__info__'(macros)
|
||||
?builtin:'__info__'(macros)
|
||||
catch
|
||||
error:undef -> []
|
||||
end.
|
||||
@@ -330,6 +339,10 @@ in_erlang_functions() ->
|
||||
{ binary_part, 3 },
|
||||
{ binary_to_atom, 2 },
|
||||
{ binary_to_existing_atom, 2 },
|
||||
{ binary_to_float, 1 },
|
||||
{ binary_to_float, 2 },
|
||||
{ binary_to_integer, 1 },
|
||||
{ binary_to_integer, 2 },
|
||||
{ binary_to_list, 1 },
|
||||
{ binary_to_list, 3 },
|
||||
{ binary_to_term, 1 },
|
||||
@@ -340,8 +353,11 @@ in_erlang_functions() ->
|
||||
% { date, 0 },
|
||||
{ exit, 1 },
|
||||
{ float, 1 },
|
||||
{ float_to_binary, 1 },
|
||||
{ float_to_list, 1 },
|
||||
{ hd, 1 },
|
||||
{ integer_to_binary, 1 },
|
||||
{ integer_to_binary, 2 },
|
||||
{ integer_to_list, 1 },
|
||||
{ integer_to_list, 2 },
|
||||
{ iolist_size, 1 },
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
handle_file_warning/2, handle_file_warning/3, handle_file_error/2,
|
||||
deprecation/3, deprecation/4, file_format/3]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Handle inspecting for exceptions
|
||||
|
||||
|
||||
@@ -1,284 +0,0 @@
|
||||
%% Modified version of filelib:wildcard that can handle "**"
|
||||
%% and automatically skips directories starting with "." by default.
|
||||
|
||||
%% %CopyrightBegin%
|
||||
%%
|
||||
%% Copyright Ericsson AB 1997-2010. All Rights Reserved.
|
||||
%%
|
||||
%% The contents of this file are subject to the Erlang Public License,
|
||||
%% Version 1.1, (the "License"); you may not use this file except in
|
||||
%% compliance with the License. You should have received a copy of the
|
||||
%% Erlang Public License along with this software. If not, it can be
|
||||
%% retrieved online at http://www.erlang.org/.
|
||||
%%
|
||||
%% Software distributed under the License is distributed on an "AS IS"
|
||||
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
%% the License for the specific language governing rights and limitations
|
||||
%% under the License.
|
||||
%%
|
||||
%% %CopyrightEnd%
|
||||
|
||||
-module(elixir_glob).
|
||||
-compile({no_auto_import,[error/1]}).
|
||||
-export([wildcard/1]).
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
-define(HANDLE_ERROR(Expr),
|
||||
try
|
||||
Expr
|
||||
catch
|
||||
error:{badpattern,_}=UnUsUalVaRiAbLeNaMe ->
|
||||
%% Get the stack backtrace correct.
|
||||
erlang:error(UnUsUalVaRiAbLeNaMe)
|
||||
end).
|
||||
|
||||
wildcard(Pattern) when is_list(Pattern) ->
|
||||
?HANDLE_ERROR(do_wildcard(Pattern, file)).
|
||||
|
||||
do_wildcard(Pattern, Mod) when is_list(Pattern) ->
|
||||
do_wildcard_comp(do_compile_wildcard(Pattern), Mod).
|
||||
|
||||
do_wildcard_comp({compiled_wildcard,{exists,File}}, Mod) ->
|
||||
case eval_read_file_info(File, Mod) of
|
||||
{ok,_} -> [File];
|
||||
_ -> []
|
||||
end;
|
||||
do_wildcard_comp({compiled_wildcard,[Base|Rest]}, Mod) ->
|
||||
do_wildcard_1([Base], Rest, Mod).
|
||||
|
||||
%%%
|
||||
%%% Pattern matching using a compiled wildcard.
|
||||
%%%
|
||||
|
||||
do_wildcard_1(Files, Pattern, Mod) ->
|
||||
do_wildcard_2(Files, Pattern, [], Mod).
|
||||
|
||||
do_wildcard_2([File|Rest], Pattern, Result, Mod) ->
|
||||
do_wildcard_2(Rest, Pattern, do_wildcard_3(File, Pattern, Result, Mod), Mod);
|
||||
do_wildcard_2([], _, Result, _Mod) ->
|
||||
Result.
|
||||
|
||||
do_wildcard_3(Base, [[double_star]|Rest], Result, Mod) ->
|
||||
lists:sort(do_double_star(current, [Base], Rest, Result, Mod, true));
|
||||
do_wildcard_3(Base, [Pattern|Rest], Result, Mod) ->
|
||||
case do_list_dir(Base, Mod) of
|
||||
{ok, Files0} ->
|
||||
Files = lists:sort(Files0),
|
||||
Matches = wildcard_4(Pattern, Files, Base, []),
|
||||
do_wildcard_2(Matches, Rest, Result, Mod);
|
||||
_ ->
|
||||
Result
|
||||
end;
|
||||
do_wildcard_3(Base, [], Result, _Mod) ->
|
||||
[Base|Result].
|
||||
|
||||
wildcard_4(Pattern, [File|Rest], Base, Result) when is_binary(File) ->
|
||||
case wildcard_5(Pattern, binary_to_list(File)) of
|
||||
true ->
|
||||
wildcard_4(Pattern, Rest, Base, [join(Base, File)|Result]);
|
||||
false ->
|
||||
wildcard_4(Pattern, Rest, Base, Result)
|
||||
end;
|
||||
wildcard_4(Pattern, [File|Rest], Base, Result) ->
|
||||
case wildcard_5(Pattern, File) of
|
||||
true ->
|
||||
wildcard_4(Pattern, Rest, Base, [join(Base, File)|Result]);
|
||||
false ->
|
||||
wildcard_4(Pattern, Rest, Base, Result)
|
||||
end;
|
||||
wildcard_4(_Patt, [], _Base, Result) ->
|
||||
Result.
|
||||
|
||||
wildcard_5([question|Rest1], [_|Rest2]) ->
|
||||
wildcard_5(Rest1, Rest2);
|
||||
wildcard_5([accept], _) ->
|
||||
true;
|
||||
wildcard_5([double_star], _) ->
|
||||
true;
|
||||
wildcard_5([star|Rest], File) ->
|
||||
do_star(Rest, File);
|
||||
wildcard_5([{one_of, Ordset}|Rest], [C|File]) ->
|
||||
case ordsets:is_element(C, Ordset) of
|
||||
true -> wildcard_5(Rest, File);
|
||||
false -> false
|
||||
end;
|
||||
wildcard_5([{alt, Alts}], File) ->
|
||||
do_alt(Alts, File);
|
||||
wildcard_5([C|Rest1], [C|Rest2]) when is_integer(C) ->
|
||||
wildcard_5(Rest1, Rest2);
|
||||
wildcard_5([X|_], [Y|_]) when is_integer(X), is_integer(Y) ->
|
||||
false;
|
||||
wildcard_5([], []) ->
|
||||
true;
|
||||
wildcard_5([], [_|_]) ->
|
||||
false;
|
||||
wildcard_5([_|_], []) ->
|
||||
false.
|
||||
|
||||
do_double_star(Base, [H|T], Rest, Result, Mod, Root) ->
|
||||
Full = join(Base, H),
|
||||
Result1 = case do_list_dir(Full, Mod) of
|
||||
{ok, Files} ->
|
||||
do_double_star(Full, Files, Rest, Result, Mod, false);
|
||||
_ -> Result
|
||||
end,
|
||||
Result2 = case Root andalso Rest == [] of
|
||||
true -> Result1;
|
||||
false -> do_wildcard_3(Full, Rest, Result1, Mod)
|
||||
end,
|
||||
do_double_star(Base, T, Rest, Result2, Mod, Root);
|
||||
do_double_star(_Base, [], _Rest, Result, _Mod, _Root) ->
|
||||
Result.
|
||||
|
||||
do_star(Pattern, [X|Rest]) ->
|
||||
case wildcard_5(Pattern, [X|Rest]) of
|
||||
true -> true;
|
||||
false -> do_star(Pattern, Rest)
|
||||
end;
|
||||
do_star(Pattern, []) ->
|
||||
wildcard_5(Pattern, []).
|
||||
|
||||
do_alt([Alt|Rest], File) ->
|
||||
case wildcard_5(Alt, File) of
|
||||
true -> true;
|
||||
false -> do_alt(Rest, File)
|
||||
end;
|
||||
do_alt([], _File) ->
|
||||
false.
|
||||
|
||||
do_list_dir(current, Mod) -> eval_list_dir(".", Mod);
|
||||
do_list_dir(Dir, Mod) -> eval_list_dir(Dir, Mod).
|
||||
|
||||
join(current, File) -> File;
|
||||
join(Base, File) -> filename:join(Base, File).
|
||||
|
||||
|
||||
%%% Compiling a wildcard.
|
||||
|
||||
do_compile_wildcard(Pattern) ->
|
||||
{compiled_wildcard,compile_wildcard_1(Pattern)}.
|
||||
|
||||
compile_wildcard_1(Pattern) ->
|
||||
[Root|Rest] = filename:split(Pattern),
|
||||
case filename:pathtype(Root) of
|
||||
relative ->
|
||||
compile_wildcard_2([Root|Rest], current);
|
||||
_ ->
|
||||
compile_wildcard_2(Rest, [Root])
|
||||
end.
|
||||
|
||||
compile_wildcard_2([Part|Rest], Root) ->
|
||||
case compile_part(Part) of
|
||||
Part ->
|
||||
compile_wildcard_2(Rest, join(Root, Part));
|
||||
Pattern ->
|
||||
compile_wildcard_3(Rest, [Pattern,Root])
|
||||
end;
|
||||
compile_wildcard_2([], Root) -> {exists,Root}.
|
||||
|
||||
compile_wildcard_3([Part|Rest], Result) ->
|
||||
compile_wildcard_3(Rest, [compile_part(Part)|Result]);
|
||||
compile_wildcard_3([], Result) ->
|
||||
lists:reverse(Result).
|
||||
|
||||
compile_part(Part) ->
|
||||
compile_part(Part, false, []).
|
||||
|
||||
compile_part_to_sep(Part) ->
|
||||
compile_part(Part, true, []).
|
||||
|
||||
compile_part([], true, _) ->
|
||||
error(missing_delimiter);
|
||||
compile_part([$,|Rest], true, Result) ->
|
||||
{ok, $,, lists:reverse(Result), Rest};
|
||||
compile_part([$}|Rest], true, Result) ->
|
||||
{ok, $}, lists:reverse(Result), Rest};
|
||||
compile_part([$?|Rest], Upto, Result) ->
|
||||
compile_part(Rest, Upto, [question|Result]);
|
||||
compile_part([$*,$*], Upto, Result) ->
|
||||
compile_part([], Upto, [double_star|Result]);
|
||||
compile_part([$*,$*|Rest], Upto, Result) ->
|
||||
compile_part(Rest, Upto, [star|Result]);
|
||||
compile_part([$*], Upto, Result) ->
|
||||
compile_part([], Upto, [accept|Result]);
|
||||
compile_part([$*|Rest], Upto, Result) ->
|
||||
compile_part(Rest, Upto, [star|Result]);
|
||||
compile_part([$[|Rest], Upto, Result) ->
|
||||
case compile_charset(Rest, ordsets:new()) of
|
||||
{ok, Charset, Rest1} ->
|
||||
compile_part(Rest1, Upto, [Charset|Result]);
|
||||
error ->
|
||||
compile_part(Rest, Upto, [$[|Result])
|
||||
end;
|
||||
compile_part([${|Rest], Upto, Result) ->
|
||||
case compile_alt(Rest) of
|
||||
{ok, Alt} ->
|
||||
lists:reverse(Result, [Alt]);
|
||||
error ->
|
||||
compile_part(Rest, Upto, [${|Result])
|
||||
end;
|
||||
compile_part([X|Rest], Upto, Result) ->
|
||||
compile_part(Rest, Upto, [X|Result]);
|
||||
compile_part([], _Upto, Result) ->
|
||||
lists:reverse(Result).
|
||||
|
||||
compile_charset([$]|Rest], Ordset) ->
|
||||
compile_charset1(Rest, ordsets:add_element($], Ordset));
|
||||
compile_charset([$-|Rest], Ordset) ->
|
||||
compile_charset1(Rest, ordsets:add_element($-, Ordset));
|
||||
compile_charset([], _Ordset) ->
|
||||
error;
|
||||
compile_charset(List, Ordset) ->
|
||||
compile_charset1(List, Ordset).
|
||||
|
||||
compile_charset1([Lower, $-, Upper|Rest], Ordset) when Lower =< Upper ->
|
||||
compile_charset1(Rest, compile_range(Lower, Upper, Ordset));
|
||||
compile_charset1([$]|Rest], Ordset) ->
|
||||
{ok, {one_of, Ordset}, Rest};
|
||||
compile_charset1([X|Rest], Ordset) ->
|
||||
compile_charset1(Rest, ordsets:add_element(X, Ordset));
|
||||
compile_charset1([], _Ordset) ->
|
||||
error.
|
||||
|
||||
compile_range(Lower, Current, Ordset) when Lower =< Current ->
|
||||
compile_range(Lower, Current-1, ordsets:add_element(Current, Ordset));
|
||||
compile_range(_, _, Ordset) ->
|
||||
Ordset.
|
||||
|
||||
compile_alt(Pattern) ->
|
||||
compile_alt(Pattern, []).
|
||||
|
||||
compile_alt(Pattern, Result) ->
|
||||
case compile_part_to_sep(Pattern) of
|
||||
{ok, $,, AltPattern, Rest} ->
|
||||
compile_alt(Rest, [AltPattern|Result]);
|
||||
{ok, $}, AltPattern, Rest} ->
|
||||
NewResult = [AltPattern|Result],
|
||||
RestPattern = compile_part(Rest),
|
||||
{ok, {alt, [Alt++RestPattern || Alt <- NewResult]}};
|
||||
Pattern ->
|
||||
error
|
||||
end.
|
||||
|
||||
error(Reason) ->
|
||||
erlang:error({badpattern,Reason}).
|
||||
|
||||
eval_read_file_info(File, file) ->
|
||||
file:read_file_info(File);
|
||||
eval_read_file_info(File, erl_prim_loader) ->
|
||||
case erl_prim_loader:read_file_info(File) of
|
||||
error -> {error, erl_prim_loader};
|
||||
Res-> Res
|
||||
end;
|
||||
eval_read_file_info(File, Mod) ->
|
||||
Mod:read_file_info(File).
|
||||
|
||||
eval_list_dir(Dir, file) ->
|
||||
file:list_dir(Dir);
|
||||
eval_list_dir(Dir, erl_prim_loader) ->
|
||||
case erl_prim_loader:list_dir(Dir) of
|
||||
error -> {error, erl_prim_loader};
|
||||
Res-> Res
|
||||
end;
|
||||
eval_list_dir(Dir, Mod) ->
|
||||
Mod:list_dir(Dir).
|
||||
@@ -2,51 +2,11 @@
|
||||
%% in between local functions and imports.
|
||||
%% For imports dispatch, please check elixir_dispatch.
|
||||
-module(elixir_import).
|
||||
-export([import/5, recorded_locals/1, format_error/1,
|
||||
ensure_no_import_conflict/4, ensure_no_local_conflict/4,
|
||||
ensure_all_imports_used/3,
|
||||
build_table/1, delete_table/1, record/3]).
|
||||
-export([import/5, format_error/1,
|
||||
ensure_no_local_conflict/4]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
table(Module) -> ?atom_concat([i, Module]).
|
||||
|
||||
build_table(Module) ->
|
||||
ets:new(table(Module), [bag, named_table, public]).
|
||||
|
||||
delete_table(Module) ->
|
||||
ets:delete(table(Module)).
|
||||
|
||||
record(_Tuple, _Receiver, nil) ->
|
||||
false;
|
||||
|
||||
record(Tuple, Receiver, Module) ->
|
||||
try
|
||||
ets:insert(table(Module), { Tuple, Receiver })
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
record_warn(_Meta, _Ref, _Opts, #elixir_scope{module=nil}) -> false;
|
||||
|
||||
record_warn(Meta, Ref, Opts, S) ->
|
||||
Table = table(S#elixir_scope.module),
|
||||
ets:delete(Table, Ref),
|
||||
|
||||
Warn =
|
||||
case keyfind(warn, Opts) of
|
||||
{ warn, false } -> false;
|
||||
{ warn, true } -> true;
|
||||
false -> not lists:keymember(context, 1, Meta)
|
||||
end,
|
||||
|
||||
Warn andalso ets:insert(Table, { Ref, ?line(Meta) }).
|
||||
|
||||
recorded_locals(Module) ->
|
||||
Table = table(Module),
|
||||
Match = { '$1', Module },
|
||||
Result = ets:match(Table, Match),
|
||||
ets:match_delete(Table, Match),
|
||||
lists:append(Result).
|
||||
%% IMPORT HELPERS
|
||||
|
||||
%% Update the scope to consider the imports for aliases
|
||||
%% based on the given options and selector.
|
||||
@@ -80,7 +40,16 @@ import(Meta, Ref, Opts, Selector, S) ->
|
||||
record_warn(Meta, Ref, Opts, S),
|
||||
SM.
|
||||
|
||||
%% IMPORT FUNCTION RELATED HELPERS
|
||||
record_warn(_Meta, _Ref, _Opts, #elixir_scope{module=nil}) -> false;
|
||||
record_warn(Meta, Ref, Opts, #elixir_scope{module=Module}) ->
|
||||
Warn =
|
||||
case keyfind(warn, Opts) of
|
||||
{ warn, false } -> false;
|
||||
{ warn, true } -> true;
|
||||
false -> not lists:keymember(context, 1, Meta)
|
||||
end,
|
||||
|
||||
elixir_tracker:record_warn(Ref, Warn, ?line(Meta), Module).
|
||||
|
||||
%% Calculates the imports based on only and except
|
||||
|
||||
@@ -88,8 +57,7 @@ calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
|
||||
File = S#elixir_scope.file,
|
||||
|
||||
New = case keyfind(only, Opts) of
|
||||
{ only, RawOnly } ->
|
||||
Only = expand_fun_arity(Meta, only, RawOnly, S),
|
||||
{ only, Only } ->
|
||||
case Only -- get_exports(Key) of
|
||||
[{Name,Arity}|_] ->
|
||||
Tuple = { invalid_import, { Key, Name, Arity } },
|
||||
@@ -101,8 +69,7 @@ calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
|
||||
case keyfind(except, Opts) of
|
||||
false -> AvailableFun(true);
|
||||
{ except, [] } -> AvailableFun(true);
|
||||
{ except, RawExcept } ->
|
||||
Except = expand_fun_arity(Meta, except, RawExcept, S),
|
||||
{ except, Except } ->
|
||||
case keyfind(Key, Old) of
|
||||
false -> AvailableFun(true) -- Except;
|
||||
{Key,OldImports} -> OldImports -- Except
|
||||
@@ -134,29 +101,6 @@ if_quoted(Meta, Temp, Callback) ->
|
||||
Temp
|
||||
end.
|
||||
|
||||
%% Ensure we are expanding macros and stuff
|
||||
|
||||
expand_fun_arity(Meta, Kind, Value, S) ->
|
||||
{ TValue, _S } = elixir_translator:translate_each(Value, S),
|
||||
cons_to_keywords(Meta, Kind, TValue, S).
|
||||
|
||||
cons_to_keywords(Meta, Kind, { cons, _, Left, { nil, _ } }, S) ->
|
||||
[tuple_to_fun_arity(Meta, Kind, Left, S)];
|
||||
|
||||
cons_to_keywords(Meta, Kind, { cons, _, Left, Right }, S) ->
|
||||
[tuple_to_fun_arity(Meta, Kind, Left, S)|cons_to_keywords(Meta, Kind, Right, S)];
|
||||
|
||||
cons_to_keywords(Meta, Kind, _, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid value for :~ts, expected a list with functions and arities", [Kind]).
|
||||
|
||||
tuple_to_fun_arity(_Meta, _Kind, { tuple, _, [{ atom, _, Atom }, { integer, _, Integer }] }, _S) ->
|
||||
{ Atom, Integer };
|
||||
|
||||
tuple_to_fun_arity(Meta, Kind, _, S) ->
|
||||
elixir_errors:syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid value for :~ts, expected a list with functions and arities", [Kind]).
|
||||
|
||||
%% Retrieve functions and macros from modules
|
||||
|
||||
get_exports(Module) ->
|
||||
@@ -196,38 +140,11 @@ get_optional_macros(Module) ->
|
||||
%% VALIDATION HELPERS
|
||||
|
||||
%% Check if any of the locals defined conflicts with an invoked
|
||||
%% Elixir "implemented in Erlang" macro. Checking if a local
|
||||
%% conflicts with an import is automatically done by Erlang.
|
||||
%% Elixir "implemented in Erlang" macro.
|
||||
|
||||
ensure_no_local_conflict(Meta, File, Module, AllDefined) ->
|
||||
ensure_no_special_form_conflict(Meta, File, Module, AllDefined, local_conflict).
|
||||
|
||||
%% Find conlicts in the given list of functions with
|
||||
%% the recorded set of imports.
|
||||
|
||||
ensure_no_import_conflict(Meta, File, Module, AllDefined) ->
|
||||
Table = table(Module),
|
||||
Matches = [X || X <- AllDefined, ets:member(Table, X)],
|
||||
|
||||
case Matches of
|
||||
[{Name,Arity}|_] ->
|
||||
Key = ets:lookup_element(Table, { Name, Arity }, 2),
|
||||
Tuple = { import_conflict, { hd(Key), Name, Arity } },
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
|
||||
[] ->
|
||||
ok
|
||||
end.
|
||||
|
||||
ensure_all_imports_used(_Line, File, Module) ->
|
||||
Table = table(Module),
|
||||
[begin
|
||||
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_import, M } })
|
||||
end || [M, L] <- ets:select(Table, module_line_spec()),
|
||||
ets:match(Table, { '$1', M }) == []].
|
||||
|
||||
module_line_spec() ->
|
||||
[{ { '$1', '$2' }, [{ is_integer, '$2' }], ['$$'] }].
|
||||
|
||||
%% Ensure the given functions don't clash with any
|
||||
%% of Elixir non overridable macros.
|
||||
|
||||
@@ -247,17 +164,10 @@ ensure_no_special_form_conflict(_Meta, _File, _Key, [], _) -> ok.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
format_error({already_imported,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("function ~ts/~B already imported from ~ts", [Name, Arity, elixir_errors:inspect(Receiver)]);
|
||||
|
||||
format_error({invalid_import,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("cannot import ~ts.~ts/~B because it doesn't exist",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({import_conflict,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("imported ~ts.~ts/~B conflicts with local function",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({local_conflict,{_, Name, Arity}}) ->
|
||||
io_lib:format("cannot define local ~ts/~B because it conflicts with Elixir special forms", [Name, Arity]);
|
||||
|
||||
@@ -265,9 +175,6 @@ format_error({internal_conflict,{Receiver, Name, Arity}}) ->
|
||||
io_lib:format("cannot import ~ts.~ts/~B because it conflicts with Elixir special forms",
|
||||
[elixir_errors:inspect(Receiver), Name, Arity]);
|
||||
|
||||
format_error({ unused_import, Module }) ->
|
||||
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]);
|
||||
|
||||
format_error({ no_macros, Module }) ->
|
||||
io_lib:format("could not load macros from module ~ts", [elixir_errors:inspect(Module)]).
|
||||
|
||||
@@ -287,8 +194,6 @@ intersection([H|T], All) ->
|
||||
|
||||
intersection([], _All) -> [].
|
||||
|
||||
%% INTROSPECTION
|
||||
|
||||
%% Internal funs that are never imported etc.
|
||||
|
||||
remove_underscored(default, List) -> remove_underscored(List);
|
||||
@@ -308,7 +213,7 @@ remove_internals(Set) ->
|
||||
ordsets:del_element({ module_info, 1 },
|
||||
ordsets:del_element({ module_info, 0 }, Set)).
|
||||
|
||||
%% Macros implemented in Erlang that are not overridable.
|
||||
%% Macros implemented in Erlang that are not importable.
|
||||
|
||||
special_form() ->
|
||||
[
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
-export([extract/5, unescape_chars/1, unescape_chars/2,
|
||||
unescape_tokens/1, unescape_tokens/2, unescape_map/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Extract string interpolations
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
%% Handle translation of Elixir literals to Erlang AST.
|
||||
-module(elixir_literal).
|
||||
-export([translate/2]).
|
||||
-import(elixir_translator, [translate_each/2, translate_args/2]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
translate({ '<<>>', Meta, Args } = Original, S) when is_list(Args) ->
|
||||
case elixir_partials:handle(Original, S, allow_tail) of
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
-include("elixir.hrl").
|
||||
-define(opt_in_types(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
%% Operators
|
||||
|
||||
@@ -148,12 +147,11 @@ translate({'case', Meta, [Expr, KV]}, S) ->
|
||||
|
||||
%% Try
|
||||
|
||||
translate({'try', Meta, [Clauses]}, RawS) ->
|
||||
S = RawS#elixir_scope{noname=true},
|
||||
translate({'try', Meta, [Clauses]}, S) ->
|
||||
assert_no_match_or_guard_scope(Meta, 'try', S),
|
||||
|
||||
Do = proplists:get_value('do', Clauses, nil),
|
||||
{ TDo, SB } = elixir_translator:translate_each(Do, S),
|
||||
{ TDo, SB } = elixir_translator:translate_each(Do, S#elixir_scope{noname=true}),
|
||||
|
||||
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
|
||||
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, umergec(S, SB)),
|
||||
@@ -164,7 +162,8 @@ translate({'try', Meta, [Clauses]}, RawS) ->
|
||||
Else = elixir_clauses:get_pairs(Meta, else, Clauses, S),
|
||||
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergec(S, SA)),
|
||||
|
||||
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, umergec(RawS, SE) };
|
||||
SF = (umergec(S, SE))#elixir_scope{noname=S#elixir_scope.noname},
|
||||
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, SF };
|
||||
|
||||
%% Receive
|
||||
|
||||
@@ -298,7 +297,7 @@ translate_in(Meta, Left, Right, S) ->
|
||||
_ ->
|
||||
case Cache of
|
||||
true ->
|
||||
{ false, ?wrap_call(Line, 'Elixir-Enum', 'member?', [TRight, TLeft]) };
|
||||
{ false, ?wrap_call(Line, 'Elixir.Enum', 'member?', [TRight, TLeft]) };
|
||||
false ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects an explicit array or an explicit range on the right side when used in guard expressions")
|
||||
end
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
-module(elixir_module).
|
||||
-export([translate/4, compile/5, data_table/1, eval_quoted/4,
|
||||
format_error/1, eval_callbacks/5]).
|
||||
-export([translate/4, compile/5, data_table/1, docs_table/1,
|
||||
eval_quoted/4, format_error/1, eval_callbacks/5]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
-define(docs_attr, '__docs_table').
|
||||
-define(acc_attr, '__acc_attributes').
|
||||
-define(persisted_attr, '__persisted_attributes').
|
||||
|
||||
eval_quoted(Module, Quoted, RawBinding, Opts) ->
|
||||
Binding = binding_for_eval(Module, RawBinding),
|
||||
@@ -32,7 +35,7 @@ data_table(Module) ->
|
||||
Module.
|
||||
|
||||
docs_table(Module) ->
|
||||
?atom_concat([o, Module]).
|
||||
ets:lookup_element(Module, ?docs_attr, 2).
|
||||
|
||||
%% TRANSFORMATION FUNCTIONS
|
||||
|
||||
@@ -71,9 +74,17 @@ compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} =
|
||||
Forms1 = specs_form(Line, Module, Private, Defmacro, Forms0, C),
|
||||
Forms2 = attributes_form(Line, File, Module, Forms1),
|
||||
|
||||
elixir_import:ensure_all_imports_used(Line, File, Module),
|
||||
case ets:lookup(data_table(Module), 'on_load') of
|
||||
[] -> ok;
|
||||
[{on_load,OnLoad}] ->
|
||||
[elixir_tracker:record_local(Tuple, Module) || Tuple <- OnLoad]
|
||||
end,
|
||||
|
||||
elixir_tracker:warn_unused_local(File, Module, Private),
|
||||
elixir_tracker:ensure_no_import_conflict(Line, File, Module, All),
|
||||
elixir_tracker:ensure_all_imports_used(Line, File, Module),
|
||||
|
||||
elixir_import:ensure_no_local_conflict(Line, File, Module, All),
|
||||
elixir_import:ensure_no_import_conflict(Line, File, Module, All),
|
||||
|
||||
Final = [
|
||||
{ attribute, Line, file, { FileList, Line } },
|
||||
@@ -83,10 +94,10 @@ compile(Line, Module, Block, Vars, #elixir_scope{context_modules=FileModules} =
|
||||
Binary = load_form(Line, Final, S),
|
||||
{ module, Module, Binary, Result }
|
||||
after
|
||||
ets:delete(data_table(Module)),
|
||||
elixir_tracker:cleanup(Module),
|
||||
elixir_def:cleanup(Module),
|
||||
ets:delete(docs_table(Module)),
|
||||
elixir_def:delete_table(Module),
|
||||
elixir_import:delete_table(Module)
|
||||
ets:delete(data_table(Module))
|
||||
end;
|
||||
|
||||
compile(Line, Other, _Block, _Vars, #elixir_scope{file=File}) ->
|
||||
@@ -119,20 +130,13 @@ build(Line, File, Module) ->
|
||||
end,
|
||||
|
||||
Attributes = [behavior, behaviour, on_load, spec, type, export_type, opaque, callback, compile],
|
||||
ets:insert(DataTable, { '__acc_attributes', [before_compile,after_compile,on_definition|Attributes] }),
|
||||
ets:insert(DataTable, { '__persisted_attributes', [vsn|Attributes] }),
|
||||
ets:insert(DataTable, { ?acc_attr, [before_compile,after_compile,on_definition|Attributes] }),
|
||||
ets:insert(DataTable, { ?persisted_attr, [vsn|Attributes] }),
|
||||
ets:insert(DataTable, { ?docs_attr, ets:new(DataTable, [ordered_set, public]) }),
|
||||
|
||||
%% Keep docs in another table since we don't want to pull out
|
||||
%% all the binaries every time a new documentation is stored.
|
||||
DocsTable = docs_table(Module),
|
||||
ets:new(DocsTable, [ordered_set, named_table, public]),
|
||||
|
||||
%% We keep a separated table for function definitions
|
||||
%% and another one for imports. We keep them in different
|
||||
%% tables for organization and speed purpose (since the
|
||||
%% imports table is frequently written to).
|
||||
elixir_def:build_table(Module),
|
||||
elixir_import:build_table(Module).
|
||||
%% Setup other modules
|
||||
elixir_def:setup(Module),
|
||||
elixir_tracker:setup(Module).
|
||||
|
||||
%% Receives the module representation and evaluates it.
|
||||
|
||||
@@ -156,13 +160,6 @@ functions_form(Line, File, Module, Export, Private, Def, Defmacro, RawFunctions,
|
||||
{ FinalExport, FinalFunctions } =
|
||||
add_info_function(Line, File, Module, Export, Functions, Def, Defmacro, C),
|
||||
|
||||
Recorded =
|
||||
case ets:lookup(data_table(Module), 'on_load') of
|
||||
[] -> elixir_import:recorded_locals(Module);
|
||||
[{on_load,OnLoad}] -> elixir_import:recorded_locals(Module) ++ OnLoad
|
||||
end,
|
||||
|
||||
elixir_def_local:check_unused_local(File, Recorded, Private),
|
||||
PrivateTuple = [Tuple || { Tuple, _, _, _, _ } <- Private],
|
||||
{ FinalExport ++ PrivateTuple, [
|
||||
{attribute, Line, export, lists:sort(FinalExport)} | FinalFunctions
|
||||
|
||||
@@ -2,7 +2,7 @@ Nonterminals
|
||||
grammar expr_list
|
||||
expr paren_expr block_expr fn_expr bracket_expr call_expr bracket_at_expr max_expr
|
||||
base_expr matched_expr matched_op_expr unmatched_expr op_expr
|
||||
add_op mult_op unary_op two_op right_op bin_concat_op
|
||||
add_op mult_op unary_op two_op regex_op right_op bin_concat_op
|
||||
match_op send_op default_op when_op pipe_op in_op inc_op range_op
|
||||
andand_op oror_op and_op or_op comp_expr_op colon_colon_op three_op at_op
|
||||
open_paren close_paren empty_paren
|
||||
@@ -54,13 +54,14 @@ Left 140 or_op.
|
||||
Left 150 and_op.
|
||||
Left 160 comp_expr_op.
|
||||
Left 170 in_op.
|
||||
Right 180 right_op.
|
||||
Left 190 range_op.
|
||||
Left 200 three_op.
|
||||
Left 210 add_op.
|
||||
Left 220 mult_op.
|
||||
Right 230 bin_concat_op.
|
||||
Right 240 two_op.
|
||||
Right 180 regex_op.
|
||||
Right 190 right_op.
|
||||
Left 200 range_op.
|
||||
Left 210 three_op.
|
||||
Left 220 add_op.
|
||||
Left 230 mult_op.
|
||||
Right 240 bin_concat_op.
|
||||
Right 250 two_op.
|
||||
Nonassoc 300 unary_op.
|
||||
Left 310 dot_call_op.
|
||||
Left 310 dot_op.
|
||||
@@ -103,6 +104,7 @@ op_expr -> match_op expr : { '$1', '$2' }.
|
||||
op_expr -> add_op expr : { '$1', '$2' }.
|
||||
op_expr -> mult_op expr : { '$1', '$2' }.
|
||||
op_expr -> two_op expr : { '$1', '$2' }.
|
||||
op_expr -> regex_op expr : { '$1', '$2' }.
|
||||
op_expr -> right_op expr : { '$1', '$2' }.
|
||||
op_expr -> andand_op expr : { '$1', '$2' }.
|
||||
op_expr -> three_op expr : { '$1', '$2' }.
|
||||
@@ -124,6 +126,7 @@ matched_op_expr -> match_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> add_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> mult_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> two_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> regex_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> right_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> andand_op matched_expr : { '$1', '$2' }.
|
||||
matched_op_expr -> three_op matched_expr : { '$1', '$2' }.
|
||||
@@ -215,8 +218,8 @@ stab_eol -> stab eol : '$1'.
|
||||
|
||||
stab_expr -> expr : '$1'.
|
||||
stab_expr -> stab_op stab_maybe_expr : build_op('$1', [], '$2').
|
||||
stab_expr -> call_args_no_parens stab_op stab_maybe_expr : build_op('$2', '$1', '$3').
|
||||
stab_expr -> call_args_parens_not_one stab_op stab_maybe_expr : build_op('$2', '$1', '$3').
|
||||
stab_expr -> call_args_no_parens stab_op stab_maybe_expr : build_op('$2', unwrap_splice('$1'), '$3').
|
||||
stab_expr -> call_args_parens_not_one stab_op stab_maybe_expr : build_op('$2', unwrap_splice('$1'), '$3').
|
||||
|
||||
stab_maybe_expr -> 'expr' : '$1'.
|
||||
stab_maybe_expr -> '$empty' : nil.
|
||||
@@ -272,8 +275,9 @@ two_op -> '--' eol : '$1'.
|
||||
two_op -> '**' : '$1'.
|
||||
two_op -> '**' eol : '$1'.
|
||||
|
||||
right_op -> '=~' : '$1'.
|
||||
right_op -> '=~' eol : '$1'.
|
||||
regex_op -> '=~' : '$1'.
|
||||
regex_op -> '=~' eol : '$1'.
|
||||
|
||||
right_op -> '|>' : '$1'.
|
||||
right_op -> '|>' eol : '$1'.
|
||||
|
||||
@@ -551,7 +555,7 @@ build_fn(Op, Stab) ->
|
||||
|
||||
build_access(Expr, Access) ->
|
||||
Meta = [{line,?line(Access)}],
|
||||
{ { '.', Meta, ['Elixir-Kernel', access] }, Meta, [ Expr, ?op(Access) ] }.
|
||||
{ { '.', Meta, ['Elixir.Kernel', access] }, Meta, [ Expr, ?op(Access) ] }.
|
||||
|
||||
%% Interpolation aware
|
||||
|
||||
@@ -591,6 +595,18 @@ build_stab([], Marker, Temp, Acc) ->
|
||||
H = { Marker, build_block(lists:reverse(Temp)) },
|
||||
lists:reverse([H|Acc]).
|
||||
|
||||
%% Every time the parser sees a (unquote_splicing())
|
||||
%% it assumes that a block is being spliced, wrapping
|
||||
%% the splicing in a __block__. But in the stab cause,
|
||||
%% we can have (unquote_splicing(1,2,3)) -> :ok, in such
|
||||
%% case, we don't actually want the block, since it is
|
||||
%% an arg style call. unwrap_splice unwraps the splice
|
||||
%% from such blocks.
|
||||
unwrap_splice([{ '__block__', [], [{ unquote_splicing, _, _ }] = Splice }]) ->
|
||||
Splice;
|
||||
|
||||
unwrap_splice(Other) -> Other.
|
||||
|
||||
%% Errors
|
||||
|
||||
throw(Line, Error, Token) ->
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%% Responsible handling Elixir partials with &1, &2 and so on.
|
||||
-module(elixir_partials).
|
||||
-export([handle/2, handle/3, is_sequential/1]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
handle(Original, S) ->
|
||||
handle(Original, S, default).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%% Implements Elixir quote.
|
||||
-module(elixir_quote).
|
||||
-export([escape/2, erl_escape/3, erl_quote/3, linify/2, unquote/5]).
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
-include("elixir.hrl").
|
||||
|
||||
%% Apply the line from site call on quoted contents.
|
||||
@@ -29,7 +29,7 @@ unquote(_File, Meta, Left, { '__aliases__', _, Args }, nil) ->
|
||||
|
||||
unquote(_File, Meta, Left, Right, nil) when is_atom(Right) ->
|
||||
case atom_to_list(Right) of
|
||||
"Elixir-" ++ _ ->
|
||||
"Elixir." ++ _ ->
|
||||
{ '__aliases__', Meta, [Left, Right] };
|
||||
_ ->
|
||||
{ { '.', Meta, [Left, Right] }, Meta, [] }
|
||||
@@ -63,10 +63,6 @@ erl_escape(Expr, Unquote, S) ->
|
||||
quote(Expr, Q, S) ->
|
||||
do_quote(Expr, Q, S).
|
||||
|
||||
erl_quote({ 'unquote_splicing', Meta, [_] } = Expr, #elixir_quote{unquote=true} = Q, S) ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "unquote_splicing in the quote body is deprecated, please use (unquote_splicing()) instead", []),
|
||||
erl_quote({ '__block__', Meta, [Expr] }, Q, S);
|
||||
|
||||
erl_quote(Expr, Q, S) ->
|
||||
{ QExpr, TQ } = quote(Expr, Q, S),
|
||||
{ TExpr, TS } = elixir_translator:translate_each(QExpr, S),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
%% Convenience functions used to manipulate scope
|
||||
%% and its variables.
|
||||
%% Convenience functions used to manipulate scope and its variables.
|
||||
-module(elixir_scope).
|
||||
-export([translate_var/5,
|
||||
build_erl_var/2, build_ex_var/2,
|
||||
@@ -9,7 +8,6 @@
|
||||
umergev/2, umergec/2, merge_clause_vars/2
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
translate_var(Meta, Name, Kind, S, Callback) ->
|
||||
Line = ?line(Meta),
|
||||
@@ -77,18 +75,19 @@ build_ex_var(Line, Key, Name, S) when is_integer(Line) ->
|
||||
|
||||
% Handle Macro.Env conversion
|
||||
|
||||
to_erl_env({ 'Elixir.Macro.Env', Module, File, _Line, Function, Aliases, Context, Requires, Functions, Macros, FileModules }) ->
|
||||
to_erl_env({ 'Elixir.Macro.Env', Module, File, _Line, Function, Aliases, Context,
|
||||
Requires, Functions, Macros, ContextModules, MacroAliases }) ->
|
||||
#elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions,
|
||||
context_modules=FileModules}.
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases}.
|
||||
|
||||
to_ex_env({ Line, #elixir_scope{module=Module,file=File,
|
||||
function=Function,aliases=Aliases,context=Context,
|
||||
requires=Requires,macros=Macros,functions=Functions,
|
||||
context_modules=FileModules} }) when is_integer(Line) ->
|
||||
context_modules=ContextModules,macro_aliases=MacroAliases} }) when is_integer(Line) ->
|
||||
{ 'Elixir.Macro.Env', Module, File, Line, Function, Aliases,
|
||||
Context, Requires, Functions, Macros, FileModules }.
|
||||
Context, Requires, Functions, Macros, ContextModules, MacroAliases }.
|
||||
|
||||
% Provides a tuple with only the scope information we want to serialize.
|
||||
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
%% Module responsible for tracking invocations of locals,
|
||||
%% imports and requires.
|
||||
-module(elixir_tracker).
|
||||
-export([
|
||||
setup/1, cleanup/1,
|
||||
record_local/2, record_local/3,
|
||||
record_import/4, record_remote/4,
|
||||
record_warn/4, record_definition/3,
|
||||
record_defaults/4,
|
||||
ensure_no_import_conflict/4, ensure_all_imports_used/3,
|
||||
warn_unused_local/3, format_error/1
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
-define(attr, '__dispatch_tracker').
|
||||
-define(tracker, 'Elixir.Module.DispatchTracker').
|
||||
|
||||
%% RECORD
|
||||
|
||||
setup(Module) ->
|
||||
case code:ensure_loaded(?tracker) of
|
||||
{ module, _ } -> ets:insert(Module, { ?attr, ?tracker:start_link() });
|
||||
{ error, _ } -> ok
|
||||
end.
|
||||
|
||||
cleanup(Module) ->
|
||||
if_tracker(Module, fun(Pid) -> ?tracker:stop(Pid) end).
|
||||
|
||||
record_local(Tuple, Module) when is_atom(Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_local(Pid, Tuple),
|
||||
true
|
||||
end).
|
||||
record_local(Tuple, _Module, Function)
|
||||
when Function == nil; Function == Tuple -> false;
|
||||
record_local(Tuple, Module, Function) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_local(Pid, Function, Tuple),
|
||||
true
|
||||
end).
|
||||
|
||||
record_warn(Ref, Warn, Line, Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_warnable(Pid, Ref, Warn, Line),
|
||||
true
|
||||
end).
|
||||
|
||||
record_import(_Tuple, Receiver, Module, _Function)
|
||||
when Module == nil; Module == Receiver -> false;
|
||||
record_import(Tuple, Receiver, Module, Function) ->
|
||||
try
|
||||
Pid = ets:lookup_element(Module, ?attr, 2),
|
||||
?tracker:add_import(Pid, Function, Receiver, Tuple)
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
record_remote(_Tuple, Receiver, Module, _Function)
|
||||
when Module == nil; Module == Receiver -> false;
|
||||
record_remote(Tuple, Receiver, Module, Function) ->
|
||||
try
|
||||
Pid = ets:lookup_element(Module, ?attr, 2),
|
||||
?tracker:add_remote(Pid, Function, Receiver, Tuple)
|
||||
catch
|
||||
error:badarg -> false
|
||||
end.
|
||||
|
||||
record_definition(Tuple, Kind, Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_definition(Pid, Kind, Tuple),
|
||||
true
|
||||
end).
|
||||
|
||||
record_defaults(_Tuple, _Kind, _Module, 0) ->
|
||||
true;
|
||||
record_defaults(Tuple, Kind, Module, Defaults) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
?tracker:add_defaults(Pid, Kind, Tuple, Defaults),
|
||||
true
|
||||
end).
|
||||
|
||||
%% HELPERS
|
||||
|
||||
if_tracker(Module, Callback) ->
|
||||
case ets:lookup(Module, ?attr) of
|
||||
[{ ?attr, Pid }] -> Callback(Pid);
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
%% ERROR HANDLING
|
||||
|
||||
ensure_no_import_conflict(Meta, File, Module, AllDefined) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
[ begin
|
||||
elixir_errors:form_error(Meta, File, ?MODULE, { import_conflict, Error })
|
||||
end || Error <- ?tracker:collect_imports_conflicts(Pid, AllDefined) ]
|
||||
end),
|
||||
ok.
|
||||
|
||||
ensure_all_imports_used(_Line, File, Module) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
[ begin
|
||||
elixir_errors:handle_file_warning(File, { L, ?MODULE, { unused_import, M } })
|
||||
end || { M, L } <- ?tracker:collect_unused_imports(Pid)]
|
||||
end),
|
||||
ok.
|
||||
|
||||
warn_unused_local(File, Module, Private) ->
|
||||
if_tracker(Module, fun(Pid) ->
|
||||
Args = [ { Fun, Kind, Defaults } ||
|
||||
{ Fun, Kind, _Line, true, Defaults } <- Private],
|
||||
|
||||
Unused = ?tracker:collect_unused_locals(Pid, Args),
|
||||
|
||||
[ begin
|
||||
{ _, _, Line, _, _ } = lists:keyfind(element(2, Error), 1, Private),
|
||||
elixir_errors:handle_file_warning(File, { Line, ?MODULE, Error })
|
||||
end || Error <- Unused ]
|
||||
end).
|
||||
|
||||
format_error({import_conflict,{Receivers, Name, Arity}}) ->
|
||||
io_lib:format("imported ~ts.~ts/~B conflicts with local function",
|
||||
[elixir_errors:inspect(hd(Receivers)), Name, Arity]);
|
||||
|
||||
format_error({ unused_import, Module }) ->
|
||||
io_lib:format("unused import ~ts", [elixir_errors:inspect(Module)]);
|
||||
|
||||
format_error({unused_args,{Name, Arity}}) ->
|
||||
io_lib:format("default arguments in ~ts/~B are never used", [Name, Arity]);
|
||||
|
||||
format_error({unused_args,{Name, Arity},1}) ->
|
||||
io_lib:format("the first default argument in ~ts/~B is never used", [Name, Arity]);
|
||||
|
||||
format_error({unused_args,{Name, Arity},Count}) ->
|
||||
io_lib:format("the first ~B default arguments in ~ts/~B are never used", [Count, Name, Arity]);
|
||||
|
||||
format_error({unused_def,{Name, Arity},defp}) ->
|
||||
io_lib:format("function ~ts/~B is unused", [Name, Arity]);
|
||||
|
||||
format_error({unused_def,{Name, Arity},defmacrop}) ->
|
||||
io_lib:format("macro ~ts/~B is unused", [Name, Arity]).
|
||||
@@ -1,38 +0,0 @@
|
||||
-module(elixir_transform).
|
||||
-export([parse_transform/2]).
|
||||
|
||||
parse_transform(Forms, _) ->
|
||||
do_transform(Forms).
|
||||
|
||||
do_transform({ atom, Line, Atom }) ->
|
||||
case atom_to_list(Atom) of
|
||||
"Elixir." ++ _ = List ->
|
||||
Module = [case T of
|
||||
$. -> $-;
|
||||
_ -> T
|
||||
end || T <- List],
|
||||
{ atom, Line, list_to_atom(Module) };
|
||||
_ ->
|
||||
{ atom, Line, Atom }
|
||||
end;
|
||||
|
||||
do_transform({ Name, Item1 }) ->
|
||||
{ Name, do_transform(Item1) };
|
||||
|
||||
do_transform({ Name, Item1, Item2 }) ->
|
||||
{ Name, do_transform(Item1), do_transform(Item2) };
|
||||
|
||||
do_transform({ Name, Item1, Item2, Item3 }) ->
|
||||
{ Name, do_transform(Item1), do_transform(Item2), do_transform(Item3) };
|
||||
|
||||
do_transform({ Name, Item1, Item2, Item3, Item4 }) ->
|
||||
{ Name, do_transform(Item1), do_transform(Item2), do_transform(Item3), do_transform(Item4) };
|
||||
|
||||
do_transform({ Name, Item1, Item2, Item3, Item4, Item5 }) ->
|
||||
{ Name, do_transform(Item1), do_transform(Item2), do_transform(Item3), do_transform(Item4), do_transform(Item5) };
|
||||
|
||||
do_transform(List) when is_list(List) ->
|
||||
[do_transform(X) || X <- List];
|
||||
|
||||
do_transform(Other) when not is_tuple(Other) ->
|
||||
Other.
|
||||
@@ -9,7 +9,6 @@
|
||||
assert_function_scope/3, assert_module_scope/3, assert_no_guard_scope/3,
|
||||
assert_no_match_or_guard_scope/3]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
forms(String, StartLine, File, Opts) ->
|
||||
try elixir_tokenizer:tokenize(String, StartLine, [{ file, File }|Opts]) of
|
||||
@@ -231,21 +230,15 @@ translate_each({ quote, Meta, [KV, Do] }, S) when is_list(Do) ->
|
||||
[]
|
||||
end,
|
||||
|
||||
Context = case lists:keyfind(var_context, 1, TKV) of
|
||||
{ var_context, VarContext } ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "var_context in quote is deprecated, please use context instead"),
|
||||
VarContext;
|
||||
Context = case lists:keyfind(context, 1, TKV) of
|
||||
{ context, Atom } when is_atom(Atom) ->
|
||||
Atom;
|
||||
{ context, _ } ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid :context for quote, expected an atom or an alias");
|
||||
false ->
|
||||
case lists:keyfind(context, 1, TKV) of
|
||||
{ context, Atom } when is_atom(Atom) ->
|
||||
Atom;
|
||||
{ context, _ } ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "invalid :context for quote, expected an atom or an alias");
|
||||
false ->
|
||||
case S#elixir_scope.module of
|
||||
nil -> 'Elixir';
|
||||
Mod -> Mod
|
||||
end
|
||||
case S#elixir_scope.module of
|
||||
nil -> 'Elixir';
|
||||
Mod -> Mod
|
||||
end
|
||||
end,
|
||||
|
||||
@@ -338,9 +331,12 @@ translate_each({ super, Meta, Args } = Original, S) ->
|
||||
{ _, Arity } = Function,
|
||||
|
||||
{ TArgs, TS } = if
|
||||
is_atom(Args) ->
|
||||
is_atom(Args) andalso Arity == 0 ->
|
||||
elixir_def_overridable:retrieve_args(Meta, Arity, S);
|
||||
length(Args) == Arity ->
|
||||
is_atom(Args) ->
|
||||
elixir_errors:deprecation(Meta, S#elixir_scope.file, "omitting arguments of super is deprecated, please call super with arguments instead"),
|
||||
elixir_def_overridable:retrieve_args(Meta, Arity, S);
|
||||
not is_atom(Args) andalso length(Args) == Arity ->
|
||||
translate_args(Args, S);
|
||||
true ->
|
||||
syntax_error(Meta, S#elixir_scope.file, "super must be called with the same number of arguments as the current function")
|
||||
@@ -534,7 +530,7 @@ translate_alias(Meta, IncludeByDefault, Old, TKV, S) ->
|
||||
"invalid args for alias, expected an atom or alias in option :as")
|
||||
end,
|
||||
|
||||
case (New == Old) orelse (length(string:tokens(atom_to_list(New), "-")) == 2) of
|
||||
case (New == Old) orelse (length(string:tokens(atom_to_list(New), ".")) == 2) of
|
||||
true -> ok;
|
||||
false -> syntax_error(Meta, S#elixir_scope.file,
|
||||
"invalid args for alias, cannot create nested alias ~s", [elixir_errors:inspect(New)])
|
||||
@@ -578,8 +574,8 @@ translate_fn(Meta, Clauses, S) ->
|
||||
|
||||
%% Locals
|
||||
|
||||
translate_local(Meta, Name, Args, #elixir_scope{local=nil,module=Module} = S) ->
|
||||
elixir_import:record({ Name, length(Args) }, Module, Module),
|
||||
translate_local(Meta, Name, Args, #elixir_scope{local=nil,module=Module,function=Function} = S) ->
|
||||
elixir_tracker:record_local({ Name, length(Args) }, Module, Function),
|
||||
Line = ?line(Meta),
|
||||
{ TArgs, NS } = translate_args(Args, S),
|
||||
{ { call, Line, { atom, Line, Name }, TArgs }, NS };
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
%% Convenience functions used throughout elixir source code
|
||||
%% to generate erlang abstract format for basic structures
|
||||
%% as lists, condition clauses, etc.
|
||||
|
||||
-module(elixir_tree_helpers).
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
-export([elixir_to_erl/1, erl_to_elixir/1, split_last/1,
|
||||
cons_to_list/1, list_to_cons/2, list_to_cons/3,
|
||||
convert_to_boolean/5, returns_boolean/1, get_line/1]).
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
-export([clauses/3, format_error/1]).
|
||||
-import(elixir_scope, [umergec/2]).
|
||||
-include("elixir.hrl").
|
||||
-compile({parse_transform, elixir_transform}).
|
||||
|
||||
clauses(Meta, Clauses, S) ->
|
||||
Catch = elixir_clauses:get_pairs(Meta, 'catch', Clauses, S),
|
||||
|
||||
@@ -13,12 +13,6 @@ defmodule AccessTest do
|
||||
assert nil[:foo] == nil
|
||||
end
|
||||
|
||||
test :function do
|
||||
function = fn x -> x == :foo end
|
||||
assert function[:foo] == true
|
||||
assert function[:bar] == false
|
||||
end
|
||||
|
||||
# Test nil at compilation time does not fail
|
||||
# and that @config[:foo] has proper precedence.
|
||||
nil = @config[:foo]
|
||||
|
||||
@@ -23,7 +23,7 @@ defmodule Binary.Chars.AtomTest do
|
||||
end
|
||||
|
||||
test :alias_atom do
|
||||
assert to_binary(Foo.Bar) == "Elixir-Foo-Bar"
|
||||
assert to_binary(Foo.Bar) == "Elixir.Foo.Bar"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -50,11 +50,9 @@ defmodule Binary.Chars.NumberTest do
|
||||
assert to_binary(100) == "100"
|
||||
end
|
||||
|
||||
unless :erlang.system_info(:otp_release) < 'R16' do
|
||||
test :float do
|
||||
assert to_binary(1.0) == "1.0"
|
||||
assert to_binary(1.0e10) == "10000000000.0"
|
||||
end
|
||||
test :float do
|
||||
assert to_binary(1.0) == "1.0"
|
||||
assert to_binary(1.0e10) == "10000000000.0"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -73,3 +71,89 @@ defmodule Binary.Chars.ListTest do
|
||||
assert to_binary([]) == ""
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Binary.Chars.ErrorsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :list do
|
||||
assert to_binary([0,1, 2, 3,255]) == <<0,1,2,3,255>>
|
||||
assert to_binary([0,[1, "hello"], 2, [["bye"]]]) == <<0,1,104,101,108,108,111,2,98,121,101>>
|
||||
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [256]. Only iolists are supported", fn ->
|
||||
to_binary([256])
|
||||
end
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [1001,10001,100001]. Only iolists are supported", fn ->
|
||||
to_binary([1001, 10001, 100001])
|
||||
end
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [:atom,13,\"hello\"]. Only iolists are supported", fn ->
|
||||
to_binary([:atom, 13, "hello"])
|
||||
end
|
||||
end
|
||||
|
||||
test :tuple do
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars not implemented for {1,2,3}", fn ->
|
||||
to_binary({1,2,3})
|
||||
end
|
||||
end
|
||||
|
||||
test :nested_in_tuple do
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars not implemented for {1,[2],:atom}", fn ->
|
||||
to_binary({1, [2], :atom})
|
||||
end
|
||||
end
|
||||
|
||||
test :nested_tuple do
|
||||
assert_raise Protocol.UndefinedError, "protocol Binary.Chars.List not implemented for [1,{[2],:atom}]. Only iolists are supported", fn ->
|
||||
to_binary([1, {[2], :atom}])
|
||||
end
|
||||
end
|
||||
|
||||
test :nested_pid do
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars\.List not implemented for \[#PID<.+?>,:atom\]\. Only iolists are supported$", fn ->
|
||||
to_binary([self(), :atom])
|
||||
end
|
||||
end
|
||||
|
||||
test :nested_fun do
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars\.List not implemented for \[:atom,#Function<.+?>\]\. Only iolists are supported$", fn ->
|
||||
to_binary([:atom, fn -> end])
|
||||
end
|
||||
end
|
||||
|
||||
test :record do
|
||||
# FIXME: is this expected error message?
|
||||
assert_raise UndefinedFunctionError, "undefined function: Binary.Chars.ArgumentError.to_binary/1", fn ->
|
||||
to_binary(ArgumentError[])
|
||||
end
|
||||
|
||||
# FIXME: is this expected error message?
|
||||
assert_raise UndefinedFunctionError, "undefined function: Binary.Chars.File.Stat.to_binary/1", fn ->
|
||||
to_binary(File.Stat[])
|
||||
end
|
||||
end
|
||||
|
||||
test :pid do
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #PID<.+?>$", fn ->
|
||||
to_binary(self())
|
||||
end
|
||||
end
|
||||
|
||||
test :ref do
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #Reference<.+?>$", fn ->
|
||||
to_binary(make_ref()) == ""
|
||||
end
|
||||
end
|
||||
|
||||
test :function do
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #Function<.+?>$", fn ->
|
||||
to_binary(fn -> end)
|
||||
end
|
||||
end
|
||||
|
||||
test :port do
|
||||
[port|_] = Port.list
|
||||
assert_raise Protocol.UndefinedError, %r"^protocol Binary\.Chars not implemented for #Port<.+?>$", fn ->
|
||||
to_binary(port)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -86,12 +86,10 @@ defmodule Binary.Inspect.NumberTest do
|
||||
assert inspect(100) == "100"
|
||||
end
|
||||
|
||||
unless :erlang.system_info(:otp_release) < 'R16' do
|
||||
test :float do
|
||||
assert inspect(1.0) == "1.0"
|
||||
assert inspect(1.0e10) == "10000000000.0"
|
||||
assert inspect(1.0e-10) == "0.0000000001"
|
||||
end
|
||||
test :float do
|
||||
assert inspect(1.0) == "1.0"
|
||||
assert inspect(1.0e10) == "10000000000.0"
|
||||
assert inspect(1.0e-10) == "0.0000000001"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@ defmodule CodeTest do
|
||||
assert { 3, _ } = Code.eval_string("a + b", [a: 1, b: 2], __ENV__.location)
|
||||
end
|
||||
|
||||
test :eval_with_unnamed_scopes do
|
||||
assert { RuntimeError[], [a: RuntimeError[]] } =
|
||||
Code.eval_string("a = (try do (raise \"hello\") rescue e -> e end)")
|
||||
end
|
||||
|
||||
test :eval_with_scope do
|
||||
assert Code.eval_string("one", [], delegate_locals_to: __MODULE__) == { 1, [] }
|
||||
end
|
||||
@@ -83,17 +88,7 @@ defmodule CodeTest do
|
||||
end
|
||||
|
||||
test :compile_source do
|
||||
compile = __MODULE__.__info__(:compile)
|
||||
|
||||
# Erlang has a bug that does not allow us to set the source
|
||||
# when compiling forms. In such cases, the source will be
|
||||
# under compile option. This is fixed and future Erlang
|
||||
# version will return the proper source always (source2).
|
||||
options = :proplists.get_value(:options, compile, [])
|
||||
source1 = :proplists.get_value(:source, options, nil)
|
||||
source2 = :proplists.get_value(:source, compile, nil)
|
||||
|
||||
assert (source1 || source2) == binary_to_list(__FILE__)
|
||||
assert __MODULE__.__info__(:compile)[:source] == binary_to_list(__FILE__)
|
||||
end
|
||||
|
||||
test :compile_info_returned_with_source_accessible_through_keyword_module do
|
||||
@@ -101,6 +96,20 @@ defmodule CodeTest do
|
||||
assert Keyword.get(compile, :source) != nil
|
||||
end
|
||||
|
||||
test :compile_string do
|
||||
assert [{ CompileStringSample, _ }] = Code.compile_string("defmodule CompileStringSample, do: :ok")
|
||||
after
|
||||
:code.purge CompileSimpleSample
|
||||
:code.delete CompileSimpleSample
|
||||
end
|
||||
|
||||
test :compile_quoted do
|
||||
assert [{ CompileQuotedSample, _ }] = Code.compile_string("defmodule CompileQuotedSample, do: :ok")
|
||||
after
|
||||
:code.purge CompileQuotedSample
|
||||
:code.delete CompileQuotedSample
|
||||
end
|
||||
|
||||
test :ensure_loaded? do
|
||||
assert Code.ensure_loaded?(__MODULE__)
|
||||
refute Code.ensure_loaded?(Unknown.Module)
|
||||
|
||||
@@ -169,6 +169,14 @@ defmodule EnumTest.List do
|
||||
assert Enum.map_join([1,2,3], ' = ', &1 * 2) == '2 = 4 = 6'
|
||||
end
|
||||
|
||||
test :join_empty do
|
||||
fun = fn (acc, _) -> acc end
|
||||
assert Enum.join(fun, ".") == ""
|
||||
assert Enum.map_join(fun, ".", &1 + 0) == ""
|
||||
assert Enum.join(fun, '.') == ''
|
||||
assert Enum.map_join(fun, '.', &1 + 0) == ''
|
||||
end
|
||||
|
||||
test :map do
|
||||
assert Enum.map([], fn x -> x * 2 end) == []
|
||||
assert Enum.map([1,2,3], fn x -> x * 2 end) == [2,4,6]
|
||||
@@ -231,28 +239,21 @@ defmodule EnumTest.List do
|
||||
|
||||
test :take_does_not_consume_next_without_a_need do
|
||||
import PathHelpers
|
||||
iterator = File.iterator!(fixture_path("one-liner.txt"))
|
||||
|
||||
try do
|
||||
File.open!(fixture_path("one-liner.txt"), [], fn file ->
|
||||
iterator = File.iterator(file)
|
||||
assert Enum.take(iterator, 1) == ["ONE"]
|
||||
assert Enum.take(iterator, 5) == []
|
||||
after
|
||||
File.close(iterator)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
test :take_with_no_item_works_as_no_op do
|
||||
import PathHelpers
|
||||
iterator = File.iterator!(fixture_path("one-liner.txt"))
|
||||
|
||||
try do
|
||||
assert Enum.take(iterator, 0) == []
|
||||
assert Enum.take(iterator, 0) == []
|
||||
assert Enum.take(iterator, 0) == []
|
||||
assert Enum.take(iterator, 0) == []
|
||||
after
|
||||
File.close(iterator)
|
||||
end
|
||||
assert Enum.take(iterator, 0) == []
|
||||
assert Enum.take(iterator, 0) == []
|
||||
assert Enum.take(iterator, 0) == []
|
||||
assert Enum.take(iterator, 0) == []
|
||||
end
|
||||
|
||||
test :take_while do
|
||||
|
||||
@@ -34,8 +34,8 @@ defmodule Kernel.ExceptionTest do
|
||||
end
|
||||
|
||||
test :format_stacktrace_entry_with_fun do
|
||||
assert Exception.format_stacktrace_entry({fn(x) -> x end, [1], []}) =~ %r"\(1\)"
|
||||
assert Exception.format_stacktrace_entry({fn(x, y) -> { x, y } end, 2, []}) =~ %r"/2"
|
||||
assert Exception.format_stacktrace_entry({fn(x) -> x end, [1], []}) =~ %r/#Function<.+>\(1\)/
|
||||
assert Exception.format_stacktrace_entry({fn(x, y) -> { x, y } end, 2, []}) =~ %r"#Function<.+>/2"
|
||||
end
|
||||
|
||||
test :format_module_function_arity do
|
||||
|
||||
@@ -905,7 +905,7 @@ defmodule FileTest do
|
||||
dest = tmp_path("tmp_test.txt")
|
||||
|
||||
try do
|
||||
{ :ok, iterator } = File.iterator(src)
|
||||
iterator = File.iterator!(src)
|
||||
File.open dest, [:write], fn(target) ->
|
||||
Enum.each iterator, fn(line) ->
|
||||
IO.write target, Regex.replace(%r/"/, line, "'")
|
||||
@@ -934,12 +934,6 @@ defmodule FileTest do
|
||||
end
|
||||
end
|
||||
|
||||
test :iterator_can_be_closed do
|
||||
src = fixture_path("file.txt")
|
||||
iterator = File.iterator!(src)
|
||||
assert File.close(iterator) == :ok
|
||||
end
|
||||
|
||||
test :biniterator do
|
||||
src = File.open! fixture_path("file.txt")
|
||||
dest = tmp_path("tmp_test.txt")
|
||||
@@ -962,7 +956,7 @@ defmodule FileTest do
|
||||
dest = tmp_path("tmp_test.txt")
|
||||
|
||||
try do
|
||||
{ :ok, iterator } = File.biniterator(src)
|
||||
iterator = File.biniterator!(src)
|
||||
File.open dest, [:write], fn(target) ->
|
||||
Enum.each iterator, fn(line) ->
|
||||
IO.write target, Regex.replace(%r/"/, line, "'")
|
||||
@@ -991,12 +985,6 @@ defmodule FileTest do
|
||||
end
|
||||
end
|
||||
|
||||
test :biniterator_can_be_closed do
|
||||
src = fixture_path("file.txt")
|
||||
iterator = File.biniterator!(src)
|
||||
assert File.close(iterator) == :ok
|
||||
end
|
||||
|
||||
test :copy do
|
||||
src = fixture_path("file.txt")
|
||||
dest = tmp_path("tmp_test.txt")
|
||||
|
||||
@@ -16,8 +16,8 @@ defmodule GenServer.BehaviourTest do
|
||||
{ :stop, :normal, :ok, config }
|
||||
end
|
||||
|
||||
def handle_call(_request, _from, _config) do
|
||||
super
|
||||
def handle_call(request, from, config) do
|
||||
super(request, from, config)
|
||||
end
|
||||
|
||||
def handle_cast({ :push, item }, config) do
|
||||
|
||||
@@ -62,6 +62,11 @@ defmodule HashDictTest do
|
||||
dict = HashDict.put_new(dict, 11, 13)
|
||||
assert HashDict.get(dict, 11) == 13
|
||||
assert HashDict.size(dict) == 9
|
||||
|
||||
dict = HashDict.put_new(dict, 11.0, 15)
|
||||
assert HashDict.get(dict, 11.0) == 15
|
||||
assert HashDict.get(dict, 11) == 13
|
||||
assert HashDict.size(dict) == 10
|
||||
end
|
||||
|
||||
test :update do
|
||||
@@ -82,20 +87,37 @@ defmodule HashDictTest do
|
||||
dict = HashDict.update(dict, 11, 13, &1 * 2)
|
||||
assert HashDict.get(dict, 11) == 13
|
||||
assert HashDict.size(dict) == 9
|
||||
|
||||
assert_raise KeyError, fn->
|
||||
HashDict.update(dict, 11.0, &1 * 2)
|
||||
end
|
||||
dict = HashDict.update(dict, 11.0, 15, &1 * 2)
|
||||
assert HashDict.get(dict, 11.0) == 15
|
||||
assert HashDict.size(dict) == 10
|
||||
dict = HashDict.update(dict, 11.0, 15, &1 * 2)
|
||||
assert HashDict.get(dict, 11.0) == 30
|
||||
assert HashDict.get(dict, 11) == 13
|
||||
assert HashDict.size(dict) == 10
|
||||
end
|
||||
|
||||
test :to_list do
|
||||
list = filled_dict(8) |> HashDict.to_list
|
||||
dict = filled_dict(8)
|
||||
list = dict |> HashDict.to_list
|
||||
assert length(list) == 8
|
||||
assert { 1, 1 } in list
|
||||
assert list == Enum.to_list(dict)
|
||||
|
||||
list = filled_dict(20) |> HashDict.to_list
|
||||
dict = filled_dict(20)
|
||||
list = dict |> HashDict.to_list
|
||||
assert length(list) == 20
|
||||
assert { 1, 1 } in list
|
||||
assert list == Enum.to_list(dict)
|
||||
|
||||
list = filled_dict(120) |> HashDict.to_list
|
||||
dict = filled_dict(120)
|
||||
list = dict |> HashDict.to_list
|
||||
assert length(list) == 120
|
||||
assert { 1, 1 } in list
|
||||
assert list == Enum.to_list(dict)
|
||||
end
|
||||
|
||||
test :keys do
|
||||
@@ -143,7 +165,7 @@ defmodule HashDictTest do
|
||||
end
|
||||
|
||||
test :inspect do
|
||||
assert inspect(filled_dict(8)) =~ %r"#HashDict<"
|
||||
assert inspect(filled_dict(8)) =~ "#HashDict<"
|
||||
end
|
||||
|
||||
test :small_range_merge do
|
||||
|
||||
@@ -12,8 +12,8 @@ defmodule Kernel.AliasTest do
|
||||
test :alias_erlang do
|
||||
alias :lists, as: MyList
|
||||
assert MyList.flatten([1,[2],3]) == [1,2,3]
|
||||
assert Elixir.MyList.Bar == :"Elixir-MyList-Bar"
|
||||
assert MyList.Bar == :"Elixir-lists-Bar"
|
||||
assert Elixir.MyList.Bar == :"Elixir.MyList.Bar"
|
||||
assert MyList.Bar == :"Elixir.lists.Bar"
|
||||
end
|
||||
|
||||
test :double_alias do
|
||||
|
||||
@@ -78,7 +78,7 @@ defmodule Kernel.CLI.CompileTest do
|
||||
fixture = fixture_path "compile_sample.ex"
|
||||
assert elixirc('#{fixture} -o #{tmp_path}') ==
|
||||
'Compiled #{fixture}\n'
|
||||
assert File.regular?(tmp_path "Elixir-CompileSample.beam")
|
||||
assert File.regular?(tmp_path "Elixir.CompileSample.beam")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -96,8 +96,8 @@ defmodule Kernel.CLI.ParallelCompilerTest do
|
||||
output = elixirc('#{fixture_path("parallel_compiler")} -o #{tmp_path}')
|
||||
assert :string.str(output, 'message_from_foo') > 0,
|
||||
"Expected #{inspect output} to contain 'message_from_foo'"
|
||||
assert File.regular?(tmp_path "Elixir-Foo.beam")
|
||||
assert File.regular?(tmp_path "Elixir-Bar.beam")
|
||||
assert File.regular?(tmp_path "Elixir.Foo.beam")
|
||||
assert File.regular?(tmp_path "Elixir.Bar.beam")
|
||||
end
|
||||
|
||||
test :possible_deadlock do
|
||||
|
||||
@@ -2,6 +2,7 @@ Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Kernel.ErrorsTest do
|
||||
use ExUnit.Case, async: true
|
||||
import CompileAssertion
|
||||
|
||||
defmodule UnproperMacro do
|
||||
defmacro unproper(args), do: args
|
||||
@@ -11,278 +12,300 @@ defmodule Kernel.ErrorsTest do
|
||||
defrecord Config, integer: 0
|
||||
|
||||
test :invalid_token do
|
||||
assert "nofile:1: invalid token: \end" == format_rescue '\end\nlol\nbarbecue'
|
||||
assert_compile_fail SyntaxError, "nofile:1: invalid token: \end", '\end\nlol\nbarbecue'
|
||||
end
|
||||
|
||||
test :sigil_terminator do
|
||||
assert "nofile:3: missing terminator: \" (for sigil %r\" starting at line 1)" == format_rescue '%r"foo\n\n'
|
||||
assert "nofile:3: missing terminator: } (for sigil %r{ starting at line 1)" == format_rescue '%r{foo\n\n'
|
||||
assert_compile_fail TokenMissingError, "nofile:3: missing terminator: \" (for sigil %r\" starting at line 1)", '%r"foo\n\n'
|
||||
assert_compile_fail TokenMissingError, "nofile:3: missing terminator: } (for sigil %r{ starting at line 1)", '%r{foo\n\n'
|
||||
end
|
||||
|
||||
test :dot_terminator do
|
||||
assert "nofile:1: missing terminator: \" (for function name starting at line 1)" == format_rescue 'foo."bar'
|
||||
assert_compile_fail TokenMissingError, "nofile:1: missing terminator: \" (for function name starting at line 1)", 'foo."bar'
|
||||
end
|
||||
|
||||
test :string_terminator do
|
||||
assert "nofile:1: missing terminator: \" (for string starting at line 1)" == format_rescue '"bar'
|
||||
assert_compile_fail TokenMissingError, "nofile:1: missing terminator: \" (for string starting at line 1)", '"bar'
|
||||
end
|
||||
|
||||
test :heredoc_terminator do
|
||||
assert "nofile:2: missing terminator: \"\"\" (for heredoc starting at line 1)" == format_rescue '"""\nbar'
|
||||
assert_compile_fail TokenMissingError, "nofile:2: missing terminator: \"\"\" (for heredoc starting at line 1)", '"""\nbar'
|
||||
end
|
||||
|
||||
test :invalid_partial do
|
||||
assert "nofile:1: partial variable &2 cannot be defined without &1" == format_rescue '&2 + 3'
|
||||
assert_compile_fail SyntaxError, "nofile:1: partial variable &2 cannot be defined without &1", '&2 + 3'
|
||||
end
|
||||
|
||||
test :unexpected_end do
|
||||
assert "nofile:1: unexpected token: end" == format_rescue '1 end'
|
||||
assert_compile_fail SyntaxError, "nofile:1: unexpected token: end", '1 end'
|
||||
end
|
||||
|
||||
test :syntax_error do
|
||||
assert "nofile:1: syntax error before: '.'" == format_rescue '+.foo'
|
||||
assert_compile_fail SyntaxError, "nofile:1: syntax error before: '.'", '+.foo'
|
||||
end
|
||||
|
||||
test :syntax_error_on_parens_call do
|
||||
assert "nofile:1: invalid comma inside parenthesis. If you are making a function call, " <>
|
||||
assert_compile_fail SyntaxError, "nofile:1: invalid comma inside parenthesis. If you are making a function call, " <>
|
||||
"do not insert spaces in between the function name and the opening parentheses. " <>
|
||||
"Syntax error before: )" == format_rescue 'foo (hello, world)'
|
||||
"Syntax error before: )", 'foo (hello, world)'
|
||||
end
|
||||
|
||||
test :syntax_error_with_no_token do
|
||||
assert "nofile:1: missing terminator: ) (for \"(\" starting at line 1)" == format_rescue 'case 1 ('
|
||||
assert_compile_fail TokenMissingError, "nofile:1: missing terminator: ) (for \"(\" starting at line 1)", 'case 1 ('
|
||||
end
|
||||
|
||||
test :bad_form do
|
||||
assert "nofile:2: function bar/0 undefined" == format_rescue 'defmodule Foo do\ndef foo, do: bar\nend'
|
||||
assert_compile_fail CompileError, "nofile:2: function bar/0 undefined", 'defmodule Foo do\ndef foo, do: bar\nend'
|
||||
end
|
||||
|
||||
test :unbound_var do
|
||||
assert "nofile:1: unbound variable ^x" == format_rescue('^x = 1')
|
||||
assert_compile_fail SyntaxError, "nofile:1: unbound variable ^x", '^x = 1'
|
||||
end
|
||||
|
||||
test :unbound_not_match do
|
||||
assert "nofile:1: cannot use ^x outside of match clauses" == format_rescue('^x')
|
||||
assert_compile_fail SyntaxError, "nofile:1: cannot use ^x outside of match clauses", '^x'
|
||||
end
|
||||
|
||||
test :unbound_expr do
|
||||
assert "nofile:1: the unary operator ^ can only be used with variables, invalid expression ^x(1)" == format_rescue('^x(1)')
|
||||
assert_compile_fail SyntaxError, "nofile:1: the unary operator ^ can only be used with variables, invalid expression ^x(1)", '^x(1)'
|
||||
end
|
||||
|
||||
test :name_for_defmodule do
|
||||
assert "nofile:1: invalid module name: 3" == format_rescue 'defmodule 1 + 2, do: 3'
|
||||
assert_compile_fail CompileError, "nofile:1: invalid module name: 3", 'defmodule 1 + 2, do: 3'
|
||||
end
|
||||
|
||||
test :invalid_scope_for_function do
|
||||
assert "nofile:1: cannot invoke def outside module" == format_rescue 'def Foo, do: 2'
|
||||
assert "nofile:3: cannot invoke defmacro outside module" == format_rescue '\n\ndefmacro Foo, do: 2'
|
||||
assert_compile_fail SyntaxError, "nofile:1: cannot invoke def outside module", 'def Foo, do: 2'
|
||||
assert_compile_fail SyntaxError, "nofile:3: cannot invoke defmacro outside module", '\n\ndefmacro Foo, do: 2'
|
||||
end
|
||||
|
||||
test :invalid_unquote do
|
||||
assert "nofile:1: unquote called outside quote" == format_rescue 'unquote 1'
|
||||
assert_compile_fail SyntaxError, "nofile:1: unquote called outside quote", 'unquote 1'
|
||||
end
|
||||
|
||||
test :invalid_quote_args do
|
||||
assert "nofile:1: invalid args for quote" == format_rescue 'quote 1'
|
||||
assert_compile_fail SyntaxError, "nofile:1: invalid args for quote", 'quote 1'
|
||||
end
|
||||
|
||||
test :invalid_parens do
|
||||
assert "nofile:1: unexpected parenthesis after foo(1)" == format_rescue 'foo(1)(2)'
|
||||
assert_compile_fail SyntaxError, "nofile:1: unexpected parenthesis after foo(1)", 'foo(1)(2)'
|
||||
end
|
||||
|
||||
test :invalid_fn_args do
|
||||
assert "nofile:1: missing terminator: end (for \"fn\" starting at line 1)" == format_rescue 'fn 1'
|
||||
assert_compile_fail TokenMissingError, "nofile:1: missing terminator: end (for \"fn\" starting at line 1)", 'fn 1'
|
||||
end
|
||||
|
||||
test :macro_conflict do
|
||||
assert "nofile:1: imported Kernel.defrecord/2 conflicts with local function" ==
|
||||
format_rescue 'defmodule Foo do\ndefrecord(Kernel.ErrorsTest.MacroConflict, a: 1)\ndef defrecord(_, _), do: OMG\nend'
|
||||
assert_compile_fail CompileError, "nofile:1: imported Kernel.defrecord/2 conflicts with local function",
|
||||
'defmodule Foo do\ndefrecord(Kernel.ErrorsTest.MacroConflict, a: 1)\ndef defrecord(_, _), do: OMG\nend'
|
||||
end
|
||||
|
||||
test :macro_with_undefined_local do
|
||||
assert "undefined function: Foo.unknown/1" ==
|
||||
format_rescue 'defmodule Foo do\ndefmacrop bar, do: unknown(1)\ndef baz, do: bar()\nend'
|
||||
assert_compile_fail UndefinedFunctionError, "undefined function: Foo.unknown/1",
|
||||
'defmodule Foo do\ndefmacrop bar, do: unknown(1)\ndef baz, do: bar()\nend'
|
||||
end
|
||||
|
||||
test :private_macro do
|
||||
assert "undefined function: Foo.foo/0" ==
|
||||
format_rescue 'defmodule Foo do\ndefmacrop foo, do: 1\ndefmacro bar, do: __MODULE__.foo\ndefmacro baz, do: bar\nend'
|
||||
assert_compile_fail UndefinedFunctionError, "undefined function: Foo.foo/0",
|
||||
'defmodule Foo do\ndefmacrop foo, do: 1\ndefmacro bar, do: __MODULE__.foo\ndefmacro baz, do: bar\nend'
|
||||
end
|
||||
|
||||
test :function_definition_with_alias do
|
||||
assert "nofile:1: function names should start with lowercase characters or underscore, invalid name Bar" ==
|
||||
format_rescue 'defmodule Foo do def Bar do :baz end\nend\n'
|
||||
assert_compile_fail SyntaxError, "nofile:1: function names should start with lowercase characters or underscore, invalid name Bar",
|
||||
'defmodule Foo do def Bar do :baz end\nend\n'
|
||||
end
|
||||
|
||||
test :function_import_conflict do
|
||||
assert "nofile:2: function exit/1 imported from both :erlang and Kernel, call is ambiguous" ==
|
||||
format_rescue 'defmodule Foo do import :erlang\n def foo, do: exit(:test)\nend'
|
||||
assert_compile_fail CompileError, "nofile:2: function exit/1 imported from both :erlang and Kernel, call is ambiguous",
|
||||
'defmodule Foo do import :erlang\n def foo, do: exit(:test)\nend'
|
||||
end
|
||||
|
||||
test :import_invalid_macro do
|
||||
assert "nofile:2: cannot import Kernel.invalid/1 because it doesn't exist" ==
|
||||
format_rescue 'defmodule Foo do\nimport Kernel, only: [invalid: 1]\nend'
|
||||
assert_compile_fail CompileError, "nofile:2: cannot import Kernel.invalid/1 because it doesn't exist",
|
||||
'defmodule Foo do\nimport Kernel, only: [invalid: 1]\nend'
|
||||
end
|
||||
|
||||
test :unrequired_macro do
|
||||
assert "nofile:2: tried to invoke macro Kernel.ErrorsTest.UnproperMacro.unproper/1 but module was not required. Required: Kernel, Kernel.Typespec, Record" ==
|
||||
format_rescue 'defmodule Foo do\nKernel.ErrorsTest.UnproperMacro.unproper([])\nend'
|
||||
assert_compile_fail CompileError, "nofile:2: tried to invoke macro Kernel.ErrorsTest.UnproperMacro.unproper/1 but module was not required. Required: Kernel, Kernel.Typespec, Record",
|
||||
'defmodule Foo do\nKernel.ErrorsTest.UnproperMacro.unproper([])\nend'
|
||||
end
|
||||
|
||||
test :def_defmacro_clause_change do
|
||||
assert "nofile:3: defmacro foo/1 already defined as def" ==
|
||||
format_rescue 'defmodule Foo do\ndef foo(1), do: 1\ndefmacro foo(x), do: x\nend'
|
||||
assert_compile_fail CompileError, "nofile:3: defmacro foo/1 already defined as def",
|
||||
'defmodule Foo do\ndef foo(1), do: 1\ndefmacro foo(x), do: x\nend'
|
||||
end
|
||||
|
||||
test :internal_function_overridden do
|
||||
assert "nofile:1: function __info__/1 is internal and should not be overridden" ==
|
||||
format_rescue 'defmodule Foo do\ndef __info__(_), do: []\nend'
|
||||
assert_compile_fail CompileError, "nofile:1: function __info__/1 is internal and should not be overridden",
|
||||
'defmodule Foo do\ndef __info__(_), do: []\nend'
|
||||
end
|
||||
|
||||
test :no_macros do
|
||||
assert "nofile:2: could not load macros from module :lists" ==
|
||||
format_rescue 'defmodule Foo do\nimport :macros, :lists\nend'
|
||||
assert_compile_fail CompileError, "nofile:2: could not load macros from module :lists",
|
||||
'defmodule Foo do\nimport :macros, :lists\nend'
|
||||
end
|
||||
|
||||
test :unloaded_module do
|
||||
assert "nofile:1: module Certainly.Doesnt.Exist is not loaded and could not be found" ==
|
||||
format_rescue 'import Certainly.Doesnt.Exist'
|
||||
assert_compile_fail CompileError, "nofile:1: module Certainly.Doesnt.Exist is not loaded and could not be found",
|
||||
'import Certainly.Doesnt.Exist'
|
||||
end
|
||||
|
||||
test :scheduled_module do
|
||||
assert "nofile:1: module Foo.Hygiene is not loaded but was defined. This happens because you are trying to use a module in the same context it is defined. Try defining the module outside the context that requires it." ==
|
||||
format_rescue 'defmodule Foo do; defmodule Hygiene do; end; import Foo.Hygiene; end'
|
||||
assert_compile_fail CompileError, "nofile:1: module Foo.Hygiene is not loaded but was defined. This happens because you are trying to use a module in the same context it is defined. Try defining the module outside the context that requires it.",
|
||||
'defmodule Foo do; defmodule Hygiene do; end; import Foo.Hygiene; end'
|
||||
end
|
||||
|
||||
test :already_compiled_module do
|
||||
assert "could not call eval_quoted on module Record because it was already compiled" ==
|
||||
format_rescue 'Module.eval_quoted Record, quote(do: 1), [], file: __FILE__'
|
||||
assert_compile_fail ArgumentError, "could not call eval_quoted on module Record because it was already compiled",
|
||||
'Module.eval_quoted Record, quote(do: 1), [], file: __FILE__'
|
||||
end
|
||||
|
||||
test :interpolation_error do
|
||||
assert "nofile:1: missing terminator: end (for \"do\" starting at line 1)" == format_rescue '"foo\#{case 1 do )}bar"'
|
||||
assert_compile_fail TokenMissingError, "nofile:1: missing terminator: end (for \"do\" starting at line 1)", '"foo\#{case 1 do )}bar"'
|
||||
end
|
||||
|
||||
test :cant_define_local_due_to_in_erlang_macros_conflict do
|
||||
assert "nofile:1: cannot define local quote/1 because it conflicts with Elixir special forms" ==
|
||||
format_rescue 'defmodule Foo do\ndef quote(x), do: x\ndef bar(x), do: quote(do: x)\nend'
|
||||
assert_compile_fail CompileError, "nofile:1: cannot define local quote/1 because it conflicts with Elixir special forms",
|
||||
'defmodule Foo do\ndef quote(x), do: x\ndef bar(x), do: quote(do: x)\nend'
|
||||
end
|
||||
|
||||
test :in_definition_module do
|
||||
assert "nofile:1: cannot define module Foo because it is currently being defined" ==
|
||||
format_rescue 'defmodule Foo, do: (defmodule Elixir.Foo, do: true)'
|
||||
assert_compile_fail CompileError, "nofile:1: cannot define module Foo because it is currently being defined",
|
||||
'defmodule Foo, do: (defmodule Elixir.Foo, do: true)'
|
||||
end
|
||||
|
||||
test :invalid_definition do
|
||||
assert "nofile:1: invalid syntax in def 1.(hello)" ==
|
||||
format_rescue 'defmodule Foo, do: (def 1.(hello), do: true)'
|
||||
assert_compile_fail SyntaxError, "nofile:1: invalid syntax in def 1.(hello)",
|
||||
'defmodule Foo, do: (def 1.(hello), do: true)'
|
||||
end
|
||||
|
||||
test :duplicated_bitstring_size do
|
||||
assert "nofile:1: duplicated size definition for bitstring" == format_rescue '<<1 :: [size(12), size(13)]>>'
|
||||
assert_compile_fail SyntaxError, "nofile:1: duplicated size definition for bitstring", '<<1 :: [size(12), size(13)]>>'
|
||||
end
|
||||
|
||||
test :invalid_bitstring_specified do
|
||||
assert "nofile:1: unknown bitstring specifier :atom" == format_rescue '<<1 :: :atom>>'
|
||||
assert "nofile:1: unknown bitstring specifier unknown" == format_rescue '<<1 :: unknown>>'
|
||||
assert "nofile:1: unknown bitstring specifier another(12)" == format_rescue '<<1 :: another(12)>>'
|
||||
assert "nofile:1: size in bitstring expects an integer or a variable as argument" == format_rescue '<<1 :: size(:a)>>'
|
||||
assert "nofile:1: unit in bitstring expects an integer as argument" == format_rescue '<<1 :: unit(x)>>'
|
||||
assert_compile_fail SyntaxError, "nofile:1: unknown bitstring specifier :atom", '<<1 :: :atom>>'
|
||||
assert_compile_fail SyntaxError, "nofile:1: unknown bitstring specifier unknown", '<<1 :: unknown>>'
|
||||
assert_compile_fail SyntaxError, "nofile:1: unknown bitstring specifier another(12)", '<<1 :: another(12)>>'
|
||||
assert_compile_fail SyntaxError, "nofile:1: size in bitstring expects an integer or a variable as argument", '<<1 :: size(:a)>>'
|
||||
assert_compile_fail SyntaxError, "nofile:1: unit in bitstring expects an integer as argument", '<<1 :: unit(x)>>'
|
||||
end
|
||||
|
||||
test :invalid_var! do
|
||||
assert "nofile:1: expected var!(y) to expand to an existing variable or be a part of a match",
|
||||
format_rescue 'var!(x)'
|
||||
assert_compile_fail SyntaxError, "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
|
||||
assert "nofile:1: invalid args for alias, cannot create nested alias Sample.Lists" ==
|
||||
format_rescue 'alias :lists, as: Sample.Lists'
|
||||
assert_compile_fail SyntaxError, "nofile:1: invalid args for alias, cannot create nested alias Sample.Lists",
|
||||
'alias :lists, as: Sample.Lists'
|
||||
end
|
||||
|
||||
test :invalid_import_option do
|
||||
assert "nofile:1: unsupported option ops given to import" ==
|
||||
format_rescue 'import :lists, [ops: 1]'
|
||||
assert_compile_fail SyntaxError, "nofile:1: unsupported option ops given to import",
|
||||
'import :lists, [ops: 1]'
|
||||
end
|
||||
|
||||
test :invalid_access_protocol_not_alias do
|
||||
assert "the access protocol cannot be used inside match clauses (for example, on the left hand side of a match or in function signatures)" ==
|
||||
format_rescue 'defmodule Foo do\ndef sample(config[integer: 0]), do: true\nend'
|
||||
assert_raise RuntimeError, "the access protocol cannot be used inside match clauses (for example, on the left hand side of a match or in function signatures)", fn ->
|
||||
defmodule Foo do
|
||||
def sample(config[integer: 0]), do: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test :invalid_access_protocol_not_available do
|
||||
assert "nofile:2: module Unknown is not loaded and could not be found" ==
|
||||
format_rescue 'defmodule Foo do\ndef sample(Unknown[integer: 0]), do: true\nend'
|
||||
assert_compile_fail CompileError, "nofile:2: module Unknown is not loaded and could not be found",
|
||||
'defmodule Foo do\ndef sample(Unknown[integer: 0]), do: true\nend'
|
||||
end
|
||||
|
||||
test :invalid_access_protocol_not_record do
|
||||
assert "cannot use module Kernel.ErrorsTest in access protocol because it does not export __record__/1" ==
|
||||
format_rescue 'defmodule Foo do\ndef sample(Kernel.ErrorsTest[integer: 0]), do: true\nend'
|
||||
assert_raise RuntimeError, "cannot use module Kernel.ErrorsTest in access protocol because it does not export __record__/1", fn ->
|
||||
defmodule Foo do
|
||||
def sample(Kernel.ErrorsTest[integer: 0]), do: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test :invalid_access_protocol_not_keywords do
|
||||
assert "expected contents inside brackets to be a Keyword" ==
|
||||
format_rescue 'defmodule Foo do\ndef sample(Kernel.ErrorsTest.Config[0]), do: true\nend'
|
||||
assert_raise RuntimeError, "expected contents inside brackets to be a Keyword", fn ->
|
||||
defmodule Foo do
|
||||
def sample(Kernel.ErrorsTest.Config[0]), do: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test :invalid_access_protocol_invalid_keywords do
|
||||
assert "record Kernel.ErrorsTest.Config does not have the keys: [:foo]" ==
|
||||
format_rescue 'defmodule Foo do\ndef sample(Kernel.ErrorsTest.Config[foo: :bar]), do: true\nend'
|
||||
assert_raise RuntimeError, "record Kernel.ErrorsTest.Config does not have the keys: [:foo]", fn ->
|
||||
defmodule Foo do
|
||||
def sample(Kernel.ErrorsTest.Config[foo: :bar]), do: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test :invalid_access_protocol_invalid_keywords_outside_match do
|
||||
assert "record Kernel.ErrorsTest.Config does not have the keys: [:foo]" ==
|
||||
format_rescue 'Kernel.ErrorsTest.Config[foo: :bar]'
|
||||
# FIXME: why RuntimeError?
|
||||
assert_compile_fail RuntimeError, "record Kernel.ErrorsTest.Config does not have the keys: [:foo]",
|
||||
'Kernel.ErrorsTest.Config[foo: :bar]'
|
||||
end
|
||||
|
||||
test :invalid_rescue_clause do
|
||||
assert "nofile:1: invalid rescue clause. The clause should match on an alias, a variable or be in the `var in [alias]` format" ==
|
||||
format_rescue 'try do\n1\nrescue\nUndefinedFunctionError[arity: 1] -> false\nend'
|
||||
assert_compile_fail SyntaxError, "nofile:1: invalid rescue clause. The clause should match on an alias, a variable or be in the `var in [alias]` format",
|
||||
'try do\n1\nrescue\nUndefinedFunctionError[arity: 1] -> false\nend'
|
||||
end
|
||||
|
||||
test :invalid_bc_return do
|
||||
assert "nofile:1: a bit comprehension expects a bit string << >> to be returned" ==
|
||||
format_rescue 'bc x inlist [1,2,3], do: x'
|
||||
assert_compile_fail SyntaxError, "nofile:1: a bit comprehension expects a bit string << >> to be returned",
|
||||
'bc x inlist [1,2,3], do: x'
|
||||
end
|
||||
|
||||
test :invalid_bc_inbits_gen do
|
||||
assert "nofile:1: a bit comprehension expects a bit string << >> to be used in inbits generators" ==
|
||||
format_rescue 'bc x inbits "123", do: <<x>>'
|
||||
assert_compile_fail SyntaxError, "nofile:1: a bit comprehension expects a bit string << >> to be used in inbits generators",
|
||||
'bc x inbits "123", do: <<x>>'
|
||||
end
|
||||
|
||||
test :unbound_cond do
|
||||
assert "unbound variable _ inside cond. If you want the last clause to match, you probably meant to use true ->" ==
|
||||
format_rescue 'cond do _ -> true end'
|
||||
assert_compile_fail ArgumentError, "unbound variable _ inside cond. If you want the last clause to match, you probably meant to use true ->",
|
||||
'cond do _ -> true end'
|
||||
end
|
||||
|
||||
test :fun_different_arities do
|
||||
assert "nofile:1: cannot mix clauses with different arities in function definition" ==
|
||||
format_rescue 'fn x -> x; x, y -> x + y end'
|
||||
assert_compile_fail SyntaxError, "nofile:1: cannot mix clauses with different arities in function definition",
|
||||
'fn x -> x; x, y -> x + y end'
|
||||
end
|
||||
|
||||
test :new_line_error do
|
||||
assert "nofile:3: syntax error before: newline" ==
|
||||
format_rescue 'if true do\n foo = [],\n baz\nend'
|
||||
assert_compile_fail SyntaxError, "nofile:3: syntax error before: newline",
|
||||
'if true do\n foo = [],\n baz\nend'
|
||||
end
|
||||
|
||||
test :invalid_var_or_function_on_guard do
|
||||
assert "nofile:1: unknown variable something_that_does_not_exist or cannot invoke function something_that_does_not_exist/0 inside guard" =
|
||||
format_rescue('case [] do; [] when something_that_does_not_exist == [] -> :ok; end')
|
||||
assert_compile_fail SyntaxError, "nofile:1: unknown variable something_that_does_not_exist or cannot invoke function something_that_does_not_exist/0 inside guard",
|
||||
('case [] do; [] when something_that_does_not_exist == [] -> :ok; end')
|
||||
end
|
||||
|
||||
test :bodyless_function do
|
||||
assert {{:module, M, _, {:foo,1}}, [], _} =
|
||||
:elixir.eval(to_char_list("defmodule M do\ndef foo(n)\nend"), [])
|
||||
end
|
||||
|
||||
test :bodyless_function_with_guard do
|
||||
assert_compile_fail SyntaxError, "nofile:2: missing keyword do in def",
|
||||
("defmodule Foo do\ndef foo(n) when is_number(n)\nend")
|
||||
end
|
||||
|
||||
test :invalid_function_on_match do
|
||||
assert "nofile:1: cannot invoke function something_that_does_not_exist/0 inside match" =
|
||||
format_rescue('case [] do; something_that_does_not_exist() -> :ok; end')
|
||||
assert_compile_fail SyntaxError, "nofile:1: cannot invoke function something_that_does_not_exist/0 inside match",
|
||||
('case [] do; something_that_does_not_exist() -> :ok; end')
|
||||
end
|
||||
|
||||
test :invalid_remote_on_match do
|
||||
assert "nofile:1: cannot invoke remote function Hello.something_that_does_not_exist/0 inside match" =
|
||||
format_rescue('case [] do; Hello.something_that_does_not_exist() -> :ok; end')
|
||||
assert_compile_fail SyntaxError, "nofile:1: cannot invoke remote function Hello.something_that_does_not_exist/0 inside match",
|
||||
('case [] do; Hello.something_that_does_not_exist() -> :ok; end')
|
||||
end
|
||||
|
||||
test :invalid_remote_on_guard do
|
||||
assert "nofile:1: cannot invoke remote function Hello.something_that_does_not_exist/0 inside guard" =
|
||||
format_rescue('case [] do; [] when Hello.something_that_does_not_exist == [] -> :ok; end')
|
||||
assert_compile_fail SyntaxError, "nofile:1: cannot invoke remote function Hello.something_that_does_not_exist/0 inside guard",
|
||||
('case [] do; [] when Hello.something_that_does_not_exist == [] -> :ok; end')
|
||||
end
|
||||
|
||||
test :macros_error_stacktrace do
|
||||
@@ -313,17 +336,6 @@ defmodule Kernel.ErrorsTest do
|
||||
|
||||
## Helpers
|
||||
|
||||
defp format_rescue(expr) do
|
||||
result = try do
|
||||
:elixir.eval(to_char_list(expr), [])
|
||||
nil
|
||||
rescue
|
||||
error -> error.message
|
||||
end
|
||||
|
||||
result || raise(ExUnit.AssertionError, message: "Expected function given to format_rescue to fail")
|
||||
end
|
||||
|
||||
defp rescue_stacktrace(expr) do
|
||||
result = try do
|
||||
:elixir.eval(to_char_list(expr), [])
|
||||
@@ -332,6 +344,6 @@ defmodule Kernel.ErrorsTest do
|
||||
_ -> System.stacktrace
|
||||
end
|
||||
|
||||
result || raise(ExUnit.AssertionError, message: "Expected function given to format_rescue to fail")
|
||||
result || raise(ExUnit.AssertionError, message: "Expected function given to rescue_stacktrace to fail")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,10 +47,6 @@ defmodule Kernel.Overridable do
|
||||
x + y
|
||||
end
|
||||
|
||||
def super_with_implicit_args(x, y) do
|
||||
x + y
|
||||
end
|
||||
|
||||
def many_clauses(0) do
|
||||
11
|
||||
end
|
||||
@@ -59,7 +55,7 @@ defmodule Kernel.Overridable do
|
||||
13
|
||||
end
|
||||
|
||||
defoverridable [implicit_nested_super: 0, super_with_implicit_args: 2,
|
||||
defoverridable [implicit_nested_super: 0,
|
||||
super_with_explicit_args: 2, many_clauses: 1]
|
||||
|
||||
def without_super do
|
||||
@@ -82,14 +78,6 @@ defmodule Kernel.Overridable do
|
||||
super x, y * 2
|
||||
end
|
||||
|
||||
def super_with_implicit_args(x, y) do
|
||||
if true do
|
||||
x + y + super
|
||||
else
|
||||
raise :NEVER
|
||||
end
|
||||
end
|
||||
|
||||
def many_clauses(2) do
|
||||
17
|
||||
end
|
||||
@@ -98,8 +86,8 @@ defmodule Kernel.Overridable do
|
||||
super(0) + super(1)
|
||||
end
|
||||
|
||||
def many_clauses(_) do
|
||||
super
|
||||
def many_clauses(x) do
|
||||
super(x)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -131,10 +119,6 @@ defmodule Kernel.OverridableTest do
|
||||
assert Overridable.super_with_explicit_args(1, 2) == 5
|
||||
end
|
||||
|
||||
test "calling super with implicit args" do
|
||||
assert Overridable.super_with_implicit_args(1, 2) == 6
|
||||
end
|
||||
|
||||
test "function without overridable returns false for super?" do
|
||||
assert Overridable.no_overridable == { :no_overridable, false }
|
||||
end
|
||||
@@ -160,4 +144,4 @@ defmodule Kernel.OverridableTest do
|
||||
"Overridable functions available are: bar/0")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -102,6 +102,16 @@ defmodule Kernel.QuoteTest do
|
||||
assert quote(do: [unquote_splicing(contents)|[1,2,3]]) == [1,2,3,1,2,3]
|
||||
end
|
||||
|
||||
test :splice_on_stab do
|
||||
{ fun, [] } =
|
||||
Code.eval_quoted(quote(do: fn(unquote_splicing([1,2,3])) -> :ok end), [])
|
||||
assert fun.(1,2,3) == :ok
|
||||
|
||||
{ fun, [] } =
|
||||
Code.eval_quoted(quote(do: fn(1, unquote_splicing([2,3])) -> :ok end), [])
|
||||
assert fun.(1,2,3) == :ok
|
||||
end
|
||||
|
||||
test :stab do
|
||||
assert { :->, _, [{[],_}] } = (quote do -> end)
|
||||
assert { :->, _, [{[],_}] } = (quote do: (->))
|
||||
|
||||
@@ -65,6 +65,11 @@ defmodule Kernel.SigilsTest do
|
||||
|
||||
bad_modifier = quote do: %w(foo bar baz)x
|
||||
assert ArgumentError[] = catch_error(Code.eval_quoted(bad_modifier))
|
||||
|
||||
assert %w(Foo Bar)a == [:"Foo", :"Bar"]
|
||||
assert %w(Foo.#{Bar}.Baz)a == [:"Foo.Elixir.Bar.Baz"]
|
||||
assert %w(Foo.Bar)b == ["Foo.Bar"]
|
||||
assert %w(Foo.#{Bar})c == ['Foo.Elixir.Bar']
|
||||
end
|
||||
|
||||
test :__W__ do
|
||||
@@ -82,5 +87,8 @@ defmodule Kernel.SigilsTest do
|
||||
|
||||
bad_modifier = quote do: %W(foo bar baz)x
|
||||
assert ArgumentError[] = catch_error(Code.eval_quoted(bad_modifier))
|
||||
|
||||
assert %W(Foo #{Bar})a == [:"Foo", :"\#{Bar}"]
|
||||
assert %W(Foo.Bar.Baz)a == [:"Foo.Bar.Baz"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Kernel.WarningTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
import ExUnit.CaptureIO
|
||||
|
||||
test :unused_variable do
|
||||
assert capture_io(fn ->
|
||||
Code.eval_string """
|
||||
defmodule Sample do
|
||||
def hello(arg), do: nil
|
||||
end
|
||||
"""
|
||||
end) =~ "variable arg is unused"
|
||||
after
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test :unused_function do
|
||||
assert capture_io(fn ->
|
||||
Code.eval_string """
|
||||
defmodule Sample1 do
|
||||
defp hello, do: nil
|
||||
end
|
||||
"""
|
||||
end) =~ "function hello/0 is unused"
|
||||
|
||||
assert capture_io(fn ->
|
||||
Code.eval_string """
|
||||
defmodule Sample2 do
|
||||
defp hello(0), do: hello(1)
|
||||
defp hello(1), do: :ok
|
||||
end
|
||||
"""
|
||||
end) =~ "function hello/1 is unused"
|
||||
|
||||
assert capture_io(fn ->
|
||||
Code.eval_string """
|
||||
defmodule Sample3 do
|
||||
def a, do: nil
|
||||
def b, do: d(10)
|
||||
defp c(x, y // 1), do: [x, y]
|
||||
defp d(x), do: x
|
||||
end
|
||||
"""
|
||||
end) =~ "function c/2 is unused"
|
||||
|
||||
after
|
||||
purge [Sample1, Sample2, Sample3]
|
||||
end
|
||||
|
||||
test :unused_cyclic_functions do
|
||||
assert capture_io(fn ->
|
||||
Code.eval_string """
|
||||
defmodule Sample do
|
||||
defp a, do: b
|
||||
defp b, do: a
|
||||
end
|
||||
"""
|
||||
end) =~ "function a/0 is unused"
|
||||
after
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test :unused_macro do
|
||||
assert capture_io(fn ->
|
||||
Code.eval_string """
|
||||
defmodule Sample do
|
||||
defmacrop hello, do: nil
|
||||
end
|
||||
"""
|
||||
end) =~ "macro hello/0 is unused"
|
||||
after
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test :unused_default_args do
|
||||
assert capture_io(fn ->
|
||||
Code.eval_string """
|
||||
defmodule Sample1 do
|
||||
def a, do: b(1,2,3)
|
||||
defp b(arg1 // 1, arg2 // 2, arg3 // 3), do: [arg1, arg2, arg3]
|
||||
end
|
||||
"""
|
||||
end) =~ "default arguments in b/3 are never used"
|
||||
|
||||
assert capture_io(fn ->
|
||||
Code.eval_string """
|
||||
defmodule Sample2 do
|
||||
def a, do: b(1,2)
|
||||
defp b(arg1 // 1, arg2 // 2, arg3 // 3), do: [arg1, arg2, arg3]
|
||||
end
|
||||
"""
|
||||
end) =~ "the first 2 default arguments in b/3 are never used"
|
||||
|
||||
assert capture_io(fn ->
|
||||
Code.eval_string """
|
||||
defmodule Sample3 do
|
||||
def a, do: b(1)
|
||||
defp b(arg1 // 1, arg2 // 2, arg3 // 3), do: [arg1, arg2, arg3]
|
||||
end
|
||||
"""
|
||||
end) =~ "the first default argument in b/3 is never used"
|
||||
|
||||
assert capture_io(fn ->
|
||||
Code.eval_string """
|
||||
defmodule Sample4 do
|
||||
def a, do: b(1)
|
||||
defp b(arg1 // 1, arg2, arg3 // 3), do: [arg1, arg2, arg3]
|
||||
end
|
||||
"""
|
||||
end) == nil
|
||||
after
|
||||
purge [Sample1, Sample2, Sample3, Sample4]
|
||||
end
|
||||
|
||||
test :unused_import do
|
||||
assert capture_io(fn ->
|
||||
Code.eval_string """
|
||||
defmodule Sample1 do
|
||||
def hello, do: nil
|
||||
end
|
||||
|
||||
defmodule Sample2 do
|
||||
import Sample1
|
||||
def a, do: nil
|
||||
end
|
||||
"""
|
||||
end) =~ "unused import Sample1"
|
||||
after
|
||||
purge [Sample1, Sample2]
|
||||
end
|
||||
|
||||
test :clause_not_match do
|
||||
assert capture_io(fn ->
|
||||
Code.eval_string """
|
||||
defmodule Sample do
|
||||
def hello, do: nil
|
||||
def hello, do: nil
|
||||
end
|
||||
"""
|
||||
end) =~ "this clause cannot match because a previous clause at line 2 always matches"
|
||||
after
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test :clause_with_defaults_should_be_first do
|
||||
assert capture_io(fn ->
|
||||
Code.eval_string """
|
||||
defmodule Sample do
|
||||
def hello(arg), do: nil
|
||||
def hello(arg // 0), do: nil
|
||||
end
|
||||
"""
|
||||
end) =~ "clause with defaults should be the first clause in def hello/1"
|
||||
after
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test :multiple_clauses_with_defaults do
|
||||
assert capture_io(fn ->
|
||||
Code.eval_string """
|
||||
defmodule Sample do
|
||||
def hello(arg // 0), do: nil
|
||||
def hello(arg // 1), do: nil
|
||||
end
|
||||
"""
|
||||
end) =~ "def hello/1 has default values and multiple clauses, use a separate clause for declaring defaults"
|
||||
after
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test :unused_with_local_with_overridable do
|
||||
assert capture_io(fn ->
|
||||
Code.eval_string """
|
||||
defmodule Sample do
|
||||
def hello, do: world
|
||||
defp world, do: :ok
|
||||
defoverridable [hello: 0]
|
||||
def hello, do: :ok
|
||||
end
|
||||
"""
|
||||
end) =~ "function world/0 is unused"
|
||||
after
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test :used_with_local_with_reattached_overridable do
|
||||
assert nil? capture_io(fn ->
|
||||
Code.eval_string """
|
||||
defmodule Sample do
|
||||
def hello, do: world
|
||||
defp world, do: :ok
|
||||
defoverridable [hello: 0, world: 0]
|
||||
end
|
||||
"""
|
||||
end)
|
||||
after
|
||||
purge Sample
|
||||
end
|
||||
|
||||
defp purge(list) when is_list(list) do
|
||||
Enum.each list, purge(&1)
|
||||
end
|
||||
|
||||
defp purge(module) when is_atom(module) do
|
||||
:code.delete module
|
||||
:code.purge module
|
||||
end
|
||||
end
|
||||
@@ -4,8 +4,24 @@ defmodule KernelTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test :match do
|
||||
assert "abcd" =~ %r/c(d)/
|
||||
refute "abcd" =~ %r/e/
|
||||
assert ("abcd" =~ %r/c(d)/) == true
|
||||
assert ("abcd" =~ %r/e/) == false
|
||||
|
||||
string = "^ab+cd*$"
|
||||
assert (string =~ "ab+") == true
|
||||
assert (string =~ "bb") == false
|
||||
|
||||
assert_raise ArgumentError, "bad argument on the right side of =~: [\"^a\",\"*$\"]", fn ->
|
||||
string =~ ["^a", "*$"]
|
||||
end
|
||||
|
||||
assert_raise ArgumentError, "argument error", fn ->
|
||||
1234 =~ "hello"
|
||||
end
|
||||
|
||||
assert_raise ArgumentError, "argument error", fn ->
|
||||
1234 =~ %r"hello"
|
||||
end
|
||||
end
|
||||
|
||||
test :nil? do
|
||||
@@ -63,7 +79,7 @@ defmodule KernelTest do
|
||||
end
|
||||
|
||||
test :__MODULE__ do
|
||||
assert __MODULE__ == :"Elixir-KernelTest"
|
||||
assert __MODULE__ == :"Elixir.KernelTest"
|
||||
end
|
||||
|
||||
test :function_from___ENV__ do
|
||||
@@ -102,20 +118,14 @@ defmodule KernelTest do
|
||||
assert float_to_binary(7.0) == "7.00000000000000000000e+00"
|
||||
end
|
||||
|
||||
case :proplists.get_value(:float_to_binary,
|
||||
:proplists.get_value(:exports, :erlang.module_info, [])) do
|
||||
2 ->
|
||||
# We can only test this where this functionality is available
|
||||
test :float_to_binary_with_options do
|
||||
assert float_to_binary(7.1, [decimals: 2]) == "7.10"
|
||||
assert float_to_binary(7.1, [scientific: 2]) == "7.10e+00"
|
||||
assert float_to_binary(7.1, [decimals: 2, compact: true]) == "7.1"
|
||||
assert float_to_binary(7.1, [scientific: 2, compact: true]) == "7.10e+00"
|
||||
assert float_to_binary(7.1, [decimals: 2, compact: false]) == "7.10"
|
||||
end
|
||||
_ ->
|
||||
:ok
|
||||
test :float_to_binary_with_options do
|
||||
assert float_to_binary(7.1, [decimals: 2]) == "7.10"
|
||||
assert float_to_binary(7.1, [scientific: 2]) == "7.10e+00"
|
||||
assert float_to_binary(7.1, [decimals: 2, compact: true]) == "7.1"
|
||||
assert float_to_binary(7.1, [scientific: 2, compact: true]) == "7.10e+00"
|
||||
assert float_to_binary(7.1, [decimals: 2, compact: false]) == "7.10"
|
||||
end
|
||||
|
||||
test :atom_to_binary_defaults_to_utf8 do
|
||||
expected = atom_to_binary :some_binary, :utf8
|
||||
actual = atom_to_binary :some_binary
|
||||
@@ -233,6 +243,18 @@ defmodule KernelTest do
|
||||
assert __MODULE__ |> :constant == 13
|
||||
end
|
||||
|
||||
test "non-call" do
|
||||
assert 1 |> (&1*2).() == 2
|
||||
assert [1] |> hd(&1).() == 1
|
||||
|
||||
import CompileAssertion
|
||||
|
||||
# FIXME: this mustn't work, but it doesn't call pipeline_op at all
|
||||
#assert_compile_fail ArgumentError, "Unsupported expression in pipeline |> operator: &1", "1 |> &1"
|
||||
assert_compile_fail ArgumentError, "Unsupported expression in pipeline |> operator: &1 * 2", "1 |> &1*2"
|
||||
assert_compile_fail ArgumentError, "Unsupported expression in pipeline |> operator: hd(&1)", "[1] |> hd(&1)"
|
||||
end
|
||||
|
||||
def constant, do: 13
|
||||
|
||||
defp twice(a), do: a * 2
|
||||
|
||||
@@ -25,7 +25,7 @@ defmodule KeywordTest do
|
||||
list = [{:b,2},{:a,1},{:c,3}]
|
||||
dict = HashDict.new list
|
||||
assert Keyword.from_enum(list) == [b: 2, a: 1, c: 3]
|
||||
assert Keyword.from_enum(dict) == [a: 1, b: 2, c: 3]
|
||||
assert Keyword.equal?(Keyword.from_enum(dict), [a: 1, b: 2, c: 3])
|
||||
end
|
||||
|
||||
test :keyword? do
|
||||
|
||||
@@ -23,10 +23,8 @@ defmodule List.Chars.NumberTest do
|
||||
assert to_char_list(1) == '1'
|
||||
end
|
||||
|
||||
unless :erlang.system_info(:otp_release) < 'R16' do
|
||||
test :float do
|
||||
assert to_char_list(1.0) == '1.0'
|
||||
end
|
||||
test :float do
|
||||
assert to_char_list(1.0) == '1.0'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule Module.DispatchTrackerTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Module.DispatchTracker, as: D
|
||||
|
||||
setup do
|
||||
{ :ok, [pid: D.start_link] }
|
||||
end
|
||||
|
||||
teardown config do
|
||||
D.stop(config[:pid])
|
||||
:ok
|
||||
end
|
||||
|
||||
## Locals
|
||||
|
||||
test "can add definitions", config do
|
||||
D.add_definition(config[:pid], :def, { :foo, 1 })
|
||||
D.add_definition(config[:pid], :defp, { :bar, 1 })
|
||||
end
|
||||
|
||||
test "can add locals", config do
|
||||
D.add_definition(config[:pid], :def, { :foo, 1 })
|
||||
D.add_local(config[:pid], { :foo, 1 }, { :bar, 1 })
|
||||
end
|
||||
|
||||
test "public definitions are always reachable", config do
|
||||
D.add_definition(config[:pid], :def, { :public, 1 })
|
||||
assert { :public, 1 } in D.reachable(config[:pid])
|
||||
|
||||
D.add_definition(config[:pid], :defmacro, { :public, 2 })
|
||||
assert { :public, 2 } in D.reachable(config[:pid])
|
||||
end
|
||||
|
||||
test "private definitions are never reachable", config do
|
||||
D.add_definition(config[:pid], :defp, { :private, 1 })
|
||||
refute { :private, 1 } in D.reachable(config[:pid])
|
||||
|
||||
D.add_definition(config[:pid], :defmacrop, { :private, 2 })
|
||||
refute { :private, 2 } in D.reachable(config[:pid])
|
||||
end
|
||||
|
||||
test "private definitions are reachable when connected to local", config do
|
||||
D.add_definition(config[:pid], :defp, { :private, 1 })
|
||||
refute { :private, 1 } in D.reachable(config[:pid])
|
||||
|
||||
D.add_local(config[:pid], { :private, 1 })
|
||||
assert { :private, 1 } in D.reachable(config[:pid])
|
||||
end
|
||||
|
||||
test "private definitions are reachable when connected through a public one", config do
|
||||
D.add_definition(config[:pid], :defp, { :private, 1 })
|
||||
refute { :private, 1 } in D.reachable(config[:pid])
|
||||
|
||||
D.add_definition(config[:pid], :def, { :public, 1 })
|
||||
D.add_local(config[:pid], { :public, 1 }, { :private, 1 })
|
||||
assert { :private, 1 } in D.reachable(config[:pid])
|
||||
end
|
||||
|
||||
@unused [
|
||||
{ { :private, 1 }, :defp, 0 }
|
||||
]
|
||||
|
||||
test "unused private definitions are marked as so", config do
|
||||
D.add_definition(config[:pid], :def, { :public, 1 })
|
||||
|
||||
unused = D.collect_unused_locals(config[:pid], @unused)
|
||||
assert unused == [{ :unused_def, { :private, 1 }, :defp }]
|
||||
|
||||
D.add_local(config[:pid], { :public, 1 }, { :private, 1 })
|
||||
unused = D.collect_unused_locals(config[:pid], @unused)
|
||||
refute unused == [{ :unused_def, { :private, 1 }, :defp }]
|
||||
end
|
||||
|
||||
@unused [
|
||||
{ { :private, 3 }, :defp, 3 }
|
||||
]
|
||||
|
||||
test "private definitions with unused default arguments", config do
|
||||
D.add_definition(config[:pid], :def, { :public, 1 })
|
||||
|
||||
unused = D.collect_unused_locals(config[:pid], @unused)
|
||||
assert unused == [{ :unused_def, { :private, 3 }, :defp }]
|
||||
|
||||
D.add_local(config[:pid], { :public, 1 }, { :private, 3 })
|
||||
unused = D.collect_unused_locals(config[:pid], @unused)
|
||||
assert unused == [{ :unused_args, { :private, 3 }}]
|
||||
end
|
||||
|
||||
test "private definitions with some unused default arguments", config do
|
||||
D.add_definition(config[:pid], :def, { :public, 1 })
|
||||
D.add_local(config[:pid], { :public, 1 }, { :private, 1 })
|
||||
unused = D.collect_unused_locals(config[:pid], @unused)
|
||||
assert unused == [{ :unused_args, { :private, 3 }, 1}]
|
||||
end
|
||||
|
||||
test "private definitions with all used default arguments", config do
|
||||
D.add_definition(config[:pid], :def, { :public, 1 })
|
||||
D.add_local(config[:pid], { :public, 1 }, { :private, 0 })
|
||||
unused = D.collect_unused_locals(config[:pid], @unused)
|
||||
assert unused == []
|
||||
end
|
||||
|
||||
## Defaults
|
||||
|
||||
test "can add defaults", config do
|
||||
D.add_definition(config[:pid], :def, { :foo, 4 })
|
||||
D.add_defaults(config[:pid], :def, { :foo, 4 }, 2)
|
||||
end
|
||||
|
||||
test "defaults are reachable if public", config do
|
||||
D.add_definition(config[:pid], :def, { :foo, 4 })
|
||||
D.add_defaults(config[:pid], :def, { :foo, 4 }, 2)
|
||||
assert { :foo, 2 } in D.reachable(config[:pid])
|
||||
assert { :foo, 3 } in D.reachable(config[:pid])
|
||||
end
|
||||
|
||||
test "defaults are not reachable if private", config do
|
||||
D.add_definition(config[:pid], :defp, { :foo, 4 })
|
||||
D.add_defaults(config[:pid], :defp, { :foo, 4 }, 2)
|
||||
refute { :foo, 2 } in D.reachable(config[:pid])
|
||||
refute { :foo, 3 } in D.reachable(config[:pid])
|
||||
end
|
||||
|
||||
test "defaults are connected", config do
|
||||
D.add_definition(config[:pid], :defp, { :foo, 4 })
|
||||
D.add_defaults(config[:pid], :defp, { :foo, 4 }, 2)
|
||||
D.add_local(config[:pid], { :foo, 2 })
|
||||
assert { :foo, 2 } in D.reachable(config[:pid])
|
||||
assert { :foo, 3 } in D.reachable(config[:pid])
|
||||
assert { :foo, 4 } in D.reachable(config[:pid])
|
||||
end
|
||||
|
||||
## Imports
|
||||
|
||||
test "can add import", config do
|
||||
D.add_import(config[:pid], nil, Module, { :concat, 1 })
|
||||
end
|
||||
|
||||
test "can retrieve imports", config do
|
||||
D.add_import(config[:pid], nil, Module, { :concat, 1 })
|
||||
assert Module in D.imports(config[:pid])
|
||||
end
|
||||
|
||||
test "find imports from dispatch", config do
|
||||
D.add_import(config[:pid], nil, Module, { :concat, 1 })
|
||||
assert Module in D.imports_with_dispatch(config[:pid], { :concat, 1 })
|
||||
refute Module in D.imports_with_dispatch(config[:pid], { :unknown, 1 })
|
||||
end
|
||||
|
||||
test "can add warnable", config do
|
||||
D.add_warnable(config[:pid], Module, true, 15)
|
||||
assert Module in D.imports(config[:pid])
|
||||
end
|
||||
|
||||
test "find unused imports", config do
|
||||
D.add_warnable(config[:pid], Module, true, 15)
|
||||
assert { Module, 15 } in D.collect_unused_imports(config[:pid])
|
||||
|
||||
D.add_warnable(config[:pid], Module, false, 15)
|
||||
refute { Module, 15 } in D.collect_unused_imports(config[:pid])
|
||||
end
|
||||
|
||||
test "find import conflicts", config do
|
||||
refute { [Module], :conflict, 1 } in D.collect_imports_conflicts(config[:pid], [conflict: 1])
|
||||
|
||||
D.add_import(config[:pid], nil, Module, { :conflict, 1 })
|
||||
assert { [Module], :conflict, 1 } in D.collect_imports_conflicts(config[:pid], [conflict: 1])
|
||||
end
|
||||
|
||||
## Remotes
|
||||
|
||||
test "can add remote", config do
|
||||
D.add_remote(config[:pid], nil, Module, { :concat, 1 })
|
||||
end
|
||||
|
||||
test "can retrieve remotes", config do
|
||||
D.add_remote(config[:pid], nil, Module, { :concat, 1 })
|
||||
assert Module in D.remotes(config[:pid])
|
||||
end
|
||||
|
||||
test "find remotes from dispatch", config do
|
||||
D.add_remote(config[:pid], nil, Module, { :concat, 1 })
|
||||
assert Module in D.remotes_with_dispatch(config[:pid], { :concat, 1 })
|
||||
refute Module in D.remotes_with_dispatch(config[:pid], { :unknown, 1 })
|
||||
end
|
||||
|
||||
test "remotes are not reachable", config do
|
||||
D.add_definition(config[:pid], :def, { :public, 1 })
|
||||
D.add_remote(config[:pid], { :public, 1 }, Module, { :concat, 1 })
|
||||
assert D.reachable(config[:pid]) == [{ :public, 1 }]
|
||||
end
|
||||
|
||||
## All
|
||||
|
||||
test "can query dispatches", config do
|
||||
D.add_definition(config[:pid], :def, { :public, 1 })
|
||||
D.add_definition(config[:pid], :def, { :caller, 1 })
|
||||
D.add_local(config[:pid], { :caller, 1 }, { :public, 1 })
|
||||
D.add_remote(config[:pid], { :public, 1 }, Module, { :concat, 1 })
|
||||
D.add_import(config[:pid], { :public, 1 }, Kernel, { :if, 2 })
|
||||
|
||||
assert D.dispatches_to(config[:pid], { :public, 1 }) == [{ :caller, 1 }]
|
||||
assert D.dispatches_to(config[:pid], { :import, :if, 2 }) == [{ :public, 1 }]
|
||||
assert D.dispatches_to(config[:pid], { :remote, :concat, 1 }) == [{ :public, 1 }]
|
||||
assert D.dispatches_to(config[:pid], Module) == [{ :remote, :concat, 1 }]
|
||||
assert D.dispatches_to(config[:pid], Kernel) == [{ :import, :if, 2 }]
|
||||
|
||||
assert { :public, 1 } in D.dispatches_from(config[:pid], { :caller, 1 })
|
||||
assert { :remote, :concat, 1 } in D.dispatches_from(config[:pid], { :public, 1 })
|
||||
assert { :import, :if, 2 } in D.dispatches_from(config[:pid], { :public, 1 })
|
||||
end
|
||||
end
|
||||
@@ -115,7 +115,7 @@ defmodule ModuleTest do
|
||||
end
|
||||
|
||||
test :alias_with_raw_atom do
|
||||
defmodule :"Elixir-ModuleTest-RawModule" do
|
||||
defmodule :"Elixir.ModuleTest.RawModule" do
|
||||
def hello, do: :world
|
||||
end
|
||||
|
||||
@@ -161,6 +161,7 @@ defmodule ModuleTest do
|
||||
assert Module.concat(Foo, 'Bar') == Foo.Bar
|
||||
assert Module.concat(Foo, Bar.Baz) == Foo.Bar.Baz
|
||||
assert Module.concat(Foo, "Bar.Baz") == Foo.Bar.Baz
|
||||
assert Module.concat(Bar, :nil) == :"Elixir.Bar.nil"
|
||||
end
|
||||
|
||||
test :safe_concat do
|
||||
@@ -173,7 +174,7 @@ defmodule ModuleTest do
|
||||
test :split do
|
||||
module = Very.Long.Module.Name.And.Even.Longer
|
||||
assert Module.split(module) == ["Very", "Long", "Module", "Name", "And", "Even", "Longer"]
|
||||
assert Module.split("Elixir-Very-Long") == ["Very", "Long"]
|
||||
assert Module.split("Elixir.Very.Long") == ["Very", "Long"]
|
||||
assert Module.concat(Module.split(module)) == module
|
||||
end
|
||||
|
||||
|
||||
@@ -64,15 +64,15 @@ defmodule PathTest do
|
||||
assert Path.type("C:\\usr\\local\\bin") == :absolute
|
||||
assert Path.type("C:usr\\local\\bin") == :volumerelative
|
||||
|
||||
assert Path.type("/usr/local/bin") == :absolute
|
||||
assert Path.type("/usr/local/bin") == :volumerelative
|
||||
assert Path.type("usr/local/bin") == :relative
|
||||
assert Path.type("../usr/local/bin") == :relative
|
||||
|
||||
assert Path.type('/usr/local/bin') == :absolute
|
||||
assert Path.type('/usr/local/bin') == :volumerelative
|
||||
assert Path.type('usr/local/bin') == :relative
|
||||
assert Path.type('../usr/local/bin') == :relative
|
||||
|
||||
assert Path.type(['/usr/', 'local/bin']) == :absolute
|
||||
assert Path.type(['/usr/', 'local/bin']) == :volumerelative
|
||||
assert Path.type(['usr/', 'local/bin']) == :relative
|
||||
assert Path.type(['../usr', '/local/bin']) == :relative
|
||||
end
|
||||
@@ -238,13 +238,13 @@ defmodule PathTest do
|
||||
end
|
||||
|
||||
test :split_with_binary do
|
||||
assert Path.split("") == ["/"]
|
||||
assert Path.split("") == []
|
||||
assert Path.split("foo") == ["foo"]
|
||||
assert Path.split("/foo/bar") == ["/", "foo", "bar"]
|
||||
end
|
||||
|
||||
test :split_with_list do
|
||||
assert Path.split('') == ''
|
||||
assert Path.split('') == []
|
||||
assert Path.split('foo') == ['foo']
|
||||
assert Path.split('/foo/bar') == ['/', 'foo', 'bar']
|
||||
end
|
||||
|
||||
@@ -104,6 +104,12 @@ defmodule ProtocolTest do
|
||||
false = ProtocolTest.WithAll.blank(ProtocolTest.Foo.new(a: 1))
|
||||
end
|
||||
|
||||
test :protocol_with_nil_record do
|
||||
assert_raise UndefinedFunctionError, fn ->
|
||||
ProtocolTest.WithOnly.blank({:nil})
|
||||
end
|
||||
end
|
||||
|
||||
test :protocol_for do
|
||||
assert_protocol_for(ProtocolTest.WithAll, Atom, :foo)
|
||||
assert_protocol_for(ProtocolTest.WithAll, Function, fn(x) -> x end)
|
||||
|
||||
@@ -52,12 +52,27 @@ end
|
||||
defmodule RecordTest.Macros do
|
||||
defrecordp :_user, name: "José", age: 25
|
||||
|
||||
defmacro gen do
|
||||
quote do
|
||||
alias RecordTest.Macros.Nested
|
||||
|
||||
def this_works, do: RecordTest.Macros.Nested[]
|
||||
def this_should_too, do: Nested[]
|
||||
end
|
||||
end
|
||||
|
||||
defrecord Nested do
|
||||
def nested_record_alias?(Nested[]) do
|
||||
true
|
||||
end
|
||||
|
||||
defrecord NestedInNested, it_compiles: true
|
||||
end
|
||||
|
||||
# Ensure there is no conflict in a nested module
|
||||
# named record.
|
||||
defrecord Record, [a: 1, b: 2]
|
||||
|
||||
def new() do
|
||||
_user()
|
||||
end
|
||||
@@ -94,6 +109,11 @@ end
|
||||
defmodule RecordTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
# Check the access from the generated macro works
|
||||
# as expected. If it compiles, we are good to go.
|
||||
require RecordTest.Macros
|
||||
RecordTest.Macros.gen
|
||||
|
||||
test :record_access_with_nil_keyword do
|
||||
record = RecordTest.DynamicName.new(a: nil)
|
||||
record_access = RecordTest.DynamicName[a: nil]
|
||||
|
||||
@@ -8,6 +8,10 @@ defmodule Regex.BinaryTest do
|
||||
assert Regex.match?(%r/^b$/m, "a\nb\nc")
|
||||
end
|
||||
|
||||
test :precedence do
|
||||
assert { "aa", :unknown } |> elem(0) =~ %r/(a)\1/
|
||||
end
|
||||
|
||||
test :backreference do
|
||||
assert "aa" =~ %r/(a)\1/
|
||||
end
|
||||
@@ -36,7 +40,7 @@ defmodule Regex.BinaryTest do
|
||||
end
|
||||
|
||||
test :unicode do
|
||||
assert ("josé" =~ %r"\p{Latin}$"u) == 3
|
||||
assert ("josé" =~ %r"\p{Latin}$"u)
|
||||
end
|
||||
|
||||
test :groups do
|
||||
@@ -89,11 +93,6 @@ defmodule Regex.BinaryTest do
|
||||
assert Regex.run(%r"e", "abcd", return: :index) == nil
|
||||
end
|
||||
|
||||
test :index do
|
||||
assert Regex.index(%r"c(d)", "abcd") == 2
|
||||
assert Regex.index(%r"e", "abcd") == nil
|
||||
end
|
||||
|
||||
test :scan do
|
||||
assert Regex.scan(%r"c(d|e)", "abcd abce") == [["d"], ["e"]]
|
||||
assert Regex.scan(%r"c(?:d|e)", "abcd abce") == ["cd", "ce"]
|
||||
@@ -123,6 +122,38 @@ defmodule Regex.BinaryTest do
|
||||
assert Regex.replace(%r(b), "abcbe", "[\\&]") == "a[&]c[&]e"
|
||||
assert Regex.replace(%r[(b)], "abcbe", "[\\1]") == "a[b]c[b]e"
|
||||
end
|
||||
|
||||
test :escape do
|
||||
assert matches_escaped?(".")
|
||||
refute matches_escaped?(".", "x")
|
||||
|
||||
assert matches_escaped?("[\w]")
|
||||
refute matches_escaped?("[\w]", "x")
|
||||
|
||||
assert matches_escaped?("\\")
|
||||
|
||||
assert matches_escaped?("\\xff", "\\xff")
|
||||
refute matches_escaped?("\\xff", "\xff")
|
||||
|
||||
assert matches_escaped?("(")
|
||||
assert matches_escaped?("()")
|
||||
assert matches_escaped?("(?:foo)")
|
||||
|
||||
assert matches_escaped?("\\A \\z")
|
||||
assert matches_escaped?(" x ")
|
||||
assert matches_escaped?(" x x ") # unicode spaces here
|
||||
assert matches_escaped?("# lol")
|
||||
|
||||
assert matches_escaped?("\\A.^$*+?()[{\\| \t\n\xff\\z #hello\x{202F}\x{205F}")
|
||||
end
|
||||
|
||||
defp matches_escaped?(string) do
|
||||
matches_escaped?(string, string)
|
||||
end
|
||||
|
||||
defp matches_escaped?(string, match) do
|
||||
Regex.match? %r/#{Regex.escape(string)}/usimx, match
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Regex.ListTest do
|
||||
@@ -147,11 +178,6 @@ defmodule Regex.ListTest do
|
||||
assert Regex.run(%r"c(d)", "abcd", return: :binary) == ["cd", "d"]
|
||||
end
|
||||
|
||||
test :index do
|
||||
assert Regex.index(%r'c(d)', 'abcd') == 2
|
||||
assert Regex.index(%r'e', 'abcd') == nil
|
||||
end
|
||||
|
||||
test :indexes do
|
||||
assert Regex.run(%r'c(d)', 'abcd', return: :index) == [{2,2},{3,1}]
|
||||
assert Regex.run(%r'e', 'abcd', return: :index) == nil
|
||||
|
||||
@@ -42,6 +42,7 @@ defmodule StringTest do
|
||||
test :upcase do
|
||||
assert String.upcase("123 abcd 456 efg hij ( %$#) kl mnop @ qrst = -_ uvwxyz") == "123 ABCD 456 EFG HIJ ( %$#) KL MNOP @ QRST = -_ UVWXYZ"
|
||||
assert String.upcase("") == ""
|
||||
assert String.upcase("abcD") == "ABCD"
|
||||
end
|
||||
|
||||
test :upcase_utf8 do
|
||||
@@ -51,16 +52,19 @@ defmodule StringTest do
|
||||
|
||||
test :upcase_utf8_multibyte do
|
||||
assert String.upcase("straße") == "STRASSE"
|
||||
assert String.upcase("áüÈß") == "ÁÜÈSS"
|
||||
end
|
||||
|
||||
test :downcase do
|
||||
assert String.downcase("123 ABcD 456 EfG HIJ ( %$#) KL MNOP @ QRST = -_ UVWXYZ") == "123 abcd 456 efg hij ( %$#) kl mnop @ qrst = -_ uvwxyz"
|
||||
assert String.downcase("abcD") == "abcd"
|
||||
assert String.downcase("") == ""
|
||||
end
|
||||
|
||||
test :downcase_utf8 do
|
||||
assert String.downcase("& % # ÀÁÂ ÃÄÅ 1 2 Ç Æ") == "& % # àáâ ãäå 1 2 ç æ"
|
||||
assert String.downcase("ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ") == "àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþ"
|
||||
assert String.downcase("áüÈß") == "áüèß"
|
||||
end
|
||||
|
||||
test :capitalize do
|
||||
@@ -280,5 +284,90 @@ defmodule StringTest do
|
||||
assert String.to_float("pi") === :error
|
||||
end
|
||||
|
||||
|
||||
test :starts_with? do
|
||||
## Normal cases ##
|
||||
assert String.starts_with? "hello", "he"
|
||||
assert String.starts_with? "hello", "hello"
|
||||
assert String.starts_with? "hello", ["hellö", "hell"]
|
||||
assert String.starts_with? "エリクシア", "エリ"
|
||||
refute String.starts_with? "hello", "lo"
|
||||
refute String.starts_with? "hello", "hellö"
|
||||
refute String.starts_with? "hello", ["hellö", "goodbye"]
|
||||
refute String.starts_with? "エリクシア", "仙丹"
|
||||
|
||||
## Edge cases ##
|
||||
assert String.starts_with? "", ""
|
||||
assert String.starts_with? "", ["", "a"]
|
||||
assert String.starts_with? "b", ["", "a"]
|
||||
|
||||
assert String.starts_with? "abc", ""
|
||||
assert String.starts_with? "abc", [""]
|
||||
|
||||
refute String.starts_with? "", "abc"
|
||||
refute String.starts_with? "", [" "]
|
||||
|
||||
## Sanity checks ##
|
||||
assert String.starts_with? "", ["", ""]
|
||||
assert String.starts_with? "abc", ["", ""]
|
||||
assert_raise ArgumentError, fn ->
|
||||
String.starts_with? "abc", [["a"], "a"]
|
||||
end
|
||||
end
|
||||
|
||||
test :ends_with? do
|
||||
## Normal cases ##
|
||||
assert String.ends_with? "hello", "lo"
|
||||
assert String.ends_with? "hello", "hello"
|
||||
assert String.ends_with? "hello", ["hell", "lo", "xx"]
|
||||
assert String.ends_with? "hello", ["hellö", "lo"]
|
||||
assert String.ends_with? "エリクシア", "シア"
|
||||
refute String.ends_with? "hello", "he"
|
||||
refute String.ends_with? "hello", "hellö"
|
||||
refute String.ends_with? "hello", ["hel", "goodbye"]
|
||||
refute String.ends_with? "エリクシア", "仙丹"
|
||||
|
||||
## Edge cases ##
|
||||
assert String.ends_with? "", ""
|
||||
assert String.ends_with? "", ["", "a"]
|
||||
refute String.ends_with? "", ["a", "b"]
|
||||
|
||||
assert String.ends_with? "abc", ""
|
||||
assert String.ends_with? "abc", ["", "x"]
|
||||
|
||||
refute String.ends_with? "", "abc"
|
||||
refute String.ends_with? "", [" "]
|
||||
|
||||
## Sanity checks ##
|
||||
assert String.ends_with? "", ["", ""]
|
||||
assert String.ends_with? "abc", ["", ""]
|
||||
assert_raise ArgumentError, fn ->
|
||||
String.ends_with? "abc", [["c"], "c"]
|
||||
end
|
||||
end
|
||||
|
||||
test :contains? do
|
||||
## Normal cases ##
|
||||
assert String.contains? "elixir of life", "of"
|
||||
assert String.contains? "エリクシア", "シ"
|
||||
assert String.contains? "elixir of life", ["mercury", "life"]
|
||||
refute String.contains? "exlixir of life", "death"
|
||||
refute String.contains? "エリクシア", "仙"
|
||||
refute String.contains? "elixir of life", ["death", "mercury", "eternal life"]
|
||||
|
||||
## Edge cases ##
|
||||
assert String.contains? "", ""
|
||||
assert String.contains? "abc", ""
|
||||
assert String.contains? "abc", ["", "x"]
|
||||
|
||||
refute String.contains? "", " "
|
||||
refute String.contains? "", "a"
|
||||
|
||||
## Sanity checks ##
|
||||
assert String.contains? "", ["", ""]
|
||||
assert String.contains? "abc", ["", ""]
|
||||
assert_raise ArgumentError, fn ->
|
||||
String.contains? "abc", [["b"], "b"]
|
||||
end
|
||||
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