Compare commits

...
155 Commits
Author SHA1 Message Date
Eric Meadows-Jönsson 82535feb90 Release v0.14.3 2014-07-12 14:49:29 +02:00
José Valim 81f007bdd7 Update CHANGELOG 2014-07-12 14:11:14 +02:00
José Valim 0b35996d5e Optimize and improve docs for Float operations 2014-07-12 14:05:18 +02:00
José Valim fc5cf506ae Merge pull request #2520 from nurugger07/float_modification_merges
Add precision to Float.ceil/1 & Float.floor/1
2014-07-12 12:38:35 +02:00
José Valim c4582e0041 Clarify attributes and exit signals in the language 2014-07-11 22:19:14 +02:00
Eric Meadows-Jönsson c80d215674 Fix stacktrace in Module.get_attribute example
Closes #2522.
2014-07-11 21:56:48 +02:00
José Valim 3ee9d5d0df Merge pull request #2521 from ericmj/record-typespecs
Add typespec syntax for records
2014-07-11 21:08:16 +02:00
Eric Meadows-Jönsson d86ba0e1c7 Add typespec syntax for records 2014-07-11 20:35:35 +02:00
Eric Meadows-Jönsson c295ee50de Allow expansion of local macros via Macro.expand/2 2014-07-11 20:35:35 +02:00
Eric Meadows-Jönsson 54c25a55fe Add compile time optimisation to record fields macro 2014-07-11 20:35:35 +02:00
Johnny Winn 583a4ec5f6 Add precision to Float.ceil/1 & Float.floor/1 2014-07-11 12:34:24 -04:00
José Valim 69b78dad51 Update docs and CHANGELOG 2014-07-11 17:49:34 +02:00
José Valim 0685e0315a Document and list aliases on help --names 2014-07-11 17:32:41 +02:00
José Valim 5e429001d9 Support aliases in Mix.Task.run/2 2014-07-11 17:32:41 +02:00
José Valim 707b810f1b Merge pull request #2517 from ericmj/record-fields
Add field retrieval of records via `record(record())`
2014-07-11 17:31:21 +02:00
José Valim e51cfc142d Merge pull request #2516 from alco/sigil-docs
Make sigil docs more readable in the source
2014-07-11 17:29:02 +02:00
Eric Meadows-Jönsson b259e793bb Fix mix archive in changelog 2014-07-11 17:12:21 +02:00
Eric Meadows-Jönsson 32738a97d9 Add field retrieval of records via record(record()) 2014-07-11 17:09:16 +02:00
Alexei Sholik 45c6fbaced Make sigil doctests more readable in the source 2014-07-11 17:17:42 +03:00
José Valim bba4f1bd1b Optimize and add specs to Mix.Task module 2014-07-10 21:58:35 +02:00
Eric Meadows-Jönsson ade2596e92 Fix bug where umbrella tasks were not reenabled
Closes #2058.
2014-07-10 21:21:23 +02:00
Eric Meadows-Jönsson 0e804a2b5c Merge pull request #2513 from ericmj/mix-mtimes
Warn and reset mime on future files
2014-07-10 20:44:28 +02:00
Eric Meadows-Jönsson 33ab1679f3 Warn and reset mime on future files 2014-07-10 20:08:06 +02:00
Eric Meadows-Jönsson f2b12633f3 Only read mtime once per source file 2014-07-10 19:26:25 +02:00
José Valim 5a4b2769ee Clarify elixirc_options are overridden via CLI 2014-07-10 18:04:23 +02:00
José Valim 2439bae409 Use Keyword syntax instead of list check 2014-07-10 17:59:56 +02:00
José Valim 6ed7e40d03 Add OptionParser.split/1 2014-07-10 17:58:19 +02:00
José Valim 2fc76fe1b0 Don't keep all in the option parser config 2014-07-10 16:46:29 +02:00
Eric Meadows-Jönsson e541a56130 Update changelog 2014-07-10 15:56:37 +02:00
José Valim 6378750b12 Only accept --no-SWITCH for booleans
The previous behaviour was confusing and a special case.
This enables negated switches only for booleans, otherwise
it is considered as a regular switch (as the combination
of --no-SWITCH for any other type is an error anyway).
2014-07-10 14:05:49 +02:00
José Valim fc25f60718 Do not remove interpolation escape on uppercase sigils, closes #2512 2014-07-10 12:54:16 +02:00
José Valim 3b351e7650 ErlangError -> CompileError 2014-07-10 11:49:56 +02:00
José Valim 999a535603 Manually call check_command before eval, closes #2449 2014-07-10 11:44:03 +02:00
José Valim a726dd812b Improve cannot pipe error 2014-07-10 11:30:00 +02:00
Eric Meadows-Jönsson 58d54afac6 Merge pull request #2511 from ericmj/macro-tostring-sigils
Use sigil syntax in Macro.to_string
2014-07-10 00:32:58 +02:00
Eric Meadows-Jönsson 001d776176 Use sigil syntax in Macro.to_string 2014-07-09 19:34:07 +02:00
José Valim 4bcd25fb4d Require Record before in defrecord samples 2014-07-09 19:22:23 +02:00
José Valim f0fa9e66a6 Add performance note to String.slice/1 2014-07-09 18:47:25 +02:00
José Valim 12f9bf09b0 Mention app: false on no app error 2014-07-09 18:47:25 +02:00
José Valim bf8f5a995f Merge pull request #2508 from chyndman/mix-bat-short-names
Use short filenames to handle spaces in path to bin\mix
2014-07-09 11:06:37 +02:00
Chris Hyndman d3a3feace4 Use short filenames to handle spaces in path to bin\mix 2014-07-08 17:09:23 -07:00
Eric Meadows-Jönsson 30331b2bea Do not allow deps names on mix deps.get
Closes #2507.
2014-07-09 00:24:13 +02:00
José Valim f5bf9d9659 Do not call handle_file_warning directly 2014-07-08 19:03:22 +02:00
José Valim 2865bd8653 Make elixir_errors API more strict and consider :file in meta 2014-07-08 18:54:40 +02:00
José Valim 4513b6965f Emit :file attribute on all nodes instead of a select few 2014-07-08 15:53:32 +02:00
José Valim b47e8c61d7 Use line from :keep when there is a :file meta in definitions 2014-07-08 13:35:17 +02:00
José Valim 40bf079566 Update CHANGELOG 2014-07-08 12:06:43 +02:00
José Valim 2eaf854e79 Slightly improve IO docs 2014-07-08 11:41:25 +02:00
Joe-noh 7c12cf029c implement IO.(bin)read which accept :all
IO.(bin)read(dev, `:all`) reads all contents from device

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-07-08 11:39:18 +02:00
José Valim 597d90b326 Add comments to code:ensure_loaded/1 snippets 2014-07-08 11:28:24 +02:00
José Valim 5dcad26b79 Ensure we don't loop when printing inspect failures, closes #2503 2014-07-08 11:25:49 +02:00
José Valim f8c2d5cb4f Merge pull request #2502 from alco/escript-embed-elixir
Fix the usage of `embed_elixir` in escript.build
2014-07-08 10:46:31 +02:00
Alexei Sholik b0f432b2f8 Fix the usage of embed_elixir in escript.build 2014-07-08 01:27:43 +03:00
José Valim 651a4bad67 Clarify behaviour of split on Windows 2014-07-07 20:31:27 +02:00
José Valim f52e350adc Ensure deps are not recompiled every second time, closes #2471 2014-07-07 17:38:51 +02:00
José Valim 35e8019447 Improve mix docs 2014-07-07 17:29:02 +02:00
José Valim a6fab4a111 Use a native splitting on String.split/1 instead of relying on regexes
The new implementation is 20x faster for small strings and about
100x times faster for large ones.
2014-07-06 21:20:52 +02:00
José Valim 64d62d2472 Emit warnings for underscored variables appearing twice in a pattern, closes #2480 2014-07-06 20:18:00 +02:00
Eric Meadows-Jönsson 10b322aa48 Rename Mix.Shell.info option :escape => :ansi 2014-07-06 16:28:38 +02:00
Eric Meadows-Jönsson 981955301d Shell info messages can still be escaped 2014-07-06 02:39:16 +02:00
Eric Meadows-Jönsson ac425f04ef Ensure the message is not ANSI escaped
Closes #2496.
2014-07-06 02:20:40 +02:00
José Valim 1dda1563da Ensure docs with no function raise warnings 2014-07-05 22:25:15 +02:00
José Valim 0e71b1e711 Merge pull request #2495 from thmzlt/patch-1
Fix typo in GenEvent moduledoc
2014-07-05 22:11:31 +02:00
thmzlt 841f6f2387 Fix typo in GenEvent moduledoc 2014-07-05 17:08:49 -03:00
José Valim 2eb7fb97de Check if ANSI escape codes are enabled on IEx's clear, closes #2441 2014-07-05 15:16:00 +02:00
José Valim e781983e6b Fix regex compilation and set ucp flags 2014-07-05 15:01:25 +02:00
José Valim 7d17a12783 Add test cases for ExUnit.DocTest failures 2014-07-05 14:53:24 +02:00
jw2013 d888057689 throws error for missing docs in DocTest
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2014-07-05 14:49:10 +02:00
José Valim 76d6e05399 Ensure case wrapper in cond gets the proper line 2014-07-05 14:45:07 +02:00
José Valim 1bd0ca6dea Return explicit :ok from more functions 2014-07-05 13:43:52 +02:00
José Valim 8e1f7fddca Always return ok from tracking functions 2014-07-05 13:21:59 +02:00
José Valim abda47903b Merge pull request #2494 from jw2013/typo
fix typo in range moduledoc
2014-07-05 10:39:49 +02:00
jw2013 030e72c9c6 fix range typo 2014-07-04 22:07:08 -07:00
Eric Meadows-Jönsson 88dbad9650 Fix some sentence tenses in changelog 2014-07-05 03:19:47 +02:00
Eric Meadows-Jönsson 07263b8533 Merge pull request #2493 from jwarwick/changelog_typos
Fixed Changelog typos
2014-07-05 03:13:44 +02:00
John Warwick f67741b6dc Fixed Changelog typos 2014-07-04 20:51:13 -04:00
José Valim 4724ff12e2 Fix bug when flat_map is used inside another flat_map with enumerable, closes #2483 2014-07-04 21:51:57 +02:00
José Valim f7c7ff46b4 Store stacktrace before invoking callback function 2014-07-04 19:27:05 +02:00
José Valim c4868f8549 s/file/function in IO docs 2014-07-04 19:26:31 +02:00
José Valim 85951052ca Add a note for binary_part/3 on String.slice/3 2014-07-04 18:37:02 +02:00
José Valim 28bd15b76c Consider multiple @external_resource entries in mix 2014-07-04 18:30:55 +02:00
José Valim 1fdb715967 Add a note about IO.bin* 2014-07-04 18:30:54 +02:00
Eric Meadows-Jönsson 0279547232 Re-add deprecated local tasks 2014-07-04 15:46:23 +02:00
José Valim 7fabf32d9e Do not crash if applications are already started 2014-07-04 12:13:53 +02:00
José Valim 4cbf0eb2ac Merge pull request #2484 from fishcakez/dialyzer_fixes
Fix unmatched returns dialyzer warnings
2014-07-04 11:54:55 +02:00
José Valim 071b0830f8 Ensure line numbers are properly preserved in EEx, closes #2485 2014-07-04 11:54:07 +02:00
James Fish 39cd4a6594 Fix unmatched returns dialyzer warnings 2014-07-04 02:45:16 +01:00
José Valim 0d2332fe5e Update CHANGELOG 2014-07-03 21:03:17 +02:00
José Valim c807fa6ec5 Add Mix.compilers 2014-07-03 21:01:13 +02:00
José Valim 4a10806d9a Pass a list to :error_logger 2014-07-03 20:51:41 +02:00
José Valim 57031f8cb0 Improve typespecs for protocols once more 2014-07-03 20:04:06 +02:00
José Valim b292486ac9 Improve more typespecs 2014-07-03 19:14:06 +02:00
José Valim 595f9bb0b1 Improve poorly specified callbacks 2014-07-03 16:50:37 +02:00
José Valim ebe3397ad9 Do not include unreachable locals 2014-07-03 16:26:44 +02:00
José Valim bc12b76bcc Merge pull request #2481 from fishcakez/remote_call_ast
Fix remote call AST
2014-07-03 14:45:17 +02:00
José Valim d8df85e99c Update plt in .gitignore 2014-07-03 14:15:58 +02:00
José Valim 80b9879d0a Refactor how definitions are analyzed 2014-07-03 14:15:24 +02:00
James Fish d54f9ec931 Fix remote call AST 2014-07-03 11:59:25 +01:00
José Valim d2258acbb3 Ensure yanked locals are added back with all nodes, closes #2188 2014-07-03 11:43:42 +02:00
José Valim 6c3a4bd019 Fix the guard for this clause evaluates to false warning
Since the guard can be hidden inside if/unless, it is better
to have a more generic error, stating we will always get the
same result from an check/guard.
2014-07-03 00:58:02 +02:00
José Valim 7903211546 Merge pull request #2479 from jwarwick/process_typo
Fixed Process doc typos
2014-07-02 22:35:49 +02:00
John Warwick 6b87fcf903 Fixed Process doc typos 2014-07-02 16:33:57 -04:00
José Valim 07d0d1b275 Improve some typespecs 2014-07-02 22:32:16 +02:00
José Valim 95361172a0 Update CHANGELOG 2014-07-02 22:05:41 +02:00
José Valim a0f3086167 Convert existing bitstring examples to new syntax 2014-07-02 21:32:00 +02:00
José Valim 65a59b41e5 New bitstring syntax, closes #1950 2014-07-02 21:19:01 +02:00
José Valim 78dc02fb81 Merge pull request #2477 from fishcakez/dialyze
Fix dialyzer makefile commands
2014-07-02 17:37:17 +02:00
José Valim dc577b8bfa Ensure mix preserves -pa and -pz given to the command line 2014-07-02 17:30:20 +02:00
José Valim ed9862ea91 First deprecate types in struct fields 2014-07-02 17:30:20 +02:00
James Fish f710c61179 Fix dialyzer makefile commands 2014-07-02 15:55:02 +01:00
Eric Meadows-Jönsson d86cf14f0d Add shorthand syntax for nonempty_list() 2014-07-02 15:48:03 +02:00
Eric Meadows-Jönsson bdfd5817dc Fix typespec_to_ast for lists 2014-07-02 02:44:47 +02:00
José Valim 3b2ece4ddc Merge pull request #2474 from chyndman/elixir-bat-short-circuit
Fix for batch file misbehaving with parenthetical arguments
2014-07-02 00:12:56 +02:00
José Valim 3452f364aa Improve typespecs and docs 2014-07-02 00:12:06 +02:00
José Valim dad77a4d4b Mention MIX_ENV on compile.protocols 2014-07-02 00:12:06 +02:00
Eric Meadows-Jönsson c8b925c581 Merge pull request #2475 from alco/version-doc-fix
Fix weird examples in Version docs
2014-07-02 00:02:43 +02:00
Alexei Sholik 24297c87e6 Fix weird examples in Version docs 2014-07-02 00:26:54 +03:00
Chris Hyndman 0c589eff04 Umanbiguous check for parameters 2014-07-01 13:51:25 -07:00
Chris Hyndman 3a3684ca78 Process Erlang commands last 2014-07-01 13:44:23 -07:00
José Valim 6e39366c40 Merge pull request #2473 from jwarwick/changelog_typo
Fixed CHANGELOG typo
2014-07-01 20:45:46 +02:00
John Warwick fd0ccb7741 Fixed CHANGELOG typo 2014-07-01 14:09:09 -04:00
José Valim 81f151245e Merge pull request #2472 from michaelkschmidt/master
Use absolute symbolic links on windows
2014-07-01 19:06:30 +02:00
Michael Schmidt acfdd5b59e Use absolute symbolic links on windows 2014-07-01 11:17:30 -05:00
Eric Meadows-Jönsson 588800c495 Merge pull request #2467 from ericmj/multiclause-defaule
Raise on multiple clauses with default arg
2014-07-01 16:51:12 +02:00
Eric Meadows-Jönsson cfd157ab7d Merge pull request #2466 from ericmj/typespec-struct
Remove default generation of `@type t` for structs
2014-07-01 16:51:01 +02:00
Eric Meadows-Jönsson 377eb6126d Raise on multiple clauses with default arg 2014-07-01 16:41:19 +02:00
Eric Meadows-Jönsson 2cad4c96bd Remove default generation of @type t for structs
`Struct{}` in typespecs will generate default types for all missing
fields.
2014-07-01 16:23:30 +02:00
José Valim 2453501c83 Rename escriptize to escript.build 2014-07-01 15:23:24 +02:00
José Valim dcc3c6290e Deprecate embed_extra_apps in escriptize 2014-07-01 15:17:59 +02:00
José Valim 54240e1b4f Inline functions in Port module 2014-07-01 14:54:56 +02:00
José Valim 6639f91ba2 No longer fill in missing intermediate values with empty maps in Access 2014-07-01 13:46:49 +02:00
José Valim 15bf309760 Improve error message for struct expansion in invalid contexts 2014-07-01 12:41:27 +02:00
José Valim 1de0302ecb Fix failing test and improve zip/unzip docs 2014-07-01 12:39:06 +02:00
José Valim 871cbda3f8 Allow functions in get_in/2 and get_and_update_in/3 (#2470) 2014-07-01 12:25:07 +02:00
José Valim c4ea85d7e5 Bug fixes on windows for buggy 17.1 2014-07-01 10:16:39 +02:00
José Valim a8f3125774 Deprecate lists in find_executable/1 2014-06-30 21:43:26 +02:00
José Valim c564af180b Update CHANGELOG 2014-06-30 21:35:54 +02:00
José Valim 3ebe1ed728 Use binaries so we can use Elixir functions 2014-06-30 21:30:01 +02:00
José Valim 6b030b2978 Use the new System.cmd/3 in stdlib and apps 2014-06-30 21:15:00 +02:00
José Valim 9f2fa0f7af Add System.cmd/3, closes #2251 2014-06-30 20:50:44 +02:00
José Valim da06e35443 Remove wrong usage of exit(0|1) 2014-06-30 20:35:06 +02:00
José Valim da4d047a37 Use iolists in collectable for binaries 2014-06-30 20:33:38 +02:00
José Valim 782314abff Also use exit({:shutdown, 1}) on mix test 2014-06-30 16:59:11 +02:00
José Valim 94d53f052b Use exit({:shutdown, int}) to signal shell status 2014-06-30 16:38:26 +02:00
José Valim 9ceebf96db Move local.install and local.uninstall to archive.* 2014-06-30 12:28:51 +02:00
José Valim 8611bb7322 Run command line and escripts in a new process without trap exit
Closes #1985
2014-06-30 11:48:31 +02:00
José Valim ba2e1c95ef Remove duplicated specs and old PR example 2014-06-30 10:17:31 +02:00
José Valim 5979b5ca75 Merge pull request #2464 from KronicDeth/feature/2460/enum-sort-by
Enum.sort_by/3
2014-06-30 10:09:45 +02:00
Luke Imhoff f144cae300 Fix phrasing of Enum.sort_by/3 @doc 2014-06-29 16:09:52 -05:00
Luke Imhoff 8bcf473b68 Pull reused test data into variable 2014-06-29 16:03:42 -05:00
Luke Imhoff b10fdfc7b8 Fix spacing 2014-06-29 16:03:09 -05:00
Luke Imhoff 95eb1c0978 Fix @spec sort_by
Use format provided by José Valim that uses `when` to eliminate the need
for `@type mapped_element`.
2014-06-29 15:30:34 -05:00
Luke Imhoff 7a5cb8d374 Add @spec for Enum.sort_by/3 2014-06-28 20:38:01 -05:00
Luke Imhoff f23b133d29 Add @doc for Enum.sort_by/3 2014-06-28 20:23:32 -05:00
Luke Imhoff 0bdee49f7f Enum.sort_by/3
Enum.sort_by/3 takes a collection, a mapper function, an an optional
sorter function.  The function sorts by the mapped value using the sorter.
The optional sorter default to `<=/2`.
2014-06-28 17:52:04 -05:00
164 changed files with 3932 additions and 2135 deletions
+1 -2
View File
@@ -10,5 +10,4 @@
/lib/elixir/test/ebin
/rel/elixir
erl_crash.dump
.dialyzer_plt
.dialyzer.base_plt
.elixir.plt
+48 -5
View File
@@ -1,18 +1,61 @@
# Changelog
## v0.14.3-dev
## v0.14.3 (2014-07-12)
* Enhancements
* [Access] Allow function access on `get_in/2` and `get_and_update_in/3`
* [Enum] Add `Enum.sort_by/3`
* [ExUnit] Match the line filter by proximity instead of exact match
* [Float] Support precision in `Float.ceil/1` and `Float.floor/1`
* [IO] Add `IO.(bin)read(device, :all)`
* [Kernel] Print a warning if a dangling `@doc` clause is found
* [Mix] Use absolute symbolic links on Windows for `_build` instead of copying
* [Mix] Add `Mix.compilers` that returns all default compilers used by mix tasks
* [Mix] Issue warning and reset mtime for source files from the future
* [Mix] Support task aliases in Mix
* [OptionParser] Add `OptionParser.split/1` that splits a string into argv
* [Record] Allow a record to be converted to a keyword list with `record(some_record)`
* [String] Improve performance of `String.split/1`
* [Typespec] Allow `%Struct{}` syntax to be used in typespecs
* [Typespec] Allow `record(:record, fields)` syntax to be used in typespecs
* Bug fixes
* [Kernel] `|>`, `<<<`, `>>>` and `^^^` were made left associative in operator table
* [Kernel] `<`, `>`, `<=`, `>=` were given higher precedence than comparison ones (`==`, `!=`, etc) in operator table
* [IEx] Do not print ANSI sequences on `IEx.Helpers.clear/0` if ANSI sequences are not supported
* [Inspect] Ensure `Inspect.Algebra.to_doc/2` doesn't go into a loop when there is a failure printing a struct
* [Kernel] `|>`, `<<<`, `>>>` and `^^^` made left associative in operator table
* [Kernel] `<`, `>`, `<=`, `>=` given higher precedence than comparison operators (`==`, `!=`, etc) in the operator table
* [Kernel] Run command line and escripts in a process that does not trap exits
* [Kernel] Fix a bug where Mix paths had higher priority than CLI ones, causing protocol consolidations to not be properly loaded
* [Kernel] Fix wording on error messages when a check/guard always passes or always fails
* [Kernel] Fix a bug where an unused function warning was printed even when the function was used via `defoverridable`
* [Kernel] Improve typespecs so they don't generate supertype dialyzer warnings
* [Macro] `to_string` correctly displays sigils
* [Mix] Ensure Mix dependencies are not compiled every second time when `mix deps.compile` is invoked
* [Mix] Fix a bug where `Mix.shell.error/1` and friends choked when printing a map
* [Mix] Ensure multiple `@external_resource` entries are read by Mix compilers
* [Mix] Fix a bug where tasks for umbrella projects were not properly reenabled
* [Stream] Fix bug when `flat_map` is used inside another `flat_map` with an Enumerable
* [Typespec] Fix a bug where the `list` typespec was incorrectly rendered as `[]`
* Soft deprecations (no warnings emitted)
* [Kernel] Using a list for bitstring modifiers is deprecated (as in `<<x :: [little, utf16]>>`), please use `-` as the separator instead (as in `<<x :: little-utf16>>`)
* [System] `System.cmd/1` is deprecated in favor of `System.cmd/3`
* Deprecations
* [Mix] `mix escriptize` is deprecated in favor of `escript.build`
* [Mix] `mix local.install` and `mix local.uninstall` have been deprecated in favor of `mix archive.install` and `mix archive.uninstall` respectively
* [Mix] `:embed_extra_apps` for escripts is deprecated, instead list the dependencies inside `def application`
* [System] Giving a char list to `System.find_executable/1` is deprecated
* Backwards incompatible changes
* [Access] No longer fill in missing intermediate values with empty maps
* [Float] `Float.ceil/2` and `Float.floor/2` now always returns floats and no longer accept integers
* [Kernel] `defstruct` no longer automatically defines a type
* [Kernel] `exit(integer)` is no longer supported from scripts to configure the exit signal. Use `exit({:shutdown, integer})` instead
* [Kernel] Default argument values have to be defined in a function head if the function has multiple clauses
* [Mix] `mix archive.build` replaces the functionality of `mix archive`, `mix archive` now lists locally installed archives
* [Mix] `Mix.shell.info/1` no longer automatically escape ANSI sequences. Instead if has to be explicitly enabled with the `ansi: true` option
* [OptionParser] `--no-SWITCH` are only allowed for declared booleans switches
## v0.14.2 (2014-06-29)
@@ -28,7 +71,7 @@
* Bug fixes
* [CLI] Support paths inside archives in `-pa` and `-pz` options
* [IEx] Remove delay when printing data from the an application start callback
* [IEx] Remove delay when printing data from the application start callback
* [IEx] Ensure we show a consistent error when we cannot evaluate `.iex.exs`
* [Kernel] Ensure derived protocols are defined with a file
* [Kernel] Change precedence of `&` to not special case `/`
@@ -70,7 +113,7 @@
* [URI] `encode/1` does not escape reserved/unreserved characters by default nor encodes whitespace as `+` (check `URI.encode_www_form/1` and `URI.decode_www_form/1` for previous behaviour)
* Deprecations
* [Mix] `:escript_*` options were moved into a single `:escript` group
* [Mix] `:escript_*` options moved into a single `:escript` group
* Backwards incompatible changes
* [GenEvent] `GenEvent.stream/2` defaults to `:sync` mode
-1
View File
@@ -279,7 +279,6 @@ We have saved some excellent pull requests we have received in the past in case
you are looking for some examples:
* https://github.com/elixir-lang/elixir/pull/992
* https://github.com/elixir-lang/elixir/pull/1041
* https://github.com/elixir-lang/elixir/pull/1058
* https://github.com/elixir-lang/elixir/pull/1059
+17 -11
View File
@@ -11,7 +11,7 @@ INSTALL_DIR = $(INSTALL) -m755 -d
INSTALL_DATA = $(INSTALL) -m644
INSTALL_PROGRAM = $(INSTALL) -m755
.PHONY: install compile erlang elixir dialyze test clean docs release_docs release_zip check_erlang_release
.PHONY: install compile erlang elixir build_plt clean_plt dialyze test clean docs release_docs release_zip check_erlang_release
.NOTPARALLEL: compile
#==> Functions
@@ -32,7 +32,7 @@ $(1): lib/$(1)/ebin/Elixir.$(2).beam lib/$(1)/ebin/$(1).app
lib/$(1)/ebin/$(1).app: lib/$(1)/mix.exs
$(Q) mkdir -p lib/$(1)/_build/shared/lib/$(1)
$(Q) cp -R lib/$(1)/ebin lib/$(1)/_build/shared/lib/$(1)/
$(Q) cd lib/$(1) && ../../bin/elixir -e "Mix.start(:permanent, [])" -r mix.exs -e "Mix.Task.run('compile.app')"
$(Q) cd lib/$(1) && ../../bin/elixir -e 'Mix.start(:permanent, [])' -r mix.exs -e 'Mix.Task.run("compile.app")'
$(Q) cp lib/$(1)/_build/shared/lib/$(1)/ebin/$(1).app lib/$(1)/ebin/$(1).app
$(Q) rm -rf lib/$(1)/_build
@@ -175,14 +175,20 @@ test_stdlib: compile
$(Q) exec epmd & exit
$(Q) cd lib/elixir && ../../bin/elixir -r "test/elixir/test_helper.exs" -pr "test/elixir/**/*_test.exs";
.dialyzer.base_plt:
@ echo "==> Adding Erlang/OTP basic applications to a new base PLT"
$(Q) dialyzer --output_plt .dialyzer.base_plt --build_plt --apps erts kernel stdlib compiler tools syntax_tools parsetools
#==> Dialyzer tasks
dialyze: .dialyzer.base_plt
$(Q) rm -f .dialyzer_plt
$(Q) cp .dialyzer.base_plt .dialyzer_plt
@ echo "==> Adding Elixir to PLT..."
$(Q) dialyzer --plt .dialyzer_plt --add_to_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin
DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wrace_conditions -Wunderspecs
PLT = .elixir.plt
$(PLT):
@ echo "==> Building PLT with Elixir's dependencies..."
$(Q) dialyzer --output_plt $(PLT) --build_plt --apps erts kernel stdlib compiler syntax_tools parsetools tools ssl inets
clean_plt:
$(Q) rm -f $(PLT)
build_plt: clean_plt $(PLT)
dialyze: compile $(PLT)
@ echo "==> Dialyzing Elixir..."
$(Q) dialyzer --plt .dialyzer_plt -r lib/elixir/ebin lib/ex_unit/ebin lib/eex/ebin lib/iex/ebin lib/mix/ebin
$(Q) dialyzer --plt $(PLT) $(DIALYZER_OPTS) lib/*/ebin
+1 -1
View File
@@ -1 +1 @@
0.14.3-dev
0.14.3
+14 -14
View File
@@ -60,22 +60,22 @@ if "%par%"=="""" (
)
rem ******* EXECUTION OPTIONS **********************
IF "%par%"==""+iex"" (Set useWerl=1)
rem ******* ERLANG PARAMETERS **********************
IF NOT "%par%"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
IF NOT "%par%"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
IF NOT "%par%"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 && shift)
IF NOT "%par%"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
IF NOT "%par%"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
IF NOT "%par%"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
rem ******* elixir parameters **********************
rem Note: we don't have to do anything with options that don't take an argument
IF NOT "%par%"=="%par:-e=%" (shift)
IF NOT "%par%"=="%par:-r=%" (shift)
IF NOT "%par%"=="%par:-pr=%" (shift)
IF NOT "%par%"=="%par:-pa=%" (shift)
IF NOT "%par%"=="%par:-pz=%" (shift)
IF NOT "%par%"=="%par:--app=%" (shift)
IF NOT "%par%"=="%par:--remsh=%" (shift)
IF """"=="%par:-e=%" (shift)
IF """"=="%par:-r=%" (shift)
IF """"=="%par:-pr=%" (shift)
IF """"=="%par:-pa=%" (shift)
IF """"=="%par:-pz=%" (shift)
IF """"=="%par:--app=%" (shift)
IF """"=="%par:--remsh=%" (shift)
rem ******* ERLANG PARAMETERS **********************
IF """"=="%par:--detached=%" (Set parsErlang=%parsErlang% -detached)
IF """"=="%par:--hidden=%" (Set parsErlang=%parsErlang% -hidden)
IF """"=="%par:--cookie=%" (Set parsErlang=%parsErlang% -setcookie %1 && shift)
IF """"=="%par:--sname=%" (Set parsErlang=%parsErlang% -sname %1 && shift)
IF """"=="%par:--name=%" (Set parsErlang=%parsErlang% -name %1 && shift)
IF """"=="%par:--erl=%" (Set beforeExtra=%beforeExtra% %~1 && shift)
goto:startloop
rem ******* assume all pre-params are parsed ********************
+1 -1
View File
@@ -1,2 +1,2 @@
@echo off
call "%~dp0\elixir.bat" "%~dp0\mix" %*
call "%~dp0\elixir.bat" "%~dps0\mix" %*
+6 -2
View File
@@ -64,7 +64,8 @@ defmodule EEx.Engine do
end
"""
def handle_assign({:@, line, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
def handle_assign({:@, meta, [{name, _, atom}]}) when is_atom(name) and is_atom(atom) do
line = meta[:line] || 0
quote line: line, do: Dict.get(var!(assigns), unquote(name))
end
@@ -98,7 +99,10 @@ defmodule EEx.Engine do
def handle_expr(buffer, "=", expr) do
quote do
tmp = unquote(buffer)
tmp <> to_string(unquote(expr))
tmp <> (case unquote(expr) do
bin when is_binary(bin) -> bin
oth -> String.Chars.to_string(oth)
end)
end
end
+10
View File
@@ -19,6 +19,16 @@ defmodule EEx.SmartEngineTest do
assert_eval "1\n2\n3\n", "<%= for x <- [1, 2, 3] do %><%= x %>\n<% end %>"
end
test "compiled preserved line numbers" do
result = EEx.compile_string("<%= @hello %>", engine: EEx.SmartEngine)
Macro.prewalk(result, fn
{_left, meta, _right} ->
assert Keyword.get(meta, :line, 0) in [0, 1]
_ ->
:ok
end)
end
defp assert_eval(expected, actual, binding \\ []) do
result = EEx.eval_string(actual, binding, file: __ENV__.file)
assert result == expected
+1 -1
View File
@@ -21,7 +21,7 @@
-record(elixir_quote, {
line=false,
keep=false,
file=nil,
context=nil,
vars_hygiene=true,
aliases_hygiene=true,
+4 -3
View File
@@ -119,7 +119,7 @@ defimpl Access, for: Map do
def get_and_update!(other, key, _fun) do
raise ArgumentError,
"could not update key #{inspect key}. Expected map/struct, got: #{inspect other}"
"could not put/update key #{inspect key}. Expected map/struct, got: #{inspect other}"
end
end
@@ -132,8 +132,9 @@ defimpl Access, for: Atom do
undefined(atom)
end
def get_and_update(nil, _, fun) do
fun.(nil)
def get_and_update(nil, key, _fun) do
raise ArgumentError,
"could not put/update key #{inspect key} on a nil value"
end
def get_and_update(atom, _key, _fun) do
+17
View File
@@ -51,6 +51,23 @@ defmodule Code do
:code.del_path(to_char_list(Path.expand path))
end
@doc """
Re-add paths given to the command line to keep their position
on the overall code path.
Some tools may change the code path by prepending new items but
still want the paths given by the user to have higher priority.
Calling this function guarantees the paths are re-added on
top of the user given ones.
"""
@spec readd_paths() :: :ok
def readd_paths() do
{pa, pz} = :elixir_code_server.call(:paths)
:code.add_pathsa(pa)
:code.add_pathsz(pz)
:ok
end
@doc """
Evaluate the contents given by `string`.
+2 -2
View File
@@ -85,8 +85,8 @@ defimpl Collectable, for: BitString do
def into(original) do
{original, fn
bitstring, {:cont, x} -> <<bitstring :: bits, x :: bits>>
bitstring, :done -> bitstring
acc, {:cont, x} when is_bitstring(x) -> [acc|x]
acc, :done -> IO.iodata_to_binary(acc)
_, :halt -> :ok
end}
end
+40 -6
View File
@@ -287,7 +287,6 @@ defmodule Enum do
:none
"""
@spec at(t, integer) :: element | nil
@spec at(t, integer, default) :: element | default
def at(collection, n, default \\ nil) do
case fetch(collection, n) do
@@ -330,7 +329,6 @@ defmodule Enum do
[[1, 2, 3], [4, 5, 6]]
"""
@spec chunk(t, non_neg_integer, non_neg_integer) :: [list]
@spec chunk(t, non_neg_integer, non_neg_integer, t | nil) :: [list]
def chunk(coll, n, step, pad \\ nil) when n > 0 and step > 0 do
limit = :erlang.max(n, step)
@@ -689,7 +687,6 @@ defmodule Enum do
3
"""
@spec find(t, (element -> any)) :: element | nil
@spec find(t, default, (element -> any)) :: element | default
def find(collection, ifnone \\ nil, fun)
@@ -716,7 +713,6 @@ defmodule Enum do
true
"""
@spec find_value(t, (element -> any)) :: any | :nil
@spec find_value(t, any, (element -> any)) :: any | :nil
def find_value(collection, ifnone \\ nil, fun)
@@ -931,7 +927,6 @@ defmodule Enum do
"1 = 2 = 3"
"""
@spec join(t) :: String.t
@spec join(t, String.t) :: String.t
def join(collection, joiner \\ "")
@@ -990,7 +985,6 @@ defmodule Enum do
"2 = 4 = 6"
"""
@spec map_join(t, (element -> any)) :: String.t
@spec map_join(t, String.t, (element -> any)) :: String.t
def map_join(collection, joiner \\ "", mapper)
@@ -1568,6 +1562,46 @@ defmodule Enum do
reduce(collection, [], &sort_reducer(&1, &2, fun)) |> sort_terminator(fun)
end
@doc """
Sorts the mapped results of the `collection` according to the `sorter` function.
This function maps each element of the collection using the `mapper`
function. The collection is then sorted by the mapped elements using the
`sorter` function, which defaults to `<=/2`
`sort_by/3` differs from `sort/2` in that it only calculates the comparison
value for each element in the collection once instead of once for each
element in each comparison. If the same function is being called on both
element, it's also more compact to use `sort_by/3`.
This technique is also known as a
[Schwartzian Transform](https://en.wikipedia.org/wiki/Schwartzian_transform),
or the Lisp decorate-sort-undecorate idiom as the `mapper` is decorating the
original `collection`, then `sorter` is sorting the decorations, and finally
the `collection` is being undecorated so only the original elements remain,
but now in sorted order.
## Examples
Using the default `sorter` of `<=/2`:
iex> Enum.sort_by ["some", "kind", "of", "monster"], &byte_size/1
["of", "some", "kind", "monster"]
Using a custom `sorter` to override the order:
iex> Enum.sort_by ["some", "kind", "of", "monster"], &byte_size/1, &>=/2
["monster", "some", "kind", "of"]
"""
@spec sort_by(t, (element -> mapped_element), (mapped_element, mapped_element -> boolean)) :: list when mapped_element: element
def sort_by(collection, mapper, sorter \\ &<=/2) do
collection
|> map(&{&1, mapper.(&1)})
|> sort(&sorter.(elem(&1, 1), elem(&2, 1)))
|> map(&elem(&1, 0))
end
@doc """
Splits the enumerable into two collections, leaving `count`
elements in the first one. If `count` is a negative number,
+3 -1
View File
@@ -14,7 +14,7 @@ defmodule Exception do
format exceptions as in the current Elixir version being used.
"""
@typedoc "The exception type (as generated by defexception)"
@typedoc "The exception type"
@type t :: %{__struct__: module, __exception__: true}
@typedoc "The kind handled by formatting functions"
@@ -482,6 +482,7 @@ end
defmodule RuntimeError do
defexception message: "runtime error"
@spec exception(String.t) :: Exception.t
def exception(msg) when is_binary(msg) do
%RuntimeError{message: msg}
end
@@ -494,6 +495,7 @@ end
defmodule ArgumentError do
defexception message: "argument error"
@spec exception(String.t) :: Exception.t
def exception(msg) when is_binary(msg) do
%ArgumentError{message: msg}
end
+21 -9
View File
@@ -308,11 +308,15 @@ defmodule File do
def touch(path, time \\ :calendar.local_time) do
path = IO.chardata_to_string(path)
case F.change_time(path, time) do
{:error, :enoent} ->
write(path, "")
F.change_time(path, time)
other ->
other
{:error, :enoent} -> touch_new(path, time)
other -> other
end
end
defp touch_new(path, time) do
case write(path, "", [:append]) do
:ok -> F.change_time(path, time)
{:error, _reason} = error -> error
end
end
@@ -548,7 +552,8 @@ defmodule File do
[dest|acc]
{:error, :eexist} ->
if callback.(src, dest) do
rm(dest)
# If rm/1 fails, copy/2 will fail
_ = rm(dest)
case copy(src, dest) do
{:ok, _} ->
copy_file_mode!(src, dest)
@@ -569,7 +574,8 @@ defmodule File do
[dest|acc]
{:error, :eexist} ->
if callback.(src, dest) do
rm(dest)
# If rm/1 fails, iF.make_symlink/2 will fail
_ = rm(dest)
case F.make_symlink(link, dest) do
:ok -> [dest|acc]
{:error, reason} -> {:error, reason, src}
@@ -665,14 +671,20 @@ defmodule File do
if match? {:win32, _}, :os.type do
case F.read_file_info(IO.chardata_to_string(path)) do
{:ok, file_info} when elem(file_info, 3) in [:read, :none] ->
File.chmod(path, (elem(file_info, 7) + 0200))
F.delete(path)
change_mode_windows(path, file_info)
_ ->
nil
end
end
end
defp change_mode_windows(path, file_info) do
case File.chmod(path, (elem(file_info, 7) + 0200)) do
:ok -> F.delete(path)
{:error, _reason} = error -> error
end
end
@doc """
Same as `rm/1`, but raises an exception in case of failure. Otherwise `:ok`.
"""
+1
View File
@@ -57,6 +57,7 @@ defmodule File.Stat do
pairs = :lists.zip(keys, vals)
defstruct keys
@type t :: %__MODULE__{}
@doc """
Converts a `File.Stat` struct to a `:file_info` record.
+8 -2
View File
@@ -13,6 +13,8 @@ defmodule File.Stream do
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true
@type t :: %__MODULE__{}
@doc false
def __build__(path, modes, line_or_bytes) do
raw = :lists.keyfind(:encoding, 1, modes) == false
@@ -55,10 +57,14 @@ defmodule File.Stream do
false -> IO.write(device, x)
end
:ok, :done ->
:file.close(device)
# If delayed_write option is used and the last write failed will
# MatchError here as {:error, _} is returned.
:ok = :file.close(device)
stream
:ok, :halt ->
:file.close(device)
# If delayed_write option is used and the last write failed will
# MatchError here as {:error, _} is returned.
:ok = :file.close(device)
end
end
end
+51 -29
View File
@@ -1,3 +1,5 @@
import Kernel, except: [round: 1]
defmodule Float do
@moduledoc """
Functions for working with floating point numbers.
@@ -99,57 +101,71 @@ defmodule Float do
@doc """
Rounds a float to the largest integer less than or equal to `num`.
Floor also accepts a precision to round a floating point value down
to an arbitrary number of fractional digits (between 0 and 15).
This function always returns floats. One may use `Kernel.trunc/1` to
truncate the result to an integer afterwards.
## Examples
iex> Float.floor(34)
34
iex> Float.floor(34.25)
34
34.0
iex> Float.floor(-56.5)
-57
-57.0
iex> Float.floor(34.253, 2)
34.25
"""
@spec floor(float | integer) :: integer
def floor(num) when is_integer(num), do: num
def floor(num) when is_float(num) do
truncated = :erlang.trunc(num)
case :erlang.abs(num - truncated) do
x when x > 0 and num < 0 -> truncated - 1
_ -> truncated
end
@spec floor(float, 0..15) :: float
def floor(number, precision \\ 0) when is_float(number) and precision in 0..15 do
power = power_of_10(precision)
number = number * power
truncated = trunc(number)
variance = if number - truncated < 0, do: -1.0, else: 0.0
(truncated + variance) / power
end
@doc """
Rounds a float to the largest integer greater than or equal to `num`.
Ceil also accepts a precision to round a floating point value down to
an arbitrary number of fractional digits (between 0 and 15).
This function always returns floats. One may use `Kernel.trunc/1` to
truncate the result to an integer afterwards.
## Examples
iex> Float.ceil(34)
34
iex> Float.ceil(34.25)
35
35.0
iex> Float.ceil(-56.5)
-56
-56.0
iex> Float.ceil(34.253, 2)
34.26
"""
@spec ceil(float | integer) :: integer
def ceil(num) when is_integer(num), do: num
def ceil(num) when is_float(num) do
truncated = :erlang.trunc(num)
case :erlang.abs(num - truncated) do
x when x > 0 and num > 0 -> truncated + 1
_ -> truncated
end
@spec ceil(float, 0..15) :: float
def ceil(number, precision \\ 0) when is_float(number) and precision in 0..15 do
power = power_of_10(precision)
number = number * power
truncated = trunc(number)
variance = if number - truncated > 0, do: 1.0, else: 0.0
(truncated + variance) / power
end
@doc """
Rounds a floating point value to an arbitrary number of fractional digits
(between 0 and 15).
This function only accepts floats and returns floats. Use `Kernel.round/1`
if you want a function that accepts both floats and integers and always
returns an integer.
## Examples
iex> Float.round(5.5674, 3)
@@ -165,9 +181,15 @@ defmodule Float do
-5.568
"""
@spec round(float, integer) :: float
def round(number, precision) when is_float(number) and is_integer(precision) and precision in 0..15 do
Kernel.round(number * :math.pow(10, precision)) / :math.pow(10, precision)
@spec round(float, 0..15) :: float
def round(number, precision \\ 0) when is_float(number) and precision in 0..15 do
power = power_of_10(precision)
Kernel.round(number * power) / power
end
Enum.reduce 0..15, 1, fn x, acc ->
defp power_of_10(unquote(x)), do: unquote(acc)
acc * 10
end
@doc """
+12 -2
View File
@@ -13,7 +13,7 @@ defmodule GenEvent do
## Example
There are many use cases for event handlers. For example, a logging
system can be built using event handlers where which log message is
system can be built using event handlers where each log message is
an event and different event handlers can be plugged to handle the
log messages. One handler may print error messages on the terminal,
another can write it to a file, while a third one can keep the
@@ -164,6 +164,16 @@ defmodule GenEvent do
@typedoc "Supported values for new handlers"
@type handler :: module | {module, term}
@typedoc "The subscription mode"
@type mode :: :sync | :async
@type t :: %__MODULE__{
manager: manager,
id: term,
timeout: timeout,
duration: timeout,
mode: mode}
@doc """
Defines a `GenEvent` stream.
@@ -387,7 +397,7 @@ defmodule GenEvent do
def cancel_streams(%GenEvent{manager: manager, id: id}) do
handlers = :gen_event.which_handlers(manager)
for {Enumerable.GenEvent, {handler_id, _}} = ref <- handlers,
_ = for {Enumerable.GenEvent, {handler_id, _}} = ref <- handlers,
handler_id === id do
:gen_event.delete_handler(manager, ref, :remove_handler)
end
+1 -1
View File
@@ -21,7 +21,7 @@ defmodule HashDict do
@node_size 8
@node_template :erlang.make_tuple(@node_size, [])
@opaque t :: map
@opaque t :: %__MODULE__{size: non_neg_integer, root: term}
@doc false
defstruct size: 0, root: @node_template
+1 -1
View File
@@ -21,7 +21,7 @@ defmodule HashSet do
@node_size 8
@node_template :erlang.make_tuple(@node_size, [])
@opaque t :: map
@opaque t :: %__MODULE__{size: non_neg_integer, root: term}
@doc false
defstruct size: 0, root: @node_template
+33 -10
View File
@@ -32,12 +32,20 @@ defmodule Inspect.Opts do
* `:width` - defaults to the 80 characters.
"""
defstruct structs: true :: boolean,
binaries: :infer :: :infer | :as_binaries | :as_strings,
char_lists: :infer :: :infer | :as_lists | :as_char_lists,
limit: 50 :: pos_integer,
width: 80 :: pos_integer | :infinity,
pretty: false :: boolean
defstruct structs: true,
binaries: :infer,
char_lists: :infer,
limit: 50,
width: 80,
pretty: false
@type t :: %__MODULE__{
structs: boolean,
binaries: :infer | :as_binaries | :as_strings,
char_lists: :infer | :as_lists | :as_char_lists,
limit: pos_integer,
width: pos_integer | :infinity,
pretty: boolean}
end
defmodule Inspect.Algebra do
@@ -170,10 +178,25 @@ defmodule Inspect.Algebra do
Inspect.inspect(map, opts)
rescue
e ->
res = Inspect.Map.inspect(map, opts)
raise ArgumentError,
"Got #{inspect e.__struct__} with message " <>
"\"#{Exception.message(e)}\" while inspecting #{pretty(res, opts.width)}"
# Because we try to raise a nice error message in case
# we can't inspect a struct, there is a chance the error
# message itself relies on the struct being printed, so
# we need to trap the inspected messages to guarantee
# we won't try to render any failed instruct when building
# the error message.
if Process.get(:inspect_trap) do
Inspect.Map.inspect(map, opts)
else
try do
Process.put(:inspect_trap, true)
res = Inspect.Map.inspect(map, opts)
raise ArgumentError,
"Got #{inspect e.__struct__} with message " <>
"\"#{Exception.message(e)}\" while inspecting #{pretty(res, opts.width)}"
after
Process.delete(:inspect_trap)
end
end
end
else
Inspect.Map.inspect(map, opts)
+2 -2
View File
@@ -115,7 +115,7 @@ defmodule Integer do
'7'
"""
@spec to_char_list(integer) :: list
@spec to_char_list(integer) :: char_list
def to_char_list(number) do
:erlang.integer_to_list(number)
end
@@ -132,7 +132,7 @@ defmodule Integer do
'3FF'
"""
@spec to_char_list(integer, pos_integer) :: list
@spec to_char_list(integer, pos_integer) :: char_list
def to_char_list(number, base) do
:erlang.integer_to_list(number, base)
end
+49 -6
View File
@@ -43,8 +43,10 @@ defmodule IO do
end
@doc """
Reads `count` characters from the IO device or until
the end of the line if `:line` is given. It returns:
Reads `count` characters from the IO device, a whole
`:line` or the whole device with `:all`.
It returns:
* `data` - the input characters
@@ -53,10 +55,17 @@ defmodule IO do
* `{:error, reason}` - other (rare) error condition;
for instance, `{:error, :estale}` if reading from an
NFS volume
If `:all` is given, `:eof` is never returned, but an
empty string in case the device has reached EOF.
"""
@spec read(device, :line | non_neg_integer) :: chardata | nodata
@spec read(device, :all | :line | non_neg_integer) :: chardata | nodata
def read(device \\ group_leader, chars_or_line)
def read(device, :all) do
do_read_all(map_dev(device), "")
end
def read(device, :line) do
:io.get_line(map_dev(device), '')
end
@@ -65,9 +74,19 @@ defmodule IO do
:io.get_chars(map_dev(device), '', count)
end
defp do_read_all(mapped_dev, acc) do
case :io.get_line(mapped_dev, "") do
line when is_binary(line) -> do_read_all(mapped_dev, acc <> line)
:eof -> acc
other -> other
end
end
@doc """
Reads `count` bytes from the IO device or until
the end of the line if `:line` is given. It returns:
Reads `count` characters from the IO device, a whole
`:line` or the whole device with `:all`.
It returns:
* `data` - the input characters
@@ -76,10 +95,20 @@ defmodule IO do
* `{:error, reason}` - other (rare) error condition;
for instance, `{:error, :estale}` if reading from an
NFS volume
If `:all` is given, `:eof` is never returned, but an
empty string in case the device has reached EOF.
Note: do not use this function on IO devices in unicode mode
as it will return the wrong result.
"""
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
@spec binread(device, :all | :line | non_neg_integer) :: iodata | nodata
def binread(device \\ group_leader, chars_or_line)
def binread(device, :all) do
do_binread_all(map_dev(device), "")
end
def binread(device, :line) do
case :file.read_line(map_dev(device)) do
{:ok, data} -> data
@@ -94,6 +123,14 @@ defmodule IO do
end
end
defp do_binread_all(mapped_dev, acc) do
case :file.read_line(mapped_dev) do
{:ok, data} -> do_binread_all(mapped_dev, acc <> data)
:eof -> acc
other -> other
end
end
@doc """
Writes the given argument to the given device.
@@ -119,6 +156,9 @@ defmodule IO do
as a binary, no unicode conversion happens.
Check `write/2` for more information.
Note: do not use this function on IO devices in unicode mode
as it will return the wrong result.
"""
@spec binwrite(device, iodata) :: :ok | {:error, term}
def binwrite(device \\ group_leader(), item) when is_iodata(item) do
@@ -263,6 +303,9 @@ defmodule IO do
Note that an IO stream has side effects and every time
you go over the stream you may get different results.
Finally, do not use this function on IO devices in unicode
mode as it will return the wrong result.
"""
@spec binstream(device, :line | pos_integer) :: Enumerable.t
def binstream(device, line_or_bytes) do
+2
View File
@@ -22,6 +22,8 @@ defmodule IO.Stream do
defstruct device: nil, raw: true, line_or_bytes: :line
@type t :: %__MODULE__{}
@doc false
def __build__(device, raw, line_or_bytes) do
%IO.Stream{device: device, raw: raw, line_or_bytes: line_or_bytes}
+205 -107
View File
@@ -150,9 +150,9 @@ defmodule Kernel do
3
"""
@spec byte_size(bitstring) :: non_neg_integer
def byte_size(bitstring) do
:erlang.byte_size(bitstring)
@spec byte_size(binary) :: non_neg_integer
def byte_size(binary) do
:erlang.byte_size(binary)
end
@doc """
@@ -183,9 +183,56 @@ defmodule Kernel do
## Examples
When a process reaches its end, by default it exits with
reason `:normal`. You can also call it explicitly if you
want to terminate a process but not signal any failure:
exit(:normal)
In case something goes wrong, you can also use `exit/1` with
a different reason:
exit(:seems_bad)
If the reason is not `:normal`, all linked process to the
exited process will crash (unless they are trapping exits).
## OTP exits
Exits are used by OTP to determine if a process exited abnormally
or not. The following exits are considered "normal":
* `exit(:normal)`
* `exit(:shutdown)`
* `exit({:shutdown, term})`
Exiting with any other reason is considered abnormal and treated
as a crash. This means the default supervisor behaviour kicks in,
error reports are emitted, etc.
This behaviour is relied on in many different places. For example,
`ExUnit` uses `exit(:shutdown)` when exiting the test process to
signal linked processes, supervision trees and so on to politely
shutdown too.
## CLI exits
Building on top of the exit signals mentioned above, if the
process started by the command line exits with any of the three
reasons above, its exit is considered normal and the Operating
System process will exit with status 0.
It is, however, possible to customize the Operating System exit
signal by invoking:
exit({:shutdown, integer})
This will cause the OS process to exit with the status given by
`integer` while signaling all linked OTP processes to politely
shutdown.
Any other exit reason will cause the OS process to exit with
status `1` and linked OTP processes to crash.
"""
@spec exit(term) :: no_return
def exit(reason) do
@@ -1453,7 +1500,16 @@ defmodule Kernel do
Gets a value from a nested structure.
Uses the `Access` protocol to traverse the structures
according to the given `keys`.
according to the given `keys`, unless the `key` is a
function.
If a key is a function, the function will be invoked
passing three arguments, the operation (`:get`), the
data to be accessed, and a function to be invoked next.
This means `get_in/2` can be extended to provide
custom lookups. The downside is that functions cannot be
stored as keys in the accessed data structures.
## Examples
@@ -1468,18 +1524,44 @@ defmodule Kernel do
iex> get_in(users, ["unknown", :age])
nil
When one of the keys is a function, the function is invoked.
In the example below, we use a function to get all the maps
inside a list:
iex> users = [%{name: "josé", age: 27}, %{name: "eric", age: 23}]
iex> all = fn :get, data, next -> Enum.map(data, next) end
iex> get_in(users, [all, :age])
[27, 23]
If the previous value before invoking the function is nil,
the function *will* receive nil as a value and must handle it
accordingly.
"""
@spec get_in(Access.t, nonempty_list(term)) :: term
def get_in(data, keys)
def get_in(nil, list) when is_list(list), do: nil
def get_in(data, [h]), do: Access.get(data, h)
def get_in(data, [h|t]), do: get_in(Access.get(data, h), t)
def get_in(data, [h]) when is_function(h),
do: h.(:get, data, &(&1))
def get_in(data, [h|t]) when is_function(h),
do: h.(:get, data, &get_in(&1, t))
def get_in(nil, [_]),
do: nil
def get_in(nil, [_|t]),
do: get_in(nil, t)
def get_in(data, [h]),
do: Access.get(data, h)
def get_in(data, [h|t]),
do: get_in(Access.get(data, h), t)
@doc """
Puts a value in a nested structure.
Uses the `Access` protocol to traverse the structures
according to the given `keys`.
according to the given `keys`, unless the `key` is a
function. If the key is a function, it will be invoked
as specified in `get_and_update_in/3`.
## Examples
@@ -1487,13 +1569,8 @@ defmodule Kernel do
iex> put_in(users, ["josé", :age], 28)
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
In case any of entries in the middle returns `nil`, a map is dynamically
created:
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
iex> put_in(users, ["dave", :age], 13)
%{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 13}}
In case any of entries in the middle returns `nil`,
an error will be raised when trying to access it next.
"""
@spec put_in(Access.t, nonempty_list(term), term) :: Access.t
def put_in(data, keys, value) do
@@ -1504,7 +1581,9 @@ defmodule Kernel do
Updates a key in a nested structure.
Uses the `Access` protocol to traverse the structures
according to the given `keys`.
according to the given `keys`, unless the `key` is a
function. If the key is a function, it will be invoked
as specified in `get_and_update_in/3`.
## Examples
@@ -1512,13 +1591,8 @@ defmodule Kernel do
iex> update_in(users, ["josé", :age], &(&1 + 1))
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
In case any of entries in the middle returns `nil`, a map is dynamically
created:
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
iex> update_in(users, ["dave", :age], &((&1 || 0) + 1))
%{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 1}}
In case any of entries in the middle returns `nil`,
an error will be raised when trying to access it next.
"""
@spec update_in(Access.t, nonempty_list(term), (term -> term)) :: Access.t
def update_in(data, keys, fun) do
@@ -1528,9 +1602,20 @@ defmodule Kernel do
@doc """
Gets a value and updates a nested structure.
It expects a tuple to be returned, containing the value retrieved
and the update one. Uses the `Access` protocol to traverse the
structures according to the given `keys`.
It expects a tuple to be returned, containing the value
retrieved and the update one.
Uses the `Access` protocol to traverse the structures
according to the given `keys`, unless the `key` is a
function.
If a key is a function, the function will be invoked
passing three arguments, the operation (`:get_and_update`),
the data to be accessed, and a function to be invoked next.
This means `get_and_update_in/3` can be extended to provide
custom lookups. The downside is that functions cannot be stored
as keys in the accessed data structures.
## Examples
@@ -1543,23 +1628,34 @@ defmodule Kernel do
iex> get_and_update_in(users, ["josé", :age], &{&1, &1 + 1})
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}}
In case any of entries in the middle returns `nil`, a map is dynamically
created:
When one of the keys is a function, the function is invoked.
In the example below, we use a function to get and increment all
ages inside a list:
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
iex> get_and_update_in(users, ["dave", :age], &{&1, 13})
{nil, %{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 13}}}
iex> users = [%{name: "josé", age: 27}, %{name: "eric", age: 23}]
iex> all = fn :get_and_update, data, next ->
...> Enum.map(data, next) |> List.unzip() |> List.to_tuple()
...> end
iex> get_and_update_in(users, [all, :age], &{&1, &1 + 1})
{[27, 23], [%{name: "josé", age: 28}, %{name: "eric", age: 24}]}
If the previous value before invoking the function is nil,
the function *will* receive `nil` as a value and must handle it
accordingly (be it by failing or providing a sane default).
"""
@spec get_and_update_in(Access.t, nonempty_list(term),
(term -> {get, term})) :: {get, Access.t} when get: var
def get_and_update_in(data, keys, fun)
def get_and_update_in(nil, list, fun), do: get_and_update_in(%{}, list, fun)
def get_and_update_in(data, [h], fun), do: Access.get_and_update(data, h, fun)
def get_and_update_in(data, [h|t], fun) do
Access.get_and_update(data, h, &get_and_update_in(&1, t, fun))
end
def get_and_update_in(data, [h], fun) when is_function(h),
do: h.(:get_and_update, data, fun)
def get_and_update_in(data, [h|t], fun) when is_function(h),
do: h.(:get_and_update, data, &get_and_update_in(&1, t, fun))
def get_and_update_in(data, [h], fun),
do: Access.get_and_update(data, h, fun)
def get_and_update_in(data, [h|t], fun),
do: Access.get_and_update(data, h, &get_and_update_in(&1, t, fun))
@doc """
Puts a value in a nested structure via the given `path`.
@@ -1645,7 +1741,7 @@ defmodule Kernel do
## Examples
iex> users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
iex> get_and_update_in(users["josé"][:age], &{&1, &1 + 1})
iex> get_and_update_in(users["josé"].age, &{&1, &1 + 1})
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}}
## Paths
@@ -1714,7 +1810,7 @@ defmodule Kernel do
defp unnest(other, [], kind) do
raise ArgumentError,
"expected expression given to #{kind} to access at least one field, got: #{Macro.to_string other}"
"expected expression given to #{kind} to access at least one element, got: #{Macro.to_string other}"
end
defp unnest(other, acc, kind) do
@@ -1723,7 +1819,7 @@ defmodule Kernel do
false ->
raise ArgumentError,
"expression given to #{kind} must start with a variable, local or remote call " <>
"and be followed by field access, got: #{Macro.to_string other}"
"and be followed by an element access, got: #{Macro.to_string other}"
end
end
@@ -1894,10 +1990,7 @@ defmodule Kernel do
case is_list(args) and length(args) == 1 and typespec(name) do
false ->
case name == :typedoc and not bootstraped?(Kernel.Typespec) do
true -> nil
false -> do_at(args, name, function?, __CALLER__)
end
do_at(args, name, function?, __CALLER__)
macro ->
case bootstraped?(Kernel.Typespec) do
false -> nil
@@ -2561,7 +2654,7 @@ defmodule Kernel do
# Generate the alias for this module definition
{new, old} = module_nesting(env.module, full)
meta = [defined: full, context: true] ++ alias_meta(alias)
meta = [defined: full, context: env.module] ++ alias_meta(alias)
{full, {:alias, meta, [old, [as: new, warn: false]]}}
false ->
@@ -2819,28 +2912,48 @@ defmodule Kernel do
## Types
`defstruct` automatically generates a type `t` unless one exists.
The following definition:
It is recommended to define types for structs, by convention this type
is called `t`. To define a struct in a type the struct literal syntax
is used:
defmodule User do
defstruct name: "José" :: String.t,
age: 25 :: integer
defstruct name: "José", age: 25
@type t :: %User{name: String.t, age: integer}
end
Generates a type as follows:
It is recommended to only use the struct syntax when defining the struct's
type. When referring to another struct use `User.t`, not `%User{}`. Fields
in the struct not included in the type defaults to `term`.
@type t :: %User{name: String.t, age: integer}
In case a struct does not declare a field type, it defaults to `term`.
Private structs that are not used outside its module should use the private
type attribute `@typep`. Public structs whose internal structure is private
to the local module (you are not allowed to pattern match it or directly
access fields) should use the `@opaque` attribute. Structs whose internal
structure is public should use `@type`.
"""
defmacro defstruct(kv) do
{fields, types} = split_fields_and_types(kv)
defmacro defstruct(fields) do
{fields, types} = split_fields_and_types(fields)
types =
case types do
true ->
stacktrace = Exception.format_stacktrace(Macro.Env.stacktrace(__CALLER__))
IO.write :stderr, "warning: passing types to struct fields with :: is deprecated, " <>
"please define a type explicitly instead\n#{stacktrace}"
quote do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: %{__struct__: __MODULE__}
end
end
false ->
:ok
end
fields =
quote bind_quoted: [fields: fields] do
fields = :lists.map(fn
{ key, _ } = pair when is_atom(key) -> pair
key when is_atom(key) -> { key, nil }
{key, _} = pair when is_atom(key) -> pair
key when is_atom(key) -> {key, nil}
other -> raise ArgumentError, "struct field names must be atoms, got: #{inspect other}"
end, fields)
@@ -2853,33 +2966,12 @@ defmodule Kernel do
Protocol.__derive__(derive, __MODULE__, __ENV__)
end
@spec __struct__() :: t
@spec __struct__() :: %__MODULE__{}
def __struct__() do
@struct
end
end
types =
case bootstraped?(Kernel.Typespec) do
true when types == [] ->
quote unquote: false do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
types = :lists.map(fn {key, _} ->
{key, quote(do: term)}
end, fields)
@type t :: %{unquote_splicing(types), __struct__: __MODULE__}
end
end
true ->
quote do
unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
@type t :: %{unquote_splicing(types), __struct__: __MODULE__}
end
end
false ->
nil
end
quote do
unquote(fields)
unquote(types)
@@ -2888,26 +2980,26 @@ defmodule Kernel do
end
defp split_fields_and_types(kv) do
case Keyword.keyword?(kv) do
true -> split_fields_and_types(kv, [], [])
false -> {kv, []}
end
end
case Keyword.keyword?(kv) do
true -> split_fields_and_types(kv, [], false)
false -> {kv, false}
end
end
defp split_fields_and_types([{field, {:::, _, [default, type]}}|t], fields, types) do
split_fields_and_types(t, [{field, default}|fields], [{field, type}|types])
defp split_fields_and_types([{field, {:::, _, [default, _]}}|t], fields, _types) do
split_fields_and_types(t, [{field, default}|fields], true)
end
defp split_fields_and_types([{field, default}|t], fields, types) do
split_fields_and_types(t, [{field, default}|fields], [{field, quote(do: term)}|types])
split_fields_and_types(t, [{field, default}|fields], types)
end
defp split_fields_and_types([field|t], fields, types) do
split_fields_and_types(t, [field|fields], [{field, quote(do: term)}|types])
split_fields_and_types(t, [field|fields], types)
end
defp split_fields_and_types([], fields, types) do
{:lists.reverse(fields), :lists.reverse(types)}
{:lists.reverse(fields), types}
end
@doc ~S"""
@@ -2970,7 +3062,7 @@ defmodule Kernel do
@behaviour Exception
fields = defstruct unquote(fields)
@spec exception(term) :: t
@spec exception(Keyword.t) :: Exception.t
def exception(args) when is_list(args) do
Kernel.struct(__struct__, args)
end
@@ -2978,7 +3070,7 @@ defmodule Kernel do
defoverridable exception: 1
if Keyword.has_key?(fields, :message) do
@spec message(t) :: String.t
@spec message(Exception.t) :: String.t
def message(exception) do
exception.message
end
@@ -3309,7 +3401,7 @@ defmodule Kernel do
## Sigils
@doc """
@doc ~S"""
Handles the sigil ~S. It simply returns a string
without escaping characters and without interpolations.
@@ -3318,15 +3410,15 @@ defmodule Kernel do
iex> ~S(foo)
"foo"
iex> ~S(f\#{o}o)
"f\\\#{o}o"
iex> ~S(f#{o}o)
"f\#{o}o"
"""
defmacro sigil_S(string, []) do
string
end
@doc """
@doc ~S"""
Handles the sigil ~s. It returns a string as if it was double quoted
string, unescaping characters and replacing interpolations.
@@ -3335,15 +3427,18 @@ defmodule Kernel do
iex> ~s(foo)
"foo"
iex> ~s(f\#{:o}o)
iex> ~s(f#{:o}o)
"foo"
iex> ~s(f\#{:o}o)
"f\#{:o}o"
"""
defmacro sigil_s({:<<>>, line, pieces}, []) do
{:<<>>, line, Macro.unescape_tokens(pieces)}
end
@doc """
@doc ~S"""
Handles the sigil ~C. It simply returns a char list
without escaping characters and without interpolations.
@@ -3352,15 +3447,15 @@ defmodule Kernel do
iex> ~C(foo)
'foo'
iex> ~C(f\#{o}o)
'f\\\#{o}o'
iex> ~C(f#{o}o)
'f\#{o}o'
"""
defmacro sigil_C({:<<>>, _line, [string]}, []) when is_binary(string) do
String.to_char_list(string)
end
@doc """
@doc ~S"""
Handles the sigil ~c. It returns a char list as if it were a single
quoted string, unescaping characters and replacing interpolations.
@@ -3369,9 +3464,12 @@ defmodule Kernel do
iex> ~c(foo)
'foo'
iex> ~c(f\#{:o}o)
iex> ~c(f#{:o}o)
'foo'
iex> ~c(f\#{:o}o)
'f\#{:o}o'
"""
# We can skip the runtime conversion if we are
@@ -3405,13 +3503,13 @@ defmodule Kernel do
quote do: Regex.compile!(unquote(binary), unquote(:binary.list_to_bin(options)))
end
@doc """
@doc ~S"""
Handles the sigil ~R. It returns a Regex pattern without escaping
nor interpreting interpolations.
## Examples
iex> Regex.match?(~R(f\#{1,3}o), "f\#o")
iex> Regex.match?(~R(f#{1,3}o), "f#o")
true
"""
@@ -3420,7 +3518,7 @@ defmodule Kernel do
Macro.escape(regex)
end
@doc """
@doc ~S"""
Handles the sigil ~w. It returns a list of "words" split by whitespace.
## Modifiers
@@ -3431,7 +3529,7 @@ defmodule Kernel do
## Examples
iex> ~w(foo \#{:bar} baz)
iex> ~w(foo #{:bar} baz)
["foo", "bar", "baz"]
iex> ~w(--source test/enum_test.exs)
@@ -3451,7 +3549,7 @@ defmodule Kernel do
split_words(binary, modifiers)
end
@doc """
@doc ~S"""
Handles the sigil ~W. It returns a list of "words" split by whitespace
without escaping nor interpreting interpolations.
@@ -3463,8 +3561,8 @@ defmodule Kernel do
## Examples
iex> ~W(foo \#{bar} baz)
["foo", "\\\#{bar}", "baz"]
iex> ~W(foo #{bar} baz)
["foo", "\#{bar}", "baz"]
"""
defmacro sigil_W({:<<>>, _line, [string]}, modifiers) when is_binary(string) do
+60 -48
View File
@@ -3,7 +3,7 @@ defmodule Kernel.CLI do
@blank_config %{commands: [], output: ".", compile: [],
halt: true, compiler_options: [], errors: [],
verbose_compile: false}
pa: [], pz: [], verbose_compile: false}
@doc """
This is the API invoked by Elixir boot process.
@@ -12,9 +12,10 @@ defmodule Kernel.CLI do
argv = for arg <- argv, do: IO.chardata_to_string(arg)
{config, argv} = parse_argv(argv)
:elixir_code_server.cast({:paths, config.pa, config.pz})
System.argv(argv)
run fn ->
run fn _ ->
errors = process_commands(config)
if errors != [] do
@@ -33,37 +34,19 @@ defmodule Kernel.CLI do
by escripts generated by Elixir.
"""
def run(fun, halt \\ true) do
try do
fun.()
if halt do
at_exit(0)
System.halt(0)
end
catch
:exit, reason when is_integer(reason) ->
at_exit(reason)
System.halt(reason)
:exit, :normal ->
at_exit(0)
System.halt(0)
kind, reason ->
at_exit(1)
print_error(kind, reason, System.stacktrace)
System.halt(1)
res = exec_fun(fun, {:ok, 0})
if elem(res, 0) == :shutdown or halt do
{_, int} = at_exit(res)
System.halt(int)
end
end
@doc """
Parses ARGV returning the CLI config and trailing args.
"""
@doc false
def parse_argv(argv) do
parse_argv(argv, @blank_config)
end
@doc """
Process commands according to the parsed config from `parse_argv/1`.
Returns all errors.
"""
@doc false
def process_commands(config) do
results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
errors = for {:error, msg} <- results, do: msg
@@ -72,21 +55,48 @@ defmodule Kernel.CLI do
## Helpers
defp at_exit(status) do
defp at_exit(res) do
hooks = :elixir_code_server.call(:flush_at_exit)
res = Enum.reduce(hooks, res, &exec_fun/2)
if hooks == [], do: res, else: at_exit(res)
end
for hook <- hooks do
try do
hook.(status)
catch
kind, reason ->
print_error(kind, reason, System.stacktrace)
defp exec_fun(fun, res) when is_function(fun, 1) and is_tuple(res) do
parent = self()
{pid, ref} =
spawn_monitor fn ->
try do
fun.(elem(res, 1))
catch
:exit, {:shutdown, int} when is_integer(int) ->
send parent, {self, {:shutdown, int}}
exit({:shutdown, int})
:exit, reason
when reason == :normal
when reason == :shutdown
when tuple_size(reason) == 2 and elem(reason, 0) == :shutdown ->
send parent, {self, {:shutdown, 0}}
exit(reason)
kind, reason ->
stack = System.stacktrace
print_error(kind, reason, stack)
send parent, {self, {:shutdown, 1}}
:erlang.raise(kind, reason, stack)
else
_ ->
send parent, {self, res}
end
end
end
# If an at_exit callback adds a
# new hook we need to invoke it.
unless hooks == [], do: at_exit(status)
receive do
{^pid, res} ->
:erlang.demonitor(ref, [:flush])
res
{:DOWN, ^ref, _, _, other} ->
print_error({:EXIT, pid}, other, [])
{:shutdown, 1}
end
end
defp shared_option?(list, config, callback) do
@@ -129,13 +139,15 @@ defmodule Kernel.CLI do
end
defp parse_shared(["-pa", h|t], config) do
add_code_path(h, &Code.prepend_path/1)
parse_shared t, config
paths = expand_code_path(h)
Enum.each(paths, &:code.add_patha/1)
parse_shared t, %{config | pa: config.pa ++ paths}
end
defp parse_shared(["-pz", h|t], config) do
add_code_path(h, &Code.append_path/1)
parse_shared t, config
paths = expand_code_path(h)
Enum.each(paths, &:code.add_pathz/1)
parse_shared t, %{config | pz: config.pz ++ paths}
end
defp parse_shared(["--app", h|t], config) do
@@ -170,12 +182,11 @@ defmodule Kernel.CLI do
{list, config}
end
defp add_code_path(path, fun) do
defp expand_code_path(path) do
path = Path.expand(path)
case Path.wildcard(path) do
[] -> fun.(path)
list -> Enum.each(list, fun)
[] -> [to_char_list(path)]
list -> Enum.map(list, &to_char_list/1)
end
end
@@ -352,7 +363,8 @@ defmodule Kernel.CLI do
end
defp process_command({:compile, patterns}, config) do
:filelib.ensure_dir(:filename.join(config.output, "."))
# If ensuring the dir returns an error no files will be found.
_ = :filelib.ensure_dir(:filename.join(config.output, "."))
case filter_multiple_patterns(patterns) do
{:ok, []} ->
@@ -407,8 +419,8 @@ defmodule Kernel.CLI do
# the actual Elixir executable.
case :os.type() do
{:win32, _} ->
exec = Path.rootname(exec)
if File.regular?(exec), do: exec
base = Path.rootname(exec)
if File.regular?(base), do: base, else: exec
_ ->
exec
end
+1 -1
View File
@@ -25,7 +25,7 @@ defmodule Kernel.ErrorHandler do
defp ensure_loaded(module) do
case Code.ensure_loaded(module) do
{:module, _} -> []
{:module, _} -> :ok
{:error, _} ->
parent = :erlang.get(:elixir_compiler_pid)
ref = :erlang.make_ref
+4 -4
View File
@@ -43,7 +43,7 @@ defmodule Kernel.ParallelCompiler do
end
defp spawn_compilers(files, path, callbacks) do
Code.ensure_loaded(Kernel.ErrorHandler)
true = Code.ensure_loaded?(Kernel.ErrorHandler)
compiler_pid = self()
:elixir_code_server.cast({:reset_warnings, compiler_pid})
schedulers = max(:erlang.system_info(:schedulers_online), 2)
@@ -54,7 +54,7 @@ defmodule Kernel.ParallelCompiler do
# compilation status will be set to error and we fail with CompileError
case :elixir_code_server.call({:compilation_status, compiler_pid}) do
:ok -> result
:error -> exit(1)
:error -> exit({:shutdown, 1})
end
end
@@ -87,7 +87,7 @@ defmodule Kernel.ParallelCompiler do
:erlang.process_flag(:error_handler, Kernel.ErrorHandler)
exit(try do
if output do
_ = if output do
:elixir_compiler.file_to_path(h, output)
else
:elixir_compiler.file(h)
@@ -186,7 +186,7 @@ defmodule Kernel.ParallelCompiler do
if all_missing?(entries, waiting, queued) do
collect_failures(queued, length(queued) - 1)
end
exit(1)
exit({:shutdown, 1})
end
end
+15 -12
View File
@@ -242,14 +242,14 @@ defmodule Kernel.SpecialForms do
Here, `val` is interpreted as unsigned.
The endianness of a segment can be big, little or native (the
latter meaning it will be resolved at VM load time). Passing
many options can be done by giving a list:
latter meaning it will be resolved at VM load time). Many options
can be given by using `-` as separator:
<<102 :: [integer, native], rest :: binary>>
<<102 :: integer-native, rest :: binary>>
Or:
<<102 :: [unsigned, big, integer], rest :: binary>>
<<102 :: unsigned-big-integer, rest :: binary>>
And so on.
@@ -271,7 +271,7 @@ defmodule Kernel.SpecialForms do
We can also match by specifying size and unit explicitly:
iex> <<102, _rest :: [size(2), unit(8)]>> = "foo"
iex> <<102, _rest :: size(2)-unit(8)>> = "foo"
"foo"
However, if we expect a size of 32, it won't match:
@@ -286,12 +286,15 @@ defmodule Kernel.SpecialForms do
in a binary match can use the default size (all others must
have their size specified explicitly).
Size can also be specified using a syntax shortcut. Instead of
writing `size(8)`, one can write just `8` and it will be interpreted
as `size(8)`
Size and unit can also be specified using a syntax shortcut
when passing integer values:
iex> << 1 :: 3 >> == << 1 :: size(3) >>
true
<< x :: 8 >> == << x :: size(8) >>
<< x :: 8 * 4 >> == << x :: size(8)-unit(4) >>
<< x :: _ * 4 >> == << x :: unit(4) >>
This syntax reflects the fact the effective size is given by
multiplying the size by the unit.
For floats, `size * unit` must result in 32 or 64, corresponding
to binary32 and binary64, respectively.
@@ -1513,9 +1516,9 @@ defmodule Kernel.SpecialForms do
The catch clause can be used to catch throws values and exits.
try do
exit(1)
exit(:shutdown)
catch
:exit, 1 -> IO.puts "Exited with 1"
:exit, :shutdown -> IO.puts "Exited with shutdown reason"
end
try do
+107 -17
View File
@@ -38,6 +38,7 @@ defmodule Kernel.Typespec do
| Fun
| Integer
| List
| Map
| Tuple
| Union
| UserDefined # Described in section "Defining a type"
@@ -65,16 +66,33 @@ defmodule Kernel.Typespec do
| nonempty_list(Type) # proper non-empty list
| [] # empty list
| [Type] # shorthand for list(Type)
| [...] # shorthand for nonempty_list()
| [Type, ...] # shorthand for nonempty_list(Type)
| [Keyword]
Tuple :: tuple # a tuple of any size
| {} # empty tuple
Map :: map() # map of any size
| %{} # map of any size
| %Struct{} # struct (see defstruct/1)
| %Struct{Keyword}
| %{Keyword}
| %{Pairs}
Tuple :: tuple # a tuple of any size
| {} # empty tuple
| {TList}
| record(Atom) # record (see Record)
| record(Atom, Keyword)
Keyword :: ElixirAtom: Type
| ElixirAtom: Type, Keyword
Pairs :: Type => Type
| Type => Type, Pairs
TList :: Type
| Type, TList
Union :: Type1 | Type2
Union :: Type | Type
### Bit strings
@@ -111,14 +129,15 @@ defmodule Kernel.Typespec do
`bitstring` | `<< _ :: _ * 1 >>`
`boolean` | `false` &#124; `true`
`byte` | `0..255`
`char` | `0..0xffff`
`char` | `0..0x10ffff`
`number` | `integer` &#124; `float`
`char_list` | [char]
`list` | `[any]`
`maybe_improper_list` | `maybe_improper_list(any, any)`
`nonempty_list` | `nonempty_list(any)`
`iodata` | `iolist` &#124; `binary`
`iolist` | `maybe_improper_list(byte` &#124; `binary` &#124; `iolist, binary` &#124; `[])`
`module` | `atom`
`module` | `atom` | `tuple`
`mfa` | `{atom, atom, arity}`
`arity` | `0..255`
`node` | `atom`
@@ -536,7 +555,7 @@ defmodule Kernel.Typespec do
if not export and doc do
:elixir_errors.warn(caller.line, caller.file, "type #{name}/#{arity} is private, " <>
"@typedoc's are always discarded for private types\n")
"@typedoc's are always discarded for private types")
end
{{kind, {name, arity}, type}, caller.line, export, doc}
@@ -640,8 +659,20 @@ defmodule Kernel.Typespec do
end
end
defp typespec_to_ast({:type, _line, :list, args}) do
for arg <- args, do: typespec_to_ast(arg)
defp typespec_to_ast({:type, line, :list, []}) do
{:list, [line: line], []}
end
defp typespec_to_ast({:type, _line, :list, [arg]}) do
[typespec_to_ast(arg)]
end
defp typespec_to_ast({:type, line, :nonempty_list, []}) do
[{:..., [line: line], nil}]
end
defp typespec_to_ast({:type, line, :nonempty_list, [arg]}) do
[typespec_to_ast(arg), {:..., [line: line], nil}]
end
defp typespec_to_ast({:type, line, :map, fields}) do
@@ -690,7 +721,11 @@ defmodule Kernel.Typespec do
end
defp typespec_to_ast({:type, line, :range, [left, right]}) do
{:"..", [line: line], [typespec_to_ast(left), typespec_to_ast(right)]}
{:.., [line: line], [typespec_to_ast(left), typespec_to_ast(right)]}
end
defp typespec_to_ast({:type, _line, nil, []}) do
[]
end
defp typespec_to_ast({:type, line, name, args}) do
@@ -792,7 +827,58 @@ defmodule Kernel.Typespec do
end
defp typespec({:%, _, [name, {:%{}, meta, fields}]}, vars, caller) do
typespec({:%{}, meta, [{:__struct__, name}|fields]}, vars, caller)
module = Macro.expand(name, caller)
struct =
if module == caller.module do
Module.get_attribute(module, :struct) ||
compile_error(caller, "struct is not defined for #{Macro.to_string(name)}")
else
module.__struct__
end
struct =
:lists.map(fn {field, _} ->
{field, quote do: term()}
end, Map.to_list(struct))
:lists.foreach(fn {field, _} ->
unless Keyword.has_key?(struct, field) do
compile_error(caller, "undefined field #{field} on struct #{Macro.to_string(name)}")
end
end, fields)
fields = Keyword.merge(struct, [__struct__: module] ++ fields)
typespec({:%{}, meta, fields}, vars, caller)
end
# Handle records
defp typespec({:record, meta, [atom]}, vars, caller) do
typespec({:record, meta, [atom, []]}, vars, caller)
end
defp typespec({:record, meta, [atom, fields]}, vars, caller) do
case Macro.expand({atom, [], [{atom, [], []}]}, caller) do
keyword when is_list(keyword) ->
keyword =
:lists.map(fn {field, _} ->
{field, quote do: term()}
end, keyword)
:lists.foreach(fn {field, _} ->
unless Keyword.has_key?(keyword, field) do
compile_error(caller, "undefined field #{field} on record #{inspect atom}")
end
end, fields)
fields = Keyword.merge(keyword, fields)
types = Keyword.values(fields)
typespec({:{}, meta, [atom|types]}, vars, caller)
_ ->
compile_error(caller, "unknown record #{inspect atom}")
end
end
# Handle ranges
@@ -866,11 +952,11 @@ defmodule Kernel.Typespec do
end
# Handle local calls
defp typespec({:string, meta, arguments}, vars, caller) do
:elixir_errors.warn caller.line, caller.file, "string() type use is discouraged. For character lists, use " <>
defp typespec({type, meta, arguments}, vars, caller) when type in [:string, :nonempty_string] do
:elixir_errors.warn caller.line, caller.file, "#{type}() type use is discouraged. For character lists, use " <>
"char_list() type, for strings, String.t()\n#{Exception.format_stacktrace(Macro.Env.stacktrace(caller))}"
arguments = for arg <- arguments, do: typespec(arg, vars, caller)
{:type, line(meta), :string, arguments}
{:type, line(meta), type, arguments}
end
defp typespec({:char_list, _meta, []}, vars, caller) do
@@ -899,14 +985,18 @@ defmodule Kernel.Typespec do
typespec({nil, [], []}, vars, caller)
end
defp typespec([spec], vars, caller) do
typespec({:list, [], [spec]}, vars, caller)
defp typespec([{:..., _, atom}], vars, caller) when is_atom(atom) do
typespec({:nonempty_list, [], []}, vars, caller)
end
defp typespec([spec, {:"...", _, quoted}], vars, caller) when is_atom(quoted) do
defp typespec([spec, {:..., _, atom}], vars, caller) when is_atom(atom) do
typespec({:nonempty_list, [], [spec]}, vars, caller)
end
defp typespec([spec], vars, caller) do
typespec({:list, [], [spec]}, vars, caller)
end
defp typespec(list, vars, caller) do
[h|t] = :lists.reverse(list)
union = :lists.foldl(fn(x, acc) ->
@@ -941,7 +1031,7 @@ defmodule Kernel.Typespec do
end
end
defp fn_args(meta, [{:"...", _, _}], _vars, _caller) do
defp fn_args(meta, [{:..., _, _}], _vars, _caller) do
{:type, line(meta), :any}
end
+7 -3
View File
@@ -307,6 +307,8 @@ defmodule List do
@doc """
Zips corresponding elements from each list in `list_of_lists`.
The zipping finishes as soon as any list terminates.
## Examples
iex> List.zip([[1, 2], [3, 4], [5, 6]])
@@ -323,8 +325,10 @@ defmodule List do
end
@doc """
Unzips the given list of lists or tuples into separate lists and returns a
list of lists.
Unzips the given list of tuples into a list of lists.
The number of unzipped elements is equal to the size
of the smallest tuple in the list.
## Examples
@@ -566,7 +570,7 @@ defmodule List do
"abc"
"""
@spec to_string(:unicode.char_list) :: String.t
@spec to_string(:unicode.charlist) :: String.t
def to_string(list) when is_list(list) do
case :unicode.characters_to_binary(list) do
result when is_binary(result) ->
+37 -8
View File
@@ -74,7 +74,7 @@ defmodule Macro do
def pipe(expr, call_args, position)
def pipe(expr, {:&, _, _} = call_args, _integer) do
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}"
bad_pipe(expr, call_args)
end
def pipe(expr, {call, line, atom}, integer) when is_atom(atom) do
@@ -86,7 +86,12 @@ defmodule Macro do
end
def pipe(expr, call_args, _integer) do
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}"
bad_pipe(expr, call_args)
end
defp bad_pipe(expr, call_args) do
raise ArgumentError, "cannot pipe #{to_string expr} into #{to_string call_args}, " <>
"can only pipe into local calls foo(), remote calls Foo.bar() or anonymous functions calls foo.()"
end
@doc """
@@ -525,11 +530,15 @@ defmodule Macro do
# All other calls
def to_string({target, _, args} = ast, fun) when is_list(args) do
{list, last} = :elixir_utils.split_last(args)
fun.(ast, case kw_blocks?(last) do
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
false -> call_to_string_with_args(target, args, fun)
end)
if sigil = sigil_call(ast, fun) do
sigil
else
{list, last} = :elixir_utils.split_last(args)
fun.(ast, case kw_blocks?(last) do
true -> call_to_string_with_args(target, list, fun) <> kw_blocks_to_string(last, fun)
false -> call_to_string_with_args(target, args, fun)
end)
end
end
# Two-item tuples
@@ -565,6 +574,26 @@ defmodule Macro do
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, [])
defp module_to_string(other, fun), do: call_to_string(other, fun)
defp sigil_call({func, _, [{:<<>>, _, [string]}, args]} = ast, fun) when is_list(args) do
sigil =
case Atom.to_string(func) do
<<"sigil_", name>> ->
"~" <> <<name>> <>
fun.(string, inspect(string, [])) <>
sigil_args(args, fun)
_ ->
nil
end
fun.(ast, sigil)
end
defp sigil_call(_other, _fun) do
nil
end
defp sigil_args([], _fun), do: ""
defp sigil_args(args, fun), do: fun.(args, List.to_string(args))
defp call_to_string(atom, _fun) when is_atom(atom), do: Atom.to_string(atom)
defp call_to_string({:., _, [arg]}, fun), do: module_to_string(arg, fun) <> "."
defp call_to_string({:., _, [left, right]}, fun), do: module_to_string(left, fun) <> "." <> call_to_string(right, fun)
@@ -838,7 +867,7 @@ defmodule Macro do
end
expand = :elixir_dispatch.expand_import(meta, {atom, length(args)}, args,
env, extra)
env, extra, true)
case expand do
{:ok, receiver, quoted} ->
+3 -3
View File
@@ -29,7 +29,7 @@ defmodule Macro.Env do
* `local` - the module to expand local functions to
"""
@type name_arity :: {atom, non_neg_integer}
@type name_arity :: {atom, arity}
@type file :: binary
@type line :: non_neg_integer
@type aliases :: [{module, module}]
@@ -42,10 +42,10 @@ defmodule Macro.Env do
@type vars :: [{atom, atom | non_neg_integer}]
@type export_vars :: vars | nil
@type lexical_tracker :: pid
@type local :: module | nil
@type local :: atom | nil
@type t :: %{__struct__: __MODULE__,
module: module,
module: atom,
file: file,
line: line,
function: name_arity | nil,
+44 -41
View File
@@ -766,15 +766,9 @@ defmodule Module do
@foo
Expands to:
Expands close to:
Module.get_attribute(__MODULE__, :foo, true)
Notice the third argument may be given to indicate a stacktrace
to be emitted when the attribute was not previously defined.
The default value for `warn` is nil for direct calls but the `@foo`
macro sets it to the proper stacktrace automatically, warning
every time `@foo` is used but not set previously.
Module.get_attribute(__MODULE__, :foo)
## Examples
@@ -788,37 +782,9 @@ defmodule Module do
end
"""
@spec get_attribute(module, atom, warn :: nil | [tuple]) :: term
def get_attribute(module, key, warn \\ nil) when
is_atom(key) and (is_list(warn) or nil?(warn)) do
assert_not_compiled!(:get_attribute, module)
table = data_table_for(module)
case :ets.lookup(table, key) do
[{^key, val}] -> val
[] ->
acc = :ets.lookup_element(table, :__acc_attributes, 2)
cond do
:lists.member(key, acc) ->
[]
is_list(warn) ->
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it to nil before access\n"
nil
true ->
nil
end
end
end
defp warn_info([entry|_]) do
opts = elem(entry, tuple_size(entry) - 1)
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
end
defp warn_info([]) do
""
@spec get_attribute(atom, atom) :: term
def get_attribute(module, key) do
get_attribute(module, key, nil)
end
@doc """
@@ -832,10 +798,12 @@ defmodule Module do
end
"""
@spec delete_attribute(atom, atom) :: :ok
def delete_attribute(module, key) when is_atom(key) do
assert_not_compiled!(:delete_attribute, module)
table = data_table_for(module)
:ets.delete(table, key)
:ok
end
@doc """
@@ -911,7 +879,9 @@ defmodule Module do
:ok ->
:ok
{:error, :private_doc} ->
:elixir_errors.warn line, env.file, "function #{name}/#{arity} is private, @doc's are always discarded for private functions\n"
:elixir_errors.warn line, env.file,
"function #{name}/#{arity} is private, " <>
"@doc's are always discarded for private functions"
end
delete_attribute(module, :doc)
@@ -933,6 +903,38 @@ defmodule Module do
:ets.insert(table, {key, new})
end
@doc false
def get_attribute(module, key, warn) when is_atom(key) and (is_list(warn) or nil?(warn)) do
assert_not_compiled!(:get_attribute, module)
table = data_table_for(module)
case :ets.lookup(table, key) do
[{^key, val}] -> val
[] ->
acc = :ets.lookup_element(table, :__acc_attributes, 2)
cond do
:lists.member(key, acc) ->
[]
is_list(warn) ->
:elixir_errors.warn warn_info(warn), "undefined module attribute @#{key}, " <>
"please remove access to @#{key} or explicitly set it to nil before access"
nil
true ->
nil
end
end
end
defp warn_info([entry|_]) do
opts = elem(entry, tuple_size(entry) - 1)
Exception.format_file_line(Keyword.get(opts, :file), Keyword.get(opts, :line)) <> " "
end
defp warn_info([]) do
""
end
## Helpers
defp normalize_attribute(:on_load, atom) when is_atom(atom) do
@@ -940,7 +942,8 @@ defmodule Module do
end
defp normalize_attribute(:behaviour, atom) when is_atom(atom) do
Code.ensure_compiled(atom)
# Attempt to compile behaviour but ignore failure (will warn later)
_ = Code.ensure_compiled(atom)
atom
end
+61 -23
View File
@@ -65,16 +65,19 @@ defmodule Module.LocalsTracker do
"""
@spec reachable(ref) :: [local]
def reachable(ref) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
reduce_reachable(d, :local, [])
reachable_from(:gen_server.call(to_pid(ref), :digraph, @timeout), :local)
end
defp reachable_from(d, starting) do
:sets.to_list(reduce_reachable(d, starting, :sets.new))
end
defp reduce_reachable(d, vertex, vertices) do
neighbours = :digraph.out_neighbours(d, vertex)
neighbours = (for {_, _} = t <- 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)
neighbours = (for {_, _} = t <- neighbours, do: t) |> :sets.from_list
remaining = :sets.subtract(neighbours, vertices)
vertices = :sets.union(neighbours, vertices)
:sets.fold(&reduce_reachable(d, &1, &2), vertices, remaining)
end
defp to_pid(pid) when is_pid(pid), do: pid
@@ -134,9 +137,7 @@ defmodule Module.LocalsTracker do
# Reattach a previously yanked node
@doc false
def reattach(pid, kind, tuple, neighbours) do
pid = to_pid(pid)
add_definition(pid, kind, tuple)
:gen_server.cast(pid, {:reattach, tuple, neighbours})
:gen_server.cast(to_pid(pid), {:reattach, kind, tuple, neighbours})
end
# Collecting all conflicting imports with the given functions
@@ -156,12 +157,42 @@ defmodule Module.LocalsTracker do
# 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)
def collect_unused_locals(ref, private) do
d = :gen_server.call(to_pid(ref), :digraph, @timeout)
{unreachable(d, private), collect_warnings(d, private)}
end
defp collect_unused_locals({tuple, kind, 0}, acc, reachable) do
defp unreachable(d, private) do
unreachable = for {tuple, _, _} <- private, do: tuple
private =
for {tuple, :defp, _} <- private do
neighbours = :digraph.in_neighbours(d, tuple)
neighbours = for {_, _} = t <- neighbours, do: t
{tuple, :sets.from_list(neighbours)}
end
reduce_unreachable(private, [], :sets.from_list(unreachable))
end
defp reduce_unreachable([{vertex, callers}|t], acc, unreachable) do
if :sets.is_subset(callers, unreachable) do
reduce_unreachable(t, [{vertex, callers}|acc], unreachable)
else
reduce_unreachable(acc ++ t, [], :sets.del_element(vertex, unreachable))
end
end
defp reduce_unreachable([], _acc, unreachable) do
:sets.to_list(unreachable)
end
defp collect_warnings(d, private) do
reachable = reachable_from(d, :local)
:lists.foldl(&collect_warnings(&1, &2, reachable), [], private)
end
defp collect_warnings({tuple, kind, 0}, acc, reachable) do
if :lists.member(tuple, reachable) do
acc
else
@@ -169,7 +200,7 @@ defmodule Module.LocalsTracker do
end
end
defp collect_unused_locals({tuple, kind, default}, acc, reachable) when default > 0 do
defp collect_warnings({tuple, kind, default}, acc, reachable) when default > 0 do
{name, arity} = tuple
min = arity - default
max = arity
@@ -268,9 +299,19 @@ defmodule Module.LocalsTracker do
{:noreply, state}
end
def handle_cast({:reattach, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
for from <- in_neigh, do: replace_edge(d, from, tuple)
for to <- out_neigh, do: replace_edge(d, tuple, to)
def handle_cast({:reattach, kind, tuple, {in_neigh, out_neigh}}, {d, _} = state) do
handle_add_definition(d, kind, tuple)
for from <- in_neigh do
:digraph.add_vertex(d, from)
replace_edge!(d, from, tuple)
end
for to <- out_neigh do
:digraph.add_vertex(d, to)
replace_edge!(d, tuple, to)
end
{:noreply, state}
end
@@ -300,6 +341,8 @@ defmodule Module.LocalsTracker do
if function != nil do
replace_edge!(d, function, tuple)
end
:ok
end
defp handle_add_local(d, from, to) do
@@ -320,11 +363,6 @@ defmodule Module.LocalsTracker do
unless :lists.member(to, :digraph.out_neighbours(d, from)) do
[:"$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
:ok
end
end
+92 -59
View File
@@ -45,7 +45,7 @@ defmodule OptionParser do
* `:strict` - the switches are strict. Any switch that does not
exist in the switch list is treated as an error.
* `:switches` - configure some switches. Switches that does not
* `:switches` - defines some switches. Switches that does not
exist in the switch list are still attempted to be parsed.
Note only `:strict` or `:switches` may be given at once.
@@ -59,7 +59,7 @@ defmodule OptionParser do
* `:float` - parses the switch as a float.
* `:string` - returns the switch as a string.
If a switch can't be parsed or is not specfied in the strict case,
If a switch can't be parsed or is not specified in the strict case,
the option is returned in the invalid options list (third element
of the returned tuple).
@@ -91,14 +91,8 @@ defmodule OptionParser do
## Negation switches
All switches starting with `--no-` are considered to be booleans and never
parse the next value:
iex> OptionParser.parse(["--no-op", "path/to/file"])
{[no_op: true], ["path/to/file"], []}
However, in case the base switch exists, it sets that particular switch to
false:
In case a switch is declared as boolean, it may be passed as `--no-SWITCH`
which will set the option to false:
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
{[op: false], ["path/to/file"], []}
@@ -113,8 +107,7 @@ defmodule OptionParser do
"""
@spec parse(argv, options) :: {parsed, argv, errors}
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
config = compile_config(opts, true)
do_parse(argv, config, [], [], [])
do_parse(argv, compile_config(opts), [], [], [], true)
end
@doc """
@@ -134,37 +127,36 @@ defmodule OptionParser do
"""
@spec parse_head(argv, options) :: {parsed, argv, errors}
def parse_head(argv, opts \\ []) when is_list(argv) and is_list(opts) do
config = compile_config(opts, false)
do_parse(argv, config, [], [], [])
do_parse(argv, compile_config(opts), [], [], [], false)
end
defp do_parse([], _config, opts, args, invalid) do
defp do_parse([], _config, opts, args, invalid, _all?) do
{Enum.reverse(opts), Enum.reverse(args), Enum.reverse(invalid)}
end
defp do_parse(argv, {aliases, switches, strict, all}=config, opts, args, invalid) do
defp do_parse(argv, {aliases, switches, strict}=config, opts, args, invalid, all?) do
case next(argv, aliases, switches, strict) do
{:ok, option, value, rest} ->
# the option exist and it was successfully parsed
kinds = List.wrap Keyword.get(switches, option)
new_opts = do_store_option(opts, option, value, kinds)
do_parse(rest, config, new_opts, args, invalid)
do_parse(rest, config, new_opts, args, invalid, all?)
{:invalid, option, value, rest} ->
# the option exist but it has wrong value
do_parse(rest, config, opts, args, [{option, value}|invalid])
do_parse(rest, config, opts, args, [{option, value}|invalid], all?)
{:undefined, option, _value, rest} ->
# the option does not exist (for strict cases)
do_parse(rest, config, opts, args, [{option, nil}|invalid])
do_parse(rest, config, opts, args, [{option, nil}|invalid], all?)
{:error, ["--"|rest]} ->
{Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
{:error, [arg|rest]=remaining_args} ->
# there is no option
if all do
do_parse(rest, config, opts, [arg|args], invalid)
if all? do
do_parse(rest, config, opts, [arg|args], invalid, all?)
else
{Enum.reverse(opts), Enum.reverse(args, remaining_args), Enum.reverse(invalid)}
end
@@ -193,12 +185,12 @@ defmodule OptionParser do
@spec next(argv, options) ::
{:ok, key :: atom, value :: term, argv} |
{:invalid, key :: atom, value :: term, argv} |
{:undefined, key :: atom, value :: term, argv} |
{:invalid, String.t, String.t | nil, argv} |
{:undefined, String.t, String.t | nil, argv} |
{:error, argv}
def next(argv, opts \\ []) when is_list(argv) and is_list(opts) do
{aliases, switches, strict, _} = compile_config(opts, true)
{aliases, switches, strict} = compile_config(opts)
next(argv, aliases, switches, strict)
end
@@ -221,7 +213,7 @@ defmodule OptionParser do
defp next(["-" <> option|rest], aliases, switches, strict) do
{option, value} = split_option(option)
opt_name_bin = "-" <> option
tagged = tag_option(option, value, switches, aliases)
tagged = tag_option(option, switches, aliases)
if strict and not option_defined?(tagged, switches) do
{:undefined, opt_name_bin, value, rest}
@@ -239,9 +231,65 @@ defmodule OptionParser do
{:error, argv}
end
@doc ~S"""
Splits a string into argv chunks.
## Examples
iex> OptionParser.split("foo bar")
["foo", "bar"]
iex> OptionParser.split("foo \"bar baz\"")
["foo", "bar baz"]
"""
@spec split(String.t) :: argv
def split(string) do
do_split(strip_leading_spaces(string), "", [], nil)
end
# If we have a escaped quote, simply remove the escape
defp do_split(<<?\\, quote, t :: binary>>, buffer, acc, quote),
do: do_split(t, <<buffer::binary, quote>>, acc, quote)
# If we have a quote and we were not in a quote, start one
defp do_split(<<quote, t :: binary>>, buffer, acc, nil) when quote in [?", ?'],
do: do_split(t, buffer, acc, quote)
# If we have a quote and we were inside it, close it
defp do_split(<<quote, t :: binary>>, buffer, acc, quote),
do: do_split(t, buffer, acc, nil)
# If we have a escaped quote/space, simply remove the escape as long as we are not inside a quote
defp do_split(<<?\\, h, t :: binary>>, buffer, acc, nil) when h in [?\s, ?', ?"],
do: do_split(t, <<buffer::binary, h>>, acc, nil)
# If we have space and we are outside of a quote, start new segment
defp do_split(<<?\s, t :: binary>>, buffer, acc, nil),
do: do_split(strip_leading_spaces(t), "", [buffer|acc], nil)
# All other characters are moved to buffer
defp do_split(<<h, t::binary>>, buffer, acc, quote) do
do_split(t, <<buffer::binary, h>>, acc, quote)
end
# Finish the string expecting a nil marker
defp do_split(<<>>, "", acc, nil),
do: Enum.reverse(acc)
defp do_split(<<>>, buffer, acc, nil),
do: Enum.reverse([buffer|acc])
# Otherwise raise
defp do_split(<<>>, _, _acc, marker) do
raise "argv string did not terminate properly, a #{<<marker>>} was opened but never closed"
end
defp strip_leading_spaces(" " <> t), do: strip_leading_spaces(t)
defp strip_leading_spaces(t), do: t
## Helpers
defp compile_config(opts, all) do
defp compile_config(opts) do
aliases = opts[:aliases] || []
{switches, strict} = cond do
@@ -253,7 +301,7 @@ defmodule OptionParser do
{[], false}
end
{aliases, switches, strict, all}
{aliases, switches, strict}
end
defp validate_option(value, kinds) do
@@ -296,11 +344,11 @@ defmodule OptionParser do
end
end
defp tag_option(<<?-, option :: binary>>, value, switches, _aliases) do
get_negated(option, value, switches)
defp tag_option(<<?-, option :: binary>>, switches, _aliases) do
get_negated(option, switches)
end
defp tag_option(option, _value, _switches, aliases) when is_binary(option) do
defp tag_option(option, _switches, aliases) when is_binary(option) do
opt = get_option(option)
if alias = aliases[opt] do
{:default, alias}
@@ -325,23 +373,14 @@ defmodule OptionParser do
{nil, [:invalid], value}
end
defp normalize_option({:negated, option}, nil, switches) do
kinds = List.wrap(switches[option])
cond do
:boolean in kinds ->
{option, kinds, false}
kinds == [] ->
{option, kinds, true}
true ->
{reverse_negated(option), [:invalid], nil}
defp normalize_option({:negated, option}, value, switches) do
if value do
{option, [:invalid], value}
else
{option, List.wrap(switches[option]), false}
end
end
defp normalize_option({:negated, option}, value, _switches) do
{option, [:invalid], value}
end
defp normalize_option({:default, option}, value, switches) do
{option, List.wrap(switches[option]), value}
end
@@ -396,24 +435,18 @@ defmodule OptionParser do
end
end
defp reverse_negated(negated) do
String.to_atom("no_" <> Atom.to_string(negated))
end
defp get_negated("no-" <> rest = option, value, switches) do
if negated = get_option(rest) do
option = if Keyword.has_key?(switches, negated) and value == nil do
negated
else
get_option(option)
end
{:negated, option}
else
:unknown
defp get_negated("no-" <> rest = original, switches) do
cond do
(negated = get_option(rest)) && :boolean in List.wrap(switches[negated]) ->
{:negated, negated}
option = get_option(original) ->
{:default, option}
true ->
:unknown
end
end
defp get_negated(rest, _value, _switches) do
defp get_negated(rest, _switches) do
if option = get_option(rest) do
{:default, option}
else
+14 -4
View File
@@ -159,7 +159,7 @@ defmodule Path do
def type(name) when is_list(name) or is_binary(name) do
case :os.type() do
{:win32, _} -> win32_pathtype(name)
_ -> unix_pathtype(name)
_ -> unix_pathtype(name)
end |> elem(0)
end
@@ -458,9 +458,14 @@ defmodule Path do
defp maybe_remove_dirsep(name, _), do:
:lists.reverse(name)
@doc """
Returns a list with the path split by the path separator.
If an empty string is given, returns the root path.
@doc ~S"""
Splits the path into a list at the path separator.
If an empty string is given, returns an empty list.
On Windows, path is split on both "\" and "/" separators
and the driver letter, if there is one, is always returned
in lowercase.
## Examples
@@ -486,6 +491,11 @@ defmodule Path do
defmodule Wildcard do
@moduledoc false
def read_link_info(file) do
call({:read_link_info, file})
end
# For compatibility with buggy Erlang 17.1.
def read_file_info(file) do
call({:read_link_info, file})
end
+18
View File
@@ -5,6 +5,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#open_port-2.
Inlined by the compiler.
"""
def open(name, settings) do
:erlang.open_port(name, settings)
@@ -12,6 +14,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_close-1.
Inlined by the compiler.
"""
def close(port) do
:erlang.port_close(port)
@@ -19,6 +23,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_command-2.
Inlined by the compiler.
"""
def command(port, data, options \\ []) do
:erlang.port_command(port, data, options)
@@ -26,6 +32,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_connect-2.
Inlined by the compiler.
"""
def connect(port, pid) do
:erlang.port_connect(port, pid)
@@ -33,6 +41,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_control-3.
Inlined by the compiler.
"""
def control(port, operation, data) do
:erlang.port_control(port, operation, data)
@@ -40,6 +50,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_call-3.
Inlined by the compiler.
"""
def call(port, operation, data) do
:erlang.port_call(port, operation, data)
@@ -47,6 +59,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_info-1.
Inlined by the compiler.
"""
def info(port) do
:erlang.port_info(port)
@@ -54,6 +68,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#port_info-2.
Inlined by the compiler.
"""
def info(port, item) do
:erlang.port_info(port, item)
@@ -61,6 +77,8 @@ defmodule Port do
@doc """
See http://www.erlang.org/doc/man/erlang.html#ports-0.
Inlined by the compiler.
"""
def list do
:erlang.ports
+23 -23
View File
@@ -34,7 +34,7 @@ defmodule Process do
end
@doc """
Returns the value for the given key.
Returns the value for the given `key`.
"""
@spec get(term) :: term
@spec get(term, default :: term) :: term
@@ -64,7 +64,7 @@ defmodule Process do
end
@doc """
Deletes the given key from the dictionary.
Deletes the given `key` from the dictionary.
"""
@spec delete(term) :: term | nil
def delete(key) do
@@ -79,11 +79,11 @@ defmodule Process do
1. If pid is not trapping exits, pid will exit with the given reason.
2. If pid is trapping exits, the exit signal is transformed into a message
{:EXIT, from, reason} and delivered to the message queue of pid.
`{:EXIT, from, reason}` and delivered to the message queue of pid.
3. If reason is the atom `:normal`, pid will not exit. If it is trapping
exits, the exit signal is transformed into a message {:EXIT, from,
:normal} and delivered to its message queue.
exits, the exit signal is transformed into a message `{:EXIT, from,
:normal}` and delivered to its message queue.
4. If reason is the atom `:kill`, that is if `exit(pid, :kill)` is called,
an untrappable exit signal is sent to pid which will unconditionally
@@ -129,10 +129,10 @@ defmodule Process do
end
@doc """
Sends `msg` to `dest` after `time` millisecons.
Sends `msg` to `dest` after `time` milliseconds.
If `dest` is a pid, it has to be a pid of a local process, dead or alive.
If `dest` is an atom, it is supposed to be the name of a registered process
If `dest` is a pid, it must be the pid of a local process, dead or alive.
If `dest` is an atom, it must be the name of a registered process
which is looked up at the time of delivery. No error is given if the name does
not refer to a process.
@@ -208,8 +208,8 @@ defmodule Process do
end
@doc """
If monitor_ref is a reference which the calling process
obtained by calling monitor/1, this monitoring is turned off.
If `monitor_ref` is a reference which the calling process
obtained by calling `monitor/1`, this monitoring is turned off.
If the monitoring is already turned off, nothing happens.
See http://www.erlang.org/doc/man/erlang.html#demonitor-2 for more info.
@@ -227,7 +227,7 @@ defmodule Process do
processes currently existing on the local node.
Note that a process that is exiting, exists but is not alive, i.e.,
alive?/1 will return false for a process that is exiting,
`alive?/1` will return `false` for a process that is exiting,
but its process identifier will be part of the result returned.
See http://www.erlang.org/doc/man/erlang.html#processes-0 for more info.
@@ -252,7 +252,7 @@ defmodule Process do
@doc """
Removes the link, if there is one, between the calling process and
the process or port referred to by `pid`. Returns true and does not
the process or port referred to by `pid`. Returns `true` and does not
fail, even if there is no link or `id` does not exist
See http://www.erlang.org/doc/man/erlang.html#unlink-1 for more info.
@@ -265,13 +265,13 @@ defmodule Process do
end
@doc """
Associates the name with a pid or a port identifier. name, which must
Associates the name with a pid or a port identifier. `name`, which must
be an atom, can be used instead of the pid / port identifier with the
`Kernel.send/2` function.
`Process.register/2` will fail with `ArgumentError` if the pid supplied
is no longer alive, (check with `alive?/1`) or if the name is
already registered (check with `registered?/1`).
already registered (check with `whereis/1`).
"""
@spec register(pid | port, atom) :: true
def register(pid, name) when not name in [nil, false, true] do
@@ -290,7 +290,7 @@ defmodule Process do
@doc """
Returns the pid or port identifier with the registered name.
Returns nil if the name is not registered.
Returns `nil` if the name is not registered.
See http://www.erlang.org/doc/man/erlang.html#whereis-1 for more info.
"""
@@ -309,7 +309,7 @@ defmodule Process do
@doc """
Sets the group leader of `pid` to `leader`. Typically, this is used when a processes
started from a certain shell should have another group leader than `:init`.
started from a certain shell should have a group leader other than `:init`.
"""
@spec group_leader(pid, leader :: pid) :: true
def group_leader(pid, leader) do
@@ -317,7 +317,7 @@ defmodule Process do
end
@doc """
Returns a list of names which have been registered using register/2.
Returns a list of names which have been registered using `register/2`.
"""
@spec registered :: [atom]
def registered do
@@ -339,9 +339,9 @@ defmodule Process do
end
@doc """
Sets certain flags for the process Pid, in the same manner as flag/2.
Returns the old value of the flag. The allowed values for Flag are
only a subset of those allowed in flag/2, namely: save_calls.
Sets certain flags for the process `pid`, in the same manner as `flag/2`.
Returns the old value of the flag. The allowed values for `flag` are
only a subset of those allowed in `flag/2`, namely: `save_calls`.
See http://www.erlang.org/doc/man/erlang.html#process_flag-3 for more info.
"""
@@ -351,7 +351,7 @@ defmodule Process do
end
@doc """
Returns information about the process identified by pid or nil if the process
Returns information about the process identified by `pid` or `nil` if the process
is not alive.
Use this only for debugging information.
@@ -363,8 +363,8 @@ defmodule Process do
end
@doc """
Returns information about the process identified by pid
or nil if the process is not alive.
Returns information about the process identified by `pid`
or `nil` if the process is not alive.
See http://www.erlang.org/doc/man/erlang.html#process_info-2 for more info.
"""
+33 -14
View File
@@ -426,7 +426,7 @@ defmodule Protocol do
defp after_defprotocol do
quote bind_quoted: [builtin: builtin] do
@spec impl_for(term) :: module | nil
@spec impl_for(term) :: atom() | nil
Kernel.def impl_for(data)
# Define the implementation for structs.
@@ -449,7 +449,7 @@ defmodule Protocol do
end
end
@spec impl_for!(term) :: module | no_return
@spec impl_for!(term) :: atom() | no_return()
Kernel.def impl_for!(data) do
impl_for(data) || raise(Protocol.UndefinedError, protocol: __MODULE__, value: data)
end
@@ -494,12 +494,19 @@ defmodule Protocol do
@protocol [fallback_to_any: !!@fallback_to_any, consolidated: false]
@doc false
@spec __protocol__(atom) :: term
Kernel.def __protocol__(:name), do: __MODULE__
@spec __protocol__(:name) :: __MODULE__
@spec __protocol__(:functions) :: unquote(Protocol.__functions_spec__(@functions))
Kernel.def __protocol__(:name), do: __MODULE__
Kernel.def __protocol__(:functions), do: unquote(:lists.sort(@functions))
end
end
@doc false
def __functions_spec__([]),
do: []
def __functions_spec__([h|t]),
do: [:lists.foldl(&{:|, [], [&1, &2]}, h, t), quote(do: ...)]
@doc false
def __impl__(protocol, opts) do
do_defimpl(protocol, :lists.keysort(1, opts))
@@ -510,6 +517,20 @@ defmodule Protocol do
end
defp do_defimpl(protocol, [do: block, for: for]) do
# Unquote the implementation just later
# when all variables will already be injected
# into the module body.
__impl__ =
quote unquote: false do
@doc false
@spec __impl__(:for) :: unquote(for)
@spec __impl__(:target) :: __MODULE__
@spec __impl__(:protocol) :: unquote(protocol)
def __impl__(:for), do: unquote(for)
def __impl__(:target), do: __MODULE__
def __impl__(:protocol), do: unquote(protocol)
end
quote do
protocol = unquote(protocol)
for = unquote(for)
@@ -527,11 +548,7 @@ defmodule Protocol do
Module.register_attribute(__MODULE__, :impl, persist: true)
@impl [protocol: @protocol, for: @for]
@doc false
@spec __impl__(atom) :: term
def __impl__(:target), do: __MODULE__
def __impl__(:protocol), do: @protocol
def __impl__(:for), do: @for
unquote(__impl__)
end
end
end
@@ -576,7 +593,9 @@ defmodule Protocol do
@impl [protocol: unquote(protocol), for: unquote(for)]
@doc false
@spec __impl__(atom) :: term
@spec __impl__(:target) :: unquote(impl)
@spec __impl__(:protocol) :: unquote(protocol)
@spec __impl__(:for) :: unquote(for)
def __impl__(:target), do: unquote(impl)
def __impl__(:protocol), do: unquote(protocol)
def __impl__(:for), do: unquote(for)
@@ -592,10 +611,10 @@ defmodule Protocol do
found =
for {:spec, expr, caller} <- specs,
Kernel.Typespec.spec_to_signature(expr) == signature do
Kernel.Typespec.define_spec(:callback, expr, caller)
true
end
Kernel.Typespec.spec_to_signature(expr) == signature do
Kernel.Typespec.define_spec(:callback, expr, caller)
true
end
found != []
end
+3 -2
View File
@@ -2,7 +2,7 @@ defmodule Range do
@moduledoc """
Defines a Range.
A Range are represented internally as a struct. However,
A Range is represented internally as a struct. However,
the most common form of creating and matching on ranges
is via the `../2` macro, auto-imported from Kernel:
@@ -18,7 +18,8 @@ defmodule Range do
defstruct first: nil, last: nil
@type t(first, last) :: %{__struct__: Range, first: first, last: last}
@type t :: %Range{}
@type t(first, last) :: %Range{first: first, last: last}
@doc """
Creates a new range.
+47 -4
View File
@@ -20,6 +20,20 @@ defmodule Record do
The macros `defrecord/3` and `defrecordp/3` can be used to create
records while `extract/2` can be used to extract records from Erlang
files.
## Types
Types can be defined for tuples with the `record/2` macro (only available
in typespecs). Like with the generated record macros it will expand to
a tuple.
defmodule MyModule do
require Record
Record.defrecord :user name: "José", age: 25
@type user :: record(:user, name: String.t, age: integer)
# expands to: `@type user :: {:user, String.t, integer}`
end
"""
@doc """
@@ -111,6 +125,7 @@ defmodule Record do
## Examples
defmodule User do
require Record
Record.defrecord :user, [name: "José", age: "25"]
end
@@ -118,8 +133,8 @@ defmodule Record do
arities will be defined to manipulate the underlying record:
# To create records
user() #=> {:user, "José", 25}
user(age: 26) #=> {:user, "José", 26}
record = user() #=> {:user, "José", 25}
record = user(age: 26) #=> {:user, "José", 26}
# To get a field from the record
user(record, :name) #=> "José"
@@ -127,10 +142,14 @@ defmodule Record do
# To update the record
user(record, age: 26) #=> {:user, "José", 26}
# Convert a record to a keyword list
user(record) #=> [name: "José", age: 26]
By default, Elixir uses the record name as the first element of
the tuple (the tag). But it can be changed to something else:
defmodule User do
require Record
Record.defrecord :user, User, name: nil
end
@@ -190,8 +209,15 @@ defmodule Record do
Keyword.keyword?(args) ->
create(atom, fields, args, caller)
true ->
msg = "expected arguments to be a compile time atom or keywords, got: #{Macro.to_string args}"
raise ArgumentError, msg
case Macro.expand(args, caller) do
{:{}, _, [^atom|list]} when length(list) == length(fields) ->
record = List.to_tuple([atom|list])
Macro.escape(Record.__keyword__(atom, fields, record))
{^atom, arg} when length(fields) == 1 ->
Macro.escape(Record.__keyword__(atom, fields, {atom, arg}))
_ ->
quote do: Record.__keyword__(unquote(atom), unquote(fields), unquote(args))
end
end
end
@@ -279,4 +305,21 @@ defmodule Record do
defp find_index([{k, _}|_], k, i), do: i + 2
defp find_index([{_, _}|t], k, i), do: find_index(t, k, i + 1)
defp find_index([], _k, _i), do: nil
# Returns a keyword list of the record
@doc false
def __keyword__(atom, fields, record) do
if record?(record, atom) do
[_tag|values] = Tuple.to_list(record)
join_keyword(fields, values, [])
else
msg = "expected argument to be a literal atom, literal keyword or a #{inspect atom} record, got runtime: #{inspect record}"
raise ArgumentError, msg
end
end
defp join_keyword([{field, _default}|fields], [value|values], acc),
do: join_keyword(fields, values, [{field, value}| acc])
defp join_keyword([], [], acc),
do: :lists.reverse(acc)
end
+19 -21
View File
@@ -22,8 +22,9 @@ defmodule Regex do
The modifiers available when creating a Regex are:
* `unicode` (u) - enables unicode specific patterns like `\p`; it expects
valid unicode strings to be given on match
* `unicode` (u) - enables unicode specific patterns like `\p` and changes
modifiers like `\w`, `\W`, `\s` and friends to also match on unicode.
It expects valid unicode strings to be given on match
* `caseless` (i) - add case insensitivity
@@ -75,7 +76,9 @@ defmodule Regex do
"""
defstruct re_pattern: nil :: term, source: "" :: binary, opts: "" :: binary
defstruct re_pattern: nil, source: "", opts: ""
@opaque t :: %__MODULE__{re_pattern: term, source: binary, opts: binary}
defmodule CompileError do
defexception message: "regex could not be compiled"
@@ -104,7 +107,7 @@ defmodule Regex do
def compile(source, options \\ "")
def compile(source, options) when is_binary(options) do
case translate_options(options) do
case translate_options(options, []) do
{:error, rest} ->
{:error, {:invalid_option, rest}}
@@ -471,12 +474,12 @@ defmodule Regex do
defp apply_list(whole, string, pos, replacement, [[{mpos, _} | _] | _] = list) when mpos > pos do
length = mpos - pos
<<untouched :: [size(length), binary], rest :: binary>> = string
<<untouched :: binary-size(length), rest :: binary>> = string
[untouched | apply_list(whole, rest, mpos, replacement, list)]
end
defp apply_list(whole, string, pos, replacement, [[{mpos, length} | _] = head | tail]) when mpos == pos do
<<_ :: [size(length), binary], rest :: binary>> = string
<<_ :: size(length)-binary, rest :: binary>> = string
new_data = apply_replace(whole, replacement, head)
[new_data | apply_list(whole, rest, pos + length, replacement, tail)]
end
@@ -509,7 +512,7 @@ defmodule Regex do
end
defp get_index(string, {pos, len}) do
<<_ :: [size(pos), binary], res :: [size(len), binary], _ :: binary>> = string
<<_ :: size(pos)-binary, res :: size(len)-binary, _ :: binary>> = string
res
end
@@ -559,18 +562,13 @@ defmodule Regex do
# Private Helpers
defp translate_options(<<?g, t :: binary>>) do
IO.write :stderr, "The /g flag for regular expressions is no longer needed\n#{Exception.format_stacktrace}"
translate_options(t)
end
defp translate_options(<<?u, t :: binary>>), do: [:unicode|translate_options(t)]
defp translate_options(<<?i, t :: binary>>), do: [:caseless|translate_options(t)]
defp translate_options(<<?x, t :: binary>>), do: [:extended|translate_options(t)]
defp translate_options(<<?f, t :: binary>>), do: [:firstline|translate_options(t)]
defp translate_options(<<?r, t :: binary>>), do: [:ungreedy|translate_options(t)]
defp translate_options(<<?s, t :: binary>>), do: [:dotall, {:newline, :anycrlf}|translate_options(t)]
defp translate_options(<<?m, t :: binary>>), do: [:multiline|translate_options(t)]
defp translate_options(<<>>), do: []
defp translate_options(rest), do: {:error, rest}
defp translate_options(<<?u, t :: binary>>, acc), do: translate_options(t, [:unicode, :ucp|acc])
defp translate_options(<<?i, t :: binary>>, acc), do: translate_options(t, [:caseless|acc])
defp translate_options(<<?x, t :: binary>>, acc), do: translate_options(t, [:extended|acc])
defp translate_options(<<?f, t :: binary>>, acc), do: translate_options(t, [:firstline|acc])
defp translate_options(<<?r, t :: binary>>, acc), do: translate_options(t, [:ungreedy|acc])
defp translate_options(<<?s, t :: binary>>, acc), do: translate_options(t, [:dotall, {:newline, :anycrlf}|acc])
defp translate_options(<<?m, t :: binary>>, acc), do: translate_options(t, [:multiline|acc])
defp translate_options(<<>>, acc), do: acc
defp translate_options(rest, _acc), do: {:error, rest}
end
+23 -17
View File
@@ -91,10 +91,11 @@ defmodule Stream do
defstruct enum: nil, funs: [], accs: [], done: nil
@type acc :: any
@type acc :: any
@type element :: any
@type index :: non_neg_integer
@type index :: non_neg_integer
@type default :: any
@type t :: %__MODULE__{}
# Require Stream.Reducers and its callbacks
require Stream.Reducers, as: R
@@ -427,7 +428,7 @@ defmodule Stream do
"""
@spec run(Enumerable.t) :: :ok
def run(stream) do
Enumerable.reduce(stream, {:cont, nil}, fn(_, _) -> {:cont, nil} end)
_ = Enumerable.reduce(stream, {:cont, nil}, fn(_, _) -> {:cont, nil} end)
:ok
end
@@ -616,12 +617,14 @@ defmodule Stream do
{[], user_acc} ->
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
{list, user_acc} when is_list(list) ->
do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.List.reduce(list, &1, fun))
do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner,
&Enumerable.List.reduce(list, &1, fun))
{:halt, _user_acc} ->
next.({:halt, next_acc})
{:halted, elem(inner_acc, 1)}
{other, user_acc} ->
do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.reduce(other, &1, inner))
do_enum_transform(user_acc, user, fun, next_acc, next, inner_acc, inner,
&Enumerable.reduce(other, &1, inner))
end
{reason, _} ->
{reason, elem(inner_acc, 1)}
@@ -633,8 +636,9 @@ defmodule Stream do
reduce.(inner_acc)
catch
kind, reason ->
stacktrace = System.stacktrace
next.({:halt, next_acc})
:erlang.raise(kind, reason, :erlang.get_stacktrace)
:erlang.raise(kind, reason, stacktrace)
else
{:done, acc} ->
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
@@ -646,29 +650,31 @@ defmodule Stream do
end
end
defp do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, reduce) do
defp do_enum_transform(user_acc, user, fun, next_acc, next, {op, inner_acc}, inner, reduce) do
try do
reduce.(inner_acc)
reduce.({op, [:outer|inner_acc]})
catch
{:stream_transform, h} ->
next.({:halt, next_acc})
{:halted, h}
kind, reason ->
stacktrace = System.stacktrace
next.({:halt, next_acc})
:erlang.raise(kind, reason, stacktrace)
else
{_, acc} ->
{:halted, [:outer|acc]} ->
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
{:suspended, acc, c} ->
{:suspended, acc, &do_other_transform(user_acc, user, fun, next_acc, next, &1, inner, c)}
{:halted, [:inner|acc]} ->
next.({:halt, next_acc})
{:halted, acc}
{:done, [_|acc]} ->
do_transform(user_acc, user, fun, next_acc, next, {:cont, acc}, inner)
{:suspended, [_|acc], c} ->
{:suspended, acc, &do_enum_transform(user_acc, user, fun, next_acc, next, &1, inner, c)}
end
end
defp do_transform_each(x, acc, f) do
defp do_transform_each(x, [:outer|acc], f) do
case f.(x, acc) do
{:halt, h} -> throw({:stream_transform, h})
{_, _} = o -> o
{:halt, res} -> {:halt, [:inner|res]}
{op, res} -> {op, [:outer|res]}
end
end
+29 -1
View File
@@ -231,7 +231,14 @@ defmodule String do
def split("", _pattern, _options), do: [""]
def split(binary, "", options), do: split(binary, ~r""u, options)
def split(binary, "", options) do
index =
case Keyword.get(options, :parts, :infinity) do
num when is_number(num) and num > 0 -> num
_ -> 0
end
split_codepoints(binary, index - 1, Keyword.get(options, :trim, false))
end
def split(binary, pattern, options) do
if Regex.regex?(pattern) do
@@ -253,6 +260,14 @@ defmodule String do
end
end
defp split_codepoints(binary, 0, _trim), do: [binary]
defp split_codepoints(<<h :: utf8, t :: binary>>, count, trim),
do: [<<h :: utf8>>|split_codepoints(t, count - 1, trim)]
defp split_codepoints(<<h, t :: binary>>, count, trim),
do: [<<h>>|split_codepoints(t, count - 1, trim)]
defp split_codepoints(<<>>, _, true), do: []
defp split_codepoints(<<>>, _, false), do: [""]
defp split_parts("", _pattern, _num), do: [""]
defp split_parts(binary, pattern, num), do: split_parts(binary, pattern, num, [])
defp split_parts("", _pattern, _num, parts), do: Enum.reverse([""|parts])
@@ -974,8 +989,13 @@ defmodule String do
@doc """
Returns a substring starting at the offset given by the first, and
a length given by the second.
If the offset is greater than string length, then it returns `""`.
Remember this function works with unicode codepoints and consider
the slices to represent codepoints offsets. If you want to split
on raw bytes, check `Kernel.binary_part/3` instead.
## Examples
iex> String.slice("elixir", 1, 3)
@@ -1028,6 +1048,14 @@ defmodule String do
If the start of the range is not a valid offset for the given
string or if the range is in reverse order, returns `""`.
If the start or end of the range are negative, the whole string
is traversed first in order to convert the negative indexes into
positive ones.
Remember this function works with unicode codepoints and consider
the slices to represent codepoints offsets. If you want to split
on raw bytes, check `Kernel.binary_part/3` instead.
## Examples
iex> String.slice("elixir", 1..3)
+2 -2
View File
@@ -199,7 +199,7 @@ defmodule StringIO do
end
defp do_get_chars(input, :latin1, n) do
<<chars :: [binary, size(n)], rest :: binary>> = input
<<chars :: binary-size(n), rest :: binary>> = input
{chars, rest}
end
@@ -209,7 +209,7 @@ defmodule StringIO do
{buf_count, split_pos} when buf_count < n or split_pos == :none ->
{input, ""}
{_buf_count, split_pos} ->
<<chars :: [binary, size(split_pos)], rest :: binary>> = input
<<chars :: binary-size(split_pos), rest :: binary>> = input
{chars, rest}
end
catch
+121 -19
View File
@@ -202,7 +202,7 @@ defmodule System do
end
@doc """
Register a program exit handler function.
Registers a program exit handler function.
Registers a function that will be invoked
at the end of program execution. Useful for
@@ -215,19 +215,7 @@ defmodule System do
:elixir_code_server.cast {:at_exit, fun}
end
@doc """
Execute a system command.
Executes `command` in a command shell of the target OS,
captures the standard output of the command and returns
the result as a binary.
If `command` is a char list, a char list is returned.
Otherwise a string, correctly encoded in UTF-8, is expected.
"""
@spec cmd(String.t) :: String.t
@spec cmd(char_list) :: char_list
@doc false
def cmd(command) when is_list(command) do
:os.cmd(command)
end
@@ -237,21 +225,18 @@ defmodule System do
end
@doc """
Locate an executable on the system.
Locates an executable on the system.
This function looks up an executable program given
its name using the environment variable PATH on Unix
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 `program` is a char list, a char list is returned.
Returns a binary otherwise.
"""
@spec find_executable(binary) :: binary | nil
@spec find_executable(char_list) :: char_list | nil
def find_executable(program) when is_list(program) do
IO.puts :stderr, "warning: passing a char_list to System.find_executable/1 is deprecated\n#{Exception.format_stacktrace}"
:os.find_executable(program) || nil
end
@@ -387,4 +372,121 @@ defmodule System do
def halt(status) when is_binary(status) do
:erlang.halt(String.to_char_list(status))
end
@doc """
Executes the given `command` with `args`.
`command` is expected to be an executable available in PATH
unless an absolute path is given.
`args` must be a list of strings and they are not expanded
in any way. For example, this means wildcard expansion will
not happen unless `Path.wildcard/2` is used. On Windows though,
wildcard expansion is up to the program.
A set of options are also supported and described below.
## Options
* `:into` - injects the result into the given collectable, defaults to `""`
* `:cd` - the directory to run the command on
* `:env` - an enumerable of tuples containing environment key-value as binary
* `:arg0` - set the command arg0
* `:stderr_to_stdout` - redirects stderr to stdout when true
* `:parallelism` - when true, the VM will schedule port tasks to improve
parallelism in the system. If set to false, the VM will try to perform
commands immediately, improving latency at the expense of parallelism.
The default can be set on system startup by passing the "+spp" argument
to `--erl`.
## Error reasons
If invalid arguments are given, `ArgumentError` is raised by
`System.cmd/3`. `System.cmd/3` also expect a strict set of
options and will raise if unknown or invalid options are given.
Furthermore, `System.cmd/3` may fail with one of the POSIX reasons
detailed below:
* `:system_limit` - all available ports in the Erlang emulator are in use
* `:enomem` - there was not enough memory to create the port
* `:eagain` - there are no more available operating system processes
* `:enametoolong` - the external command given was too long.
* `:emfile` - there are no more available file descriptors
(for the operating system process that the Erlang emulator runs in)
* `:enfile` - the file table is full (for the entire operating system)
* `:eacces` - the command does not point to an executable file
* `:enoent` - the command does not point to an existing file
## Shell commands
If you desire to execute a trusted command inside a shell, with pipes,
redirecting and so on, please check
[Erlang's :os.cmd/1 function](http://www.erlang.org/doc/man/os.html#cmd-1).
"""
@spec cmd(binary, [binary], Keyword.t) ::
{:ok, Collectable.t, exit_status :: non_neg_integer} | {:error, atom}
def cmd(command, args, opts \\ []) when is_binary(command) and is_list(args) do
command = String.to_char_list(command)
command =
if Path.type(command) == :absolute do
command
else
:os.find_executable(command) || command
end
{into, opts} = cmd_opts(opts, [:use_stdio, :exit_status, :binary, :hide, args: args], "")
{initial, fun} = Collectable.into(into)
do_cmd Port.open({:spawn_executable, command}, opts), initial, fun
end
defp do_cmd(port, acc, fun) do
receive do
{^port, {:data, data}} ->
do_cmd(port, fun.(acc, {:cont, data}), fun)
{^port, {:exit_status, status}} ->
{fun.(acc, :done), status}
end
end
defp cmd_opts([{:into, any}|t], opts, _into),
do: cmd_opts(t, opts, any)
defp cmd_opts([{:cd, bin}|t], opts, into) when is_binary(bin),
do: cmd_opts(t, [{:cd, bin}|opts], into)
defp cmd_opts([{:arg0, bin}|t], opts, into) when is_binary(bin),
do: cmd_opts(t, [{:arg0, bin}|opts], into)
defp cmd_opts([{:stderr_to_stdout, bool}|t], opts, into) when is_boolean(bool),
do: cmd_opts(t, [:stderr_to_stdout|opts], into)
defp cmd_opts([{:parallelism, bool}|t], opts, into) when is_boolean(bool),
do: cmd_opts(t, [{:parallelism, bool}|opts], into)
defp cmd_opts([{:env, enum}|t], opts, into),
do: cmd_opts(t, [{:env, validate_env(enum)}|opts], into)
defp cmd_opts([{key, val}|_], _opts, _into),
do: raise(ArgumentError, "invalid option #{inspect key} with value #{inspect val}")
defp cmd_opts([], opts, into),
do: {into, opts}
defp validate_env(enum) do
Enum.map enum, fn
{k, v} ->
{String.to_char_list(k), String.to_char_list(v)}
other ->
raise ArgumentError, "invalid environment key-value #{inspect other}"
end
end
end
+3 -1
View File
@@ -16,7 +16,7 @@ defmodule Task do
They are implemented by spawning a process that sends a message
to the caller once the given computation is performed.
Besides `async/1` and `await/2`, tasks can also be
Besides `async/1` and `await/2`, tasks can also be
started as part of supervision trees and dynamically spawned
in remote nodes. We will explore all three scenarios next.
@@ -93,6 +93,8 @@ defmodule Task do
"""
defstruct pid: nil, ref: nil
@type t :: %__MODULE__{}
@doc """
Starts a task as part of a supervision tree.
"""
+6 -6
View File
@@ -89,12 +89,12 @@ defmodule Task.Supervised do
{fun, args} = get_running(mfa)
:error_logger.format(
"** Task ~p terminating~n" <>
"** Started from ~p~n" <>
"** When function == ~p~n" <>
"** arguments == ~p~n" <>
"** Reason for termination == ~n" <>
"** ~p~n", [self, get_from(info), fun, args, reason])
'** Task ~p terminating~n' ++
'** Started from ~p~n' ++
'** When function == ~p~n' ++
'** arguments == ~p~n' ++
'** Reason for termination == ~n' ++
'** ~p~n', [self, get_from(info), fun, args, reason])
exit(reason)
end
+2
View File
@@ -7,6 +7,8 @@ defmodule URI do
fragment: nil, authority: nil,
userinfo: nil, host: nil, port: nil
@type t :: %__MODULE__{}
import Bitwise
@ports %{
+20 -6
View File
@@ -58,14 +58,26 @@ defmodule Version do
@type version :: String.t | t
@type requirement :: String.t | Version.Requirement.t
@type matchable :: {major :: String.t | non_neg_integer,
minor :: non_neg_integer | nil,
patch :: non_neg_integer | nil,
pre :: [String.t]}
@type major :: String.t | non_neg_integer
@type minor :: non_neg_integer | nil
@type patch :: non_neg_integer | nil
@type pre :: [String.t]
@type build :: String.t | nil
@type matchable :: {major :: major,
minor :: minor,
patch :: patch,
pre :: pre}
@type t :: %__MODULE__{
major: major,
minor: minor,
patch: patch,
pre: pre,
build: build}
defmodule Requirement do
@moduledoc false
defstruct [:source, :matchspec]
@type t :: %__MODULE__{}
end
defmodule InvalidRequirementError do
@@ -157,7 +169,8 @@ defmodule Version do
## Examples
iex> Version.parse("2.0.1-alpha1") |> elem(1)
iex> {:ok, version} = Version.parse("2.0.1-alpha1")
iex> version
#Version<2.0.1-alpha1>
iex> Version.parse("2.0-alpha1")
@@ -181,7 +194,8 @@ defmodule Version do
## Examples
iex> Version.parse_requirement("== 2.0.1") |> elem(1)
iex> {:ok, req} = Version.parse_requirement("== 2.0.1")
iex> req
#Version.Requirement<== 2.0.1>
iex> Version.parse_requirement("== == 2.0.1")
+14 -7
View File
@@ -28,8 +28,10 @@ start(_Type, _Args) ->
error -> [binary]
end,
io:setopts(standard_io, Opts),
io:setopts(standard_error, [{unicode,true}]),
ok = io:setopts(standard_io, Opts),
%% Must use undocument {unicode, true} to set unicode on standard_error, more
%% info: http://erlang.org/pipermail/erlang-bugs/2014-April/004310.html
ok = io:setopts(standard_error, [{unicode,true}]),
case file:native_name_encoding() of
latin1 ->
io:format(standard_error,
@@ -51,13 +53,13 @@ config_change(_Changed, _New, _Remove) ->
%% escript entry point
main(Args) ->
application:start(?MODULE),
ok = application:ensure_started(?MODULE),
'Elixir.Kernel.CLI':main(Args).
%% Boot and process given options. Invoked by Elixir's script.
start_cli() ->
application:start(?MODULE),
ok = application:ensure_started(?MODULE),
'Elixir.Kernel.CLI':main(init:get_plain_arguments()).
%% EVAL HOOKS
@@ -153,14 +155,19 @@ eval_forms(Tree, Binding, Env, Scope) ->
{atom, _, Atom} ->
{Atom, Binding, NewEnv, NewScope};
_ ->
{value, Value, NewBinding} = erl_eval(Erl, ParsedBinding),
{value, Value, NewBinding} = erl_eval(Erl, ParsedBinding, Env),
{Value, elixir_scope:dump_binding(NewBinding, NewScope), NewEnv, NewScope}
end.
erl_eval(Erl, ParsedBinding) ->
erl_eval(Erl, ParsedBinding, E) ->
case erl_eval:check_command([Erl], ParsedBinding) of
ok -> ok;
{error, Desc} -> elixir_errors:handle_file_error(?m(E, file), Desc)
end,
% Below must be all one line for locations to be the same when the stacktrace
% needs to be extended to the full stacktrace.
try erl_eval:expr(Erl, ParsedBinding) catch Class:Exception -> erlang:raise(Class, Exception, get_stacktrace()) end.
try erl_eval:expr(Erl, ParsedBinding, none, none, none) catch Class:Exception -> erlang:raise(Class, Exception, get_stacktrace()) end.
get_stacktrace() ->
Stacktrace = erlang:get_stacktrace(),
+100 -67
View File
@@ -35,70 +35,106 @@ expand_bitstr(Fun, [H|T], Acc, E) ->
%% Expand bit info
expand_bit_info(Meta, Info, E) when is_list(Info) ->
expand_bit_info(Meta, Info, default, [], E);
expand_bit_info(Meta, Info, E) ->
expand_bit_info(Meta, [Info], E).
expand_bit_info(Meta, unpack_bit_info(Meta, Info, E), default, [], E).
expand_bit_info(Meta, [{Expr, ExprMeta, Args}|T], Size, Types, E) when is_atom(Expr) ->
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
case expand_bit_type_or_size(Expr, ListArgs) of
type ->
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
expand_bit_info(Meta, T, Size, [{Expr, [], EArgs}|Types], EE);
size ->
case Size of
default -> ok;
_ -> elixir_errors:compile_error(Meta, ?m(E, file), "duplicated size definition in bitstring")
expand_bit_info(Meta, [{size, _, [_]=Args}|T], Size, Types, E) ->
case Size of
default ->
{[EArg], EE} = elixir_exp:expand_args(Args, E),
case EArg of
{Var, _, Context} when is_atom(Var) and is_atom(Context) ->
ok;
_ when is_integer(EArg) ->
ok;
_ ->
elixir_errors:compile_error(Meta, ?m(E, file),
"size in bitstring expects an integer or a variable as argument, got: ~ts",
['Elixir.Macro':to_string(EArg)])
end,
{EArgs, EE} = elixir_exp:expand_args(ListArgs, E),
expand_bit_info(Meta, T, {Expr, [], EArgs}, Types, EE);
none ->
handle_unknown_bit_info(Meta, {Expr, ExprMeta, ListArgs}, T, Size, Types, E)
expand_bit_info(Meta, T, {size, [], [EArg]}, Types, EE);
_ ->
elixir_errors:compile_error(Meta, ?m(E, file),
"duplicated size definition in bitstring")
end;
expand_bit_info(Meta, [Int|T], Size, Types, E) when is_integer(Int) ->
expand_bit_info(Meta, [{size, [], [Int]}|T], Size, Types, E);
expand_bit_info(Meta, [{Expr, ExprMeta, Args}|T], Size, Types, E) when is_atom(Expr) ->
case expand_bit_type(Expr, Args) of
type ->
{EArgs, EE} = elixir_exp:expand_args(Args, E),
validate_bit_type_args(Meta, Expr, EArgs, EE),
expand_bit_info(Meta, T, Size, [{Expr, [], EArgs}|Types], EE);
none ->
handle_unknown_bit_info(Meta, {Expr, ExprMeta, Args}, T, Size, Types, E)
end;
expand_bit_info(Meta, [Expr|_], _Size, _Types, E) ->
elixir_errors:compile_error(Meta, ?m(E, file),
"unknown bitstring specifier ~ts", ['Elixir.Kernel':inspect(Expr)]);
expand_bit_info(_Meta, [], Size, Types, _) ->
case Size of
expand_bit_info(Meta, [], Size, Types, _) ->
[H|T] = case Size of
default -> lists:reverse(Types);
_ -> [Size|lists:reverse(Types)]
end.
_ -> lists:reverse(Types, [Size])
end,
lists:foldl(fun(I, Acc) -> {'-', Meta, [Acc, I]} end, H, T).
expand_bit_type_or_size(binary, []) -> type;
expand_bit_type_or_size(integer, []) -> type;
expand_bit_type_or_size(float, []) -> type;
expand_bit_type_or_size(bitstring, []) -> type;
expand_bit_type_or_size(bytes, []) -> type;
expand_bit_type_or_size(bits, []) -> type;
expand_bit_type_or_size(utf8, []) -> type;
expand_bit_type_or_size(utf16, []) -> type;
expand_bit_type_or_size(utf32, []) -> type;
expand_bit_type_or_size(signed, []) -> type;
expand_bit_type_or_size(unsigned, []) -> type;
expand_bit_type_or_size(big, []) -> type;
expand_bit_type_or_size(little, []) -> type;
expand_bit_type_or_size(native, []) -> type;
expand_bit_type_or_size(unit, [_]) -> type;
expand_bit_type_or_size(size, [_]) -> size;
expand_bit_type_or_size(_, _) -> none.
expand_bit_type(binary, []) -> type;
expand_bit_type(integer, []) -> type;
expand_bit_type(float, []) -> type;
expand_bit_type(bitstring, []) -> type;
expand_bit_type(bytes, []) -> type;
expand_bit_type(bits, []) -> type;
expand_bit_type(utf8, []) -> type;
expand_bit_type(utf16, []) -> type;
expand_bit_type(utf32, []) -> type;
expand_bit_type(signed, []) -> type;
expand_bit_type(unsigned, []) -> type;
expand_bit_type(big, []) -> type;
expand_bit_type(little, []) -> type;
expand_bit_type(native, []) -> type;
expand_bit_type(unit, [_]) -> type;
expand_bit_type(_, _) -> none.
handle_unknown_bit_info(Meta, {_, ExprMeta, _} = Expr, T, Size, Types, E) ->
case 'Elixir.Macro':expand(Expr, elixir_env:linify({?line(ExprMeta), E})) of
validate_bit_type_args(Meta, unit, [Unit], E) when not is_integer(Unit) ->
elixir_errors:compile_error(Meta, ?m(E, file),
"unit in bitstring expects an integer as argument, got: ~ts",
['Elixir.Macro':to_string(Unit)]);
validate_bit_type_args(_Meta, _Expr, _Args, _E) ->
ok.
handle_unknown_bit_info(Meta, Expr, T, Size, Types, E) ->
case 'Elixir.Macro':expand(Expr, elixir_env:linify({?line(Meta), E})) of
Expr ->
elixir_errors:compile_error(ExprMeta, ?m(E, file),
elixir_errors:compile_error(Meta, ?m(E, file),
"unknown bitstring specifier ~ts", ['Elixir.Macro':to_string(Expr)]);
Other ->
List = case is_list(Other) of true -> Other; false -> [Other] end,
expand_bit_info(Meta, List ++ T, Size, Types, E)
Info ->
expand_bit_info(Meta, unpack_bit_info(Meta, Info, E) ++ T, Size, Types, E)
end.
%% Deprecate me: we can remove unpack_bit_info/3 once deprecated.
unpack_bit_info(Meta, [H|T], _E) ->
Dashed = lists:foldl(fun(I, Acc) -> {'-', Meta, [Acc, I]} end, H, T),
unpack_bit_info(Dashed, []);
unpack_bit_info(_Meta, Expr, _E) ->
unpack_bit_info(Expr, []).
unpack_bit_info({'-', _, [H, T]}, Acc) ->
unpack_bit_info(H, unpack_bit_info(T, Acc));
unpack_bit_info({'*', _, [{'_', _, Atom}, Unit]}, Acc) when is_atom(Atom) and is_integer(Unit) ->
[{unit, [], [Unit]}|Acc];
unpack_bit_info({'*', _, [Size, Unit]}, Acc) when is_integer(Size) and is_integer(Unit) ->
[{size, [], [Size]}, {unit, [], [Unit]}|Acc];
unpack_bit_info(Size, Acc) when is_integer(Size) ->
[{size, [], [Size]}|Acc];
unpack_bit_info({Expr, Meta, Args}, Acc) when is_atom(Expr) ->
ListArgs = if is_atom(Args) -> []; is_list(Args) -> Args end,
[{Expr, Meta, ListArgs}|Acc];
unpack_bit_info(Other, Acc) ->
[Other|Acc].
%% Translation
has_size({bin, _, Elements}) ->
@@ -124,7 +160,7 @@ build_bitstr_each(_Fun, [], _Meta, S, Acc) ->
{Acc, S};
build_bitstr_each(Fun, [{'::',_,[H,V]}|T], Meta, S, Acc) ->
{Size, Types} = extract_bit_info(Meta, V, S#elixir_scope{context=nil}),
{Size, Types} = extract_bit_info(V, S#elixir_scope{context=nil}),
build_bitstr_each(Fun, T, Meta, S, Acc, H, Size, Types);
build_bitstr_each(Fun, [H|T], Meta, S, Acc) ->
@@ -201,23 +237,20 @@ unit_size([], Guess) -> Guess.
%% Extra bitstring specifiers
extract_bit_info(Meta, [{size, _, [Arg]}|T], S) ->
case elixir_translator:translate(Arg, S) of
{{Kind, _, _} = Size, _} when Kind == integer; Kind == var ->
{Size, extract_bit_type(Meta, T, S)};
_ ->
elixir_errors:compile_error(Meta, S#elixir_scope.file,
"size in bitstring expects an integer or a variable as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)])
end;
extract_bit_info(Meta, T, S) ->
{default, extract_bit_type(Meta, T, S)}.
extract_bit_info({'-', _, [L, {size, _, [Size]}]}, S) ->
{extract_bit_size(Size, S), extract_bit_type(L, [])};
extract_bit_info({size, _, [Size]}, S) ->
{extract_bit_size(Size, S), []};
extract_bit_info(L, _S) ->
{default, extract_bit_type(L, [])}.
extract_bit_type(Meta, [{unit, _, [Arg]}|T], S) when is_integer(Arg) ->
[{unit, Arg}|extract_bit_type(Meta, T, S)];
extract_bit_type(Meta, [{unit, _, [Arg]}|_], S) ->
elixir_errors:compile_error(Meta, S#elixir_scope.file,
"unit in bitstring expects an integer as argument, got: ~ts", ['Elixir.Macro':to_string(Arg)]);
extract_bit_type(Meta, [{Other, _, []}|T], S) ->
[Other|extract_bit_type(Meta, T, S)];
extract_bit_type(_Meta, [], _S) ->
[].
extract_bit_size(Size, S) ->
{TSize, _} = elixir_translator:translate(Size, S),
TSize.
extract_bit_type({'-', _, [L, R]}, Acc) ->
extract_bit_type(L, extract_bit_type(R, Acc));
extract_bit_type({unit, _, [Arg]}, Acc) ->
[{unit, Arg}|Acc];
extract_bit_type({Other, _, []}, Acc) ->
[Other|Acc].
+17 -5
View File
@@ -11,6 +11,7 @@
loaded=[],
at_exit=[],
pool={[],0},
paths={[],[]},
compiler_options=[{docs,true},{debug_info,true},{warnings_as_errors,false}],
erl_compiler_options=nil
}).
@@ -27,9 +28,13 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, ok, []).
init(ok) ->
code:ensure_loaded('Elixir.Macro.Env'),
code:ensure_loaded('Elixir.Module.LocalsTracker'),
code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
%% We attempt to load those modules here so throughout
%% the codebase we can use a code:is_loaded/1 check on
%% bootstrap instead of using the more expensive
%% code:ensure_loaded/1 multiple times.
_ = code:ensure_loaded('Elixir.Macro.Env'),
_ = code:ensure_loaded('Elixir.Module.LocalsTracker'),
_ = code:ensure_loaded('Elixir.Kernel.LexicalTracker'),
{ok, #elixir_code_server{}}.
handle_call({acquire, Path}, From, Config) ->
@@ -64,7 +69,8 @@ handle_call({compilation_status, CompilerPid}, _From, Config) ->
CompilationStatusList = Config#elixir_code_server.compilation_status,
CompilationStatusListNew = orddict:erase(CompilerPid, CompilationStatusList),
CompilationStatus = orddict:fetch(CompilerPid, CompilationStatusList),
{reply, CompilationStatus, Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
{reply, CompilationStatus,
Config#elixir_code_server{compilation_status=CompilationStatusListNew}};
handle_call(retrieve_module_name, _From, Config) ->
case Config#elixir_code_server.pool of
@@ -83,6 +89,9 @@ handle_call(erl_compiler_options, _From, Config) ->
{reply, Opts, Config}
end;
handle_call(paths, _From, Config) ->
{reply, Config#elixir_code_server.paths, Config};
handle_call(Request, _From, Config) ->
{stop, {badcall, Request}, Config}.
@@ -115,7 +124,7 @@ handle_cast({loaded, Path}, Config) ->
{ok, true} ->
{noreply, Config};
{ok, {Ref, List}} when is_list(List), is_reference(Ref) ->
[Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)],
_ = [Pid ! {elixir_code_server, Ref, loaded} || {Pid, _Tag} <- lists:reverse(List)],
Done = orddict:store(Path, true, Current),
{noreply, Config#elixir_code_server{loaded=Done}};
error ->
@@ -131,6 +140,9 @@ handle_cast({unload_files, Files}, Config) ->
handle_cast({return_module_name, H}, #elixir_code_server{pool={T,Counter}} = Config) ->
{noreply, Config#elixir_code_server{pool={[H|T],Counter}}};
handle_cast({paths, PA, PZ}, #elixir_code_server{} = Config) ->
{noreply, Config#elixir_code_server{paths={PA,PZ}}};
handle_cast(Request, Config) ->
{stop, {badcast, Request}, Config}.
+6 -6
View File
@@ -40,7 +40,7 @@ file(Relative) when is_binary(Relative) ->
file_to_path(File, Path) when is_binary(File), is_binary(Path) ->
Lists = file(File),
[binary_to_path(X, Path) || X <- Lists],
_ = [binary_to_path(X, Path) || X <- Lists],
Lists.
%% Evaluation
@@ -144,7 +144,7 @@ module(Forms, File, Options, Bootstrap, Callback) when
case compile:noenv_forms([no_auto_import()|Forms], [return,{source,Listname}|Options]) of
{ok, ModuleName, Binary, Warnings} ->
format_warnings(Bootstrap, Warnings),
code:load_binary(ModuleName, Listname, Binary),
{module, ModuleName} = code:load_binary(ModuleName, Listname, Binary),
Callback(ModuleName, Binary);
{error, Errors, Warnings} ->
format_warnings(Bootstrap, Warnings),
@@ -157,14 +157,14 @@ no_auto_import() ->
%% CORE HANDLING
core() ->
application:start(elixir),
ok = application:ensure_started(elixir),
elixir_code_server:cast({compiler_options, [{docs,false},{internal,true}]}),
[core_file(File) || File <- core_main()].
core_file(File) ->
try
Lists = file(File),
[binary_to_path(X, "lib/elixir/ebin") || X <- Lists],
_ = [binary_to_path(X, "lib/elixir/ebin") || X <- Lists],
io:format("Compiled ~ts~n", [File])
catch
Kind:Reason ->
@@ -214,11 +214,11 @@ format_errors([]) ->
format_errors(Errors) ->
lists:foreach(fun ({File, Each}) ->
BinFile = elixir_utils:characters_to_binary(File),
lists:foreach(fun (Error) -> elixir_errors:handle_file_error(BinFile, Error) end, Each)
lists:foreach(fun(Error) -> elixir_errors:handle_file_error(BinFile, Error) end, Each)
end, Errors).
format_warnings(Bootstrap, Warnings) ->
lists:foreach(fun ({File, Each}) ->
BinFile = elixir_utils:characters_to_binary(File),
lists:foreach(fun (Warning) -> elixir_errors:handle_file_warning(Bootstrap, BinFile, Warning) end, Each)
lists:foreach(fun(Warning) -> elixir_errors:handle_file_warning(Bootstrap, BinFile, Warning) end, Each)
end, Warnings).
+137 -85
View File
@@ -2,7 +2,7 @@
-module(elixir_def).
-export([table/1, clauses_table/1, setup/1,
cleanup/1, reset_last/1, lookup_definition/2,
delete_definition/2, store_definition/6, unwrap_definitions/1,
delete_definition/2, store_definition/6, unwrap_definitions/2,
store_each/8, format_error/1]).
-include("elixir.hrl").
@@ -55,7 +55,7 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
{Name, Args} = case NameAndArgs of
{N, _, A} when is_atom(N), is_atom(A) -> {N, []};
{N, _, A} when is_atom(N), is_list(A) -> {N, A};
_ -> elixir_errors:form_error(Line, ?m(E, file), ?MODULE, {invalid_def, Kind, NameAndArgs})
_ -> elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE, {invalid_def, Kind, NameAndArgs})
end,
%% Now that we have verified the call format,
@@ -64,11 +64,17 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
DoCheckClauses = (not lists:keymember(context, 1, Meta)) andalso (CheckClauses),
%% Check if there is a file information in the definition.
%% If so, we assume this come from another source and we need
%% to linify taking into account keep line numbers.
{File, Key} = case lists:keyfind(file, 1, Meta) of
{file, Bin} when is_binary(Bin) -> {Bin, keep};
_ -> {nil, line}
%% If so, we assume this come from another source and
%% we need to linify taking into account keep line numbers.
{Location, Key} = case lists:keyfind(file, 1, Meta) of
{file, KeepFile} when is_binary(KeepFile) ->
case lists:keyfind(keep, 1, Meta) of
{keep, KeepLine} when is_integer(KeepLine) -> KeepLine;
_ -> KeepLine = 0
end,
{{KeepFile, KeepLine}, keep};
_ ->
{nil, line}
end,
LinifyArgs = elixir_quote:linify(Line, Key, Args),
@@ -77,15 +83,15 @@ store_definition(Line, Kind, CheckClauses, Call, Body, Pos) ->
assert_no_aliases_name(Line, Name, Args, E),
store_definition(Line, Kind, DoCheckClauses, Name,
LinifyArgs, LinifyGuards, LinifyBody, File, E).
LinifyArgs, LinifyGuards, LinifyBody, Location, E).
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, MetaFile, #{module := Module} = ER) ->
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepLocation, #{module := Module} = ER) ->
Arity = length(Args),
Tuple = {Name, Arity},
E = ER#{function := Tuple},
elixir_locals:record_definition(Tuple, Kind, Module),
Location = retrieve_location(Line, MetaFile, Module),
Location = retrieve_location(KeepLocation, Module),
{Function, Defaults, Super} = translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E),
DefaultsLength = length(Defaults),
@@ -115,31 +121,36 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
_ ->
Env = elixir_env:linify({Line, E}),
Callbacks = 'Elixir.Module':get_attribute(Module, on_definition),
[Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks]
_ = [Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks],
ok
end.
make_struct_available(def, Module, '__struct__', []) ->
case erlang:get(elixir_compiler_pid) of
undefined -> ok;
Pid -> Pid ! {struct_available, Module}
Pid ->
Pid ! {struct_available, Module},
ok
end;
make_struct_available(_, _, _, _) ->
ok.
%% Retrieve location from meta file or @file, otherwise nil
%% Retrieve location from meta file (if Key == keep)
%% or @file, otherwise nil
retrieve_location(Line, File, Module) ->
retrieve_location(Location, Module) ->
case get_location_attribute(Module) of
nil when not is_binary(File) ->
nil;
nil ->
nil when is_tuple(Location) ->
{File, Line} = Location,
{normalize_location(File), Line};
X when is_binary(X) ->
nil ->
nil;
File when is_binary(File) ->
'Elixir.Module':delete_attribute(Module, file),
{normalize_location(X), 0};
{X, L} when is_binary(X) andalso is_integer(L) ->
{normalize_location(File), 0};
{File, Line} when is_binary(File) andalso is_integer(Line) ->
'Elixir.Module':delete_attribute(Module, file),
{normalize_location(X), L}
{normalize_location(File), Line}
end.
get_location_attribute(Module) ->
@@ -148,8 +159,8 @@ get_location_attribute(Module) ->
false -> 'Elixir.Module':get_attribute(Module, file)
end.
normalize_location(X) ->
elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(X)).
normalize_location(File) ->
elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)).
%% Compile super
@@ -179,7 +190,7 @@ translate_definition(Kind, Line, Module, Name, Args, Guards, Body, E) when is_in
translate_clause(nil, _Line, _Kind, _Args, [], _Body, _S) ->
{[], false};
translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
elixir_errors:form_error(Line, File, ?MODULE, {missing_do, Kind});
elixir_errors:form_error([{line, Line}], File, ?MODULE, {missing_do, Kind});
translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
{TClause, TS} = elixir_clauses:clause(Line,
fun elixir_translator:translate_args/2, Args, Body, Guards, true, S),
@@ -216,88 +227,117 @@ is_macro(_) -> false.
% Unwrap the functions stored in the functions table.
% It returns a list of all functions to be exported, plus the macros,
% and the body of all functions.
unwrap_definitions(Module) ->
Table = table(Module),
unwrap_definitions(File, Module) ->
Table = table(Module),
CTable = clauses_table(Module),
ets:delete(Table, last),
unwrap_definition(ets:tab2list(Table), CTable, [], [], [], [], [], [], []).
unwrap_definition([Fun|T], CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
Tuple = element(1, Fun),
Clauses = [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)],
{All, Private} = unwrap_definition(ets:tab2list(Table), File, Module, CTable, [], []),
Unreachable = elixir_locals:warn_unused_local(File, Module, Private),
split_definition(All, Unreachable, [], [], [], [], {[], []}).
{NewFun, NewExports, NewPrivate, NewDef, NewDefmacro} =
case Clauses of
[] -> {false, Exports, Private, Def, Defmacro};
_ -> unwrap_definition(element(2, Fun), Tuple, Fun, Exports, Private, Def, Defmacro)
end,
unwrap_definition([Fun|T], File, Module, CTable, All, Private) ->
{Tuple, Kind, Line, _, Check, Location, {Defaults, _, _}} = Fun,
Export = export(Kind, Tuple),
{NewFunctions, NewTail} = case NewFun of
case [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)] of
[] ->
warn_bodyless_function(Line, File, Module, Kind, Tuple),
unwrap_definition(T, File, Module, CTable, All, Private);
Clauses ->
Unwrapped = {Tuple, Kind, Line, Location,
function_for_stored_definition(Line, Export, Clauses)},
NewPrivate =
if
Kind == defp; Kind == defmacrop ->
[{Tuple, Kind, Line, Check, Defaults}|Private];
true ->
Private
end,
unwrap_definition(T, File, Module, CTable, [Unwrapped|All], NewPrivate)
end;
unwrap_definition([], _File, _Module, _CTable, All, Private) ->
{All, Private}.
split_definition([{Tuple, def, Line, Location, Body}|T], Unreachable,
Def, Defp, Defmacro, Defmacrop, Functions) ->
split_definition(T, Unreachable, [Tuple|Def], Defp, Defmacro, Defmacrop,
add_definition(Line, Location, Body, Functions));
split_definition([{Tuple, defp, Line, Location, Body}|T], Unreachable,
Def, Defp, Defmacro, Defmacrop, Functions) ->
case lists:member(Tuple, Unreachable) of
false ->
NewAll = All,
{Functions, Tail};
_ ->
NewAll = [Tuple|All],
function_for_stored_definition(NewFun, Clauses, Functions, Tail)
end,
split_definition(T, Unreachable, Def, [Tuple|Defp], Defmacro, Defmacrop,
add_definition(Line, Location, Body, Functions));
true ->
split_definition(T, Unreachable, Def, [Tuple|Defp], Defmacro, Defmacrop, Functions)
end;
unwrap_definition(T, CTable, NewAll, NewExports, NewPrivate,
NewDef, NewDefmacro, NewFunctions, NewTail);
unwrap_definition([], _CTable, All, Exports, Private, Def, Defmacro, Functions, Tail) ->
{All, Exports, Private, ordsets:from_list(Def),
ordsets:from_list(Defmacro), lists:reverse(Tail ++ Functions)}.
split_definition([{Tuple, defmacro, Line, Location, Body}|T], Unreachable,
Def, Defp, Defmacro, Defmacrop, Functions) ->
split_definition(T, Unreachable, Def, Defp, [Tuple|Defmacro], Defmacrop,
add_definition(Line, Location, Body, Functions));
unwrap_definition(def, Tuple, Fun, Exports, Private, Def, Defmacro) ->
{Fun, [Tuple|Exports], Private, [Tuple|Def], Defmacro};
unwrap_definition(defmacro, {Name, Arity} = Tuple, Fun, Exports, Private, Def, Defmacro) ->
Macro = {elixir_utils:macro_name(Name), Arity + 1},
{setelement(1, Fun, Macro), [Macro|Exports], Private, Def, [Tuple|Defmacro]};
unwrap_definition(defp, Tuple, Fun, Exports, Private, Def, Defmacro) ->
%% {Name, Arity}, Kind, Line, Check, Defaults
Info = {Tuple, defp, element(3, Fun), element(5, Fun), element(7, Fun)},
{Fun, Exports, [Info|Private], Def, Defmacro};
unwrap_definition(defmacrop, Tuple, Fun, Exports, Private, Def, Defmacro) ->
%% {Name, Arity}, Kind, Line, Check, Defaults
Info = {Tuple, defmacrop, element(3, Fun), element(5, Fun), element(7, Fun)},
{false, Exports, [Info|Private], Def, Defmacro}.
split_definition([{Tuple, defmacrop, _Line, _Location, _Body}|T], Unreachable,
Def, Defp, Defmacro, Defmacrop, Functions) ->
split_definition(T, Unreachable, Def, Defp, Defmacro, [Tuple|Defmacrop], Functions);
split_definition([], _Unreachable, Def, Defp, Defmacro, Defmacrop, {Head, Tail}) ->
{Def, Defp, Defmacro, Defmacrop, Head ++ Tail}.
%% Helpers
function_for_stored_definition({{Name,Arity}, _, Line, _, _, nil, _}, Clauses, Functions, Tail) ->
{[{function, Line, Name, Arity, Clauses}|Functions], Tail};
export(Kind, {Name, Arity}) when Kind == defmacro; Kind == defmacrop ->
{elixir_utils:macro_name(Name), Arity + 1};
export(Kind, {Name, Arity}) when Kind == def; Kind == defp ->
{Name, Arity}.
function_for_stored_definition({{Name,Arity}, _, Line, _, _, Location, _}, Clauses, Functions, Tail) ->
{Functions, [
{function, Line, Name, Arity, Clauses},
{attribute, Line, file, Location} | Tail
]}.
function_for_stored_definition(Line, {Name,Arity}, Clauses) ->
{function, Line, Name, Arity, Clauses}.
add_definition(_Line, nil, Body, {Head, Tail}) ->
{[Body|Head], Tail};
add_definition(Line, Location, Body, {Head, Tail}) ->
{Head,
[{attribute, Line, file, Location}, Body|Tail]}.
default_function_for(Kind, Name, {clause, Line, Args, _Guards, _Exprs} = Clause)
when Kind == defmacro; Kind == defmacrop ->
{function, Line, Name, length(Args) - 1, [Clause]};
default_function_for(_, Name, {clause, Line, Args, _Guards, _Exprs} = Clause) ->
{function, Line, Name, length(Args), [Clause]}.
warn_bodyless_function(_Line, _File, Special, _Kind, _Tuple)
when Special == 'Elixir.Kernel.SpecialForms'; Special == 'Elixir.Module' ->
ok;
warn_bodyless_function(Line, File, _Module, Kind, Tuple) ->
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {bodyless_fun, Kind, Tuple}),
ok.
%% Store each definition in the table.
%% This function also checks and emit warnings in case
%% the kind, of the visibility of the function changes.
store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line, Name, Arity, Clauses}) ->
Tuple = {Name, Arity},
HasBody = Clauses =/= [],
case ets:lookup(Table, Tuple) of
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, StoredDefaults}] ->
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck, StoredLocation, {StoredDefaults, LastHasBody, LastDefaults}}] ->
FinalLine = StoredLine,
FinalLocation = StoredLocation,
FinalDefaults = max(Defaults, StoredDefaults),
FinalDefaults = {max(Defaults, StoredDefaults), HasBody, Defaults},
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
(Check and StoredCheck) andalso
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile),
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults);
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, LastDefaults, LastHasBody);
[] ->
FinalLine = Line,
FinalLocation = Location,
FinalDefaults = Defaults
FinalDefaults = {Defaults, HasBody, Defaults}
end,
Check andalso ets:insert(Table, {last, {Name, Arity}}),
ets:insert(CTable, [{Tuple, Clause} || Clause <- Clauses ]),
@@ -307,7 +347,7 @@ store_each(Check, Kind, File, Location, Table, CTable, Defaults, {function, Line
check_valid_kind(_Line, _File, _Name, _Arity, Kind, Kind) -> [];
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
elixir_errors:form_error(Line, File, ?MODULE,
elixir_errors:form_error([{line, Line}], File, ?MODULE,
{changed_kind, {Name, Arity, StoredKind, Kind}}).
check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile) ->
@@ -315,21 +355,29 @@ check_valid_clause(Line, File, Name, Arity, Kind, Table, StoredLine, StoredFile)
{Name,Arity} -> [];
[] -> [];
_ ->
Relative = elixir_utils:relative_to_cwd(StoredFile),
elixir_errors:handle_file_warning(File, {Line, ?MODULE,
{ungrouped_clause, {Kind, Name, Arity, StoredLine, Relative}}})
Relative = elixir_utils:relative_to_cwd(elixir_utils:relative_to_cwd(StoredFile)),
elixir_errors:form_warn([{line, Line}], File, ?MODULE,
{ungrouped_clause, {Kind, Name, Arity, StoredLine, Relative}})
end.
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:form_error(Line, File, ?MODULE, {clauses_with_defaults, {Kind, Name, Arity}}).
% Any clause after clause with defaults (body less does not count)
check_valid_defaults(Line, File, Name, Arity, Kind, _, StoredDefaults, true) when StoredDefaults > 0 ->
elixir_errors:form_error([{line, Line}], File, ?MODULE,
{clauses_with_defaults, {Kind, Name, Arity}});
% Clause without defaults
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _, _) -> [];
% Clause with defaults after clause without defaults
check_valid_defaults(Line, File, Name, Arity, Kind, _, 0, _) ->
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {out_of_order_defaults, {Kind, Name, Arity}});
% Clause with defaults after clause with defaults
check_valid_defaults(Line, File, Name, Arity, Kind, _, _, _) ->
elixir_errors:form_error([{line, Line}], File, ?MODULE,
{clauses_with_defaults, {Kind, Name, Arity}}).
check_previous_defaults(Table, Line, Name, Arity, Kind, Defaults, E) ->
Matches = ets:match(Table, {{Name, '$2'}, '$1', '_', '_', '_', '_', '$3'}),
Matches = ets:match(Table, {{Name, '$2'}, '$1', '_', '_', '_', '_', {'$3', '_', '_'}}),
[ begin
elixir_errors:form_error(Line, ?m(E, file), ?MODULE,
elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE,
{defs_with_defaults, Name, {Kind, Arity}, {K, A}})
end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
@@ -339,7 +387,8 @@ defaults_conflict(A, D, Arity, Defaults) ->
check_args_for_bodyless_clause(Line, Args, E) ->
[ begin
elixir_errors:form_error(Line, ?m(E, file), ?MODULE, invalid_args_for_bodyless_clause)
elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE,
invalid_args_for_bodyless_clause)
end || Arg <- Args, invalid_arg(Arg) ].
invalid_arg({Name, _, Kind}) when is_atom(Name), is_atom(Kind) ->
@@ -350,13 +399,16 @@ invalid_arg(_) ->
true.
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
elixir_errors:form_error(Line, File, ?MODULE, {no_alias, Atom});
elixir_errors:form_error([{line, Line}], File, ?MODULE, {no_alias, Atom});
assert_no_aliases_name(_Meta, _Aliases, _Args, _S) ->
ok.
%% Format errors
format_error({bodyless_fun, Kind, {Name, Arity}}) ->
io_lib:format("bodyless clause provided for nonexistent ~ts ~ts/~B", [Kind, Name, Arity]);
format_error({no_module,{Kind,Name,Arity}}) ->
io_lib:format("cannot define function outside module, invalid scope for ~ts ~ts/~B", [Kind, Name, Arity]);
+5 -4
View File
@@ -36,7 +36,7 @@ store(Module, Function, GenerateName) ->
{_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,
{{{Name, Arity}, Kind, Line, File, _Check, Location, {Defaults, _HasBody, _LastDefaults}}, Clauses} = Clause,
{FinalKind, FinalName} = case GenerateName of
true -> {defp, name(Module, Function, Overridable)};
@@ -58,8 +58,9 @@ store(Module, Function, GenerateName) ->
%% Store pending declarations that were not manually made concrete.
store_pending(Module) ->
[store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module),
not 'Elixir.Module':'defines?'(Module, X)].
_ = [store(Module, X, false) || {X, {_, _, _, false}} <- overridable(Module),
not 'Elixir.Module':'defines?'(Module, X)],
ok.
%% Error handling
@@ -72,4 +73,4 @@ format_error({no_super, Module, {Name, Arity}}) ->
format_fa({Name, Arity}) ->
A = atom_to_binary(Name, utf8),
B = integer_to_binary(Arity),
<< A/binary, $/, B/binary >>.
<< A/binary, $/, B/binary >>.
+24 -8
View File
@@ -4,7 +4,7 @@
-module(elixir_dispatch).
-export([dispatch_import/5, dispatch_require/6,
require_function/5, import_function/4,
expand_import/5, expand_require/5,
expand_import/6, expand_require/5,
default_functions/0, default_macros/0, default_requires/0,
find_import/4, format_error/1]).
-include("elixir.hrl").
@@ -18,6 +18,7 @@
-define(list, 'Elixir.List').
-define(map, 'Elixir.Map').
-define(node, 'Elixir.Node').
-define(port, 'Elixir.Port').
-define(process, 'Elixir.Process').
-define(string, 'Elixir.String').
-define(system, 'Elixir.System').
@@ -87,7 +88,7 @@ remote_function(Meta, Receiver, Name, Arity, E) ->
dispatch_import(Meta, Name, Args, E, Callback) ->
Arity = length(Args),
case expand_import(Meta, {Name, Arity}, Args, E, []) of
case expand_import(Meta, {Name, Arity}, Args, E, [], false) of
{ok, Receiver, Quoted} ->
expand_quoted(Meta, Receiver, Name, Arity, Quoted, E);
{ok, Receiver, NewName, NewArgs} ->
@@ -114,12 +115,12 @@ dispatch_require(_Meta, Receiver, Name, Args, _E, Callback) ->
%% Macros expansion
expand_import(Meta, {Name, Arity} = Tuple, Args, E, Extra) ->
Module = ?m(E, module),
Dispatch = find_dispatch(Meta, Tuple, Extra, E),
Function = ?m(E, function),
Local = (Function /= nil) andalso (Function /= Tuple) andalso
elixir_locals:macro_for(Module, Name, Arity),
expand_import(Meta, {Name, Arity} = Tuple, Args, E, Extra, External) ->
Module = ?m(E, module),
Dispatch = find_dispatch(Meta, Tuple, Extra, E),
Function = ?m(E, function),
AllowLocals = External orelse ((Function /= nil) andalso (Function /= Tuple)),
Local = AllowLocals andalso elixir_locals:macro_for(Module, Name, Arity),
case Dispatch of
%% In case it is an import, we dispatch the import.
@@ -476,6 +477,17 @@ inline(?process, demonitor, 2) -> {erlang, demonitor};
inline(?process, link, 1) -> {erlang, link};
inline(?process, unlink, 1) -> {erlang, unlink};
inline(?port, open, 2) -> {erlang, open_port};
inline(?port, call, 3) -> {erlang, port_call};
inline(?port, close, 1) -> {erlang, port_close};
inline(?port, command, 2) -> {erlang, port_command};
inline(?port, command, 3) -> {erlang, port_command};
inline(?port, connect, 2) -> {erlang, port_connect};
inline(?port, control, 3) -> {erlang, port_control};
inline(?port, info, 1) -> {erlang, port_info};
inline(?port, info, 2) -> {erlang, port_info};
inline(?port, list, 0) -> {erlang, ports};
inline(?string, to_float, 1) -> {erlang, binary_to_float};
inline(?string, to_integer, 1) -> {erlang, binary_to_integer};
inline(?string, to_integer, 2) -> {erlang, binary_to_integer};
@@ -517,5 +529,9 @@ deprecation('Elixir.EEx.AssignsEngine', '__using__', _) ->
"check EEx.SmartEngine for how to build custom engines";
deprecation('Elixir.Kernel', 'xor', _) ->
true; %% Remember to remove xor operator from tokenizer
% deprecation('Elixir.System', 'cmd', 1) ->
% "use System.cmd/3 instead;"
deprecation(_, _, _) ->
false.
+87 -60
View File
@@ -2,36 +2,37 @@
% This is not exposed in the Elixir language.
-module(elixir_errors).
-export([compile_error/3, compile_error/4,
form_error/4, parse_error/4, warn/2, warn/3,
form_error/4, form_warn/4, parse_error/4, warn/2, warn/3,
handle_file_warning/2, handle_file_warning/3, handle_file_error/2]).
-include("elixir.hrl").
-type line_or_meta() :: integer() | list().
-spec warn(non_neg_integer(), binary(), iolist()) -> ok.
warn(Warning) ->
CompilerPid = get(elixir_compiler_pid),
if
CompilerPid =/= undefined ->
elixir_code_server:cast({register_warning, CompilerPid});
true -> false
end,
io:put_chars(standard_error, Warning).
warn(Line, File, Warning) when is_integer(Line), is_binary(File) ->
warn(file_format(Line, File), Warning).
-spec warn(iolist(), iolist()) -> ok.
warn(Caller, Warning) ->
warn([Caller, "warning: ", Warning]).
do_warn([Caller, "warning: ", Warning, $\n]).
warn(Line, File, Warning) when is_integer(Line) ->
warn(file_format(Line, File, "warning: " ++ Warning)).
%% General forms handling.
%% Raised during expansion/translation/compilation.
-spec form_error(line_or_meta(), binary(), module(), any()) -> no_return().
-spec form_error(list(), binary(), module(), any()) -> no_return().
form_error(Meta, File, Module, Desc) ->
compile_error(Meta, File, format_error(Module, Desc)).
-spec compile_error(line_or_meta(), binary(), iolist()) -> no_return().
-spec compile_error(line_or_meta(), binary(), iolist(), list()) -> no_return().
-spec form_warn(list(), binary(), module(), any()) -> ok.
form_warn(Meta, File, Module, Desc) when is_list(Meta) ->
{MetaLine, MetaFile} = meta_location(Meta, File),
warn(MetaLine, MetaFile, format_error(Module, Desc)).
%% Compilation error.
-spec compile_error(list(), binary(), iolist()) -> no_return().
-spec compile_error(list(), binary(), iolist(), list()) -> no_return().
compile_error(Meta, File, Message) when is_list(Message) ->
raise(Meta, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
@@ -39,23 +40,23 @@ compile_error(Meta, File, Message) when is_list(Message) ->
compile_error(Meta, File, Format, Args) when is_list(Format) ->
compile_error(Meta, File, io_lib:format(Format, Args)).
%% Raised on tokenizing/parsing
%% Tokenization parsing/errors.
-spec parse_error(line_or_meta(), binary(), binary(), binary()) -> no_return().
-spec parse_error(non_neg_integer(), binary(), binary(), binary()) -> no_return().
parse_error(Meta, File, Error, <<>>) ->
parse_error(Line, File, Error, <<>>) ->
Message = case Error of
<<"syntax error before: ">> -> <<"syntax error: expression is incomplete">>;
_ -> Error
end,
raise(Meta, File, 'Elixir.TokenMissingError', Message);
do_raise(Line, File, 'Elixir.TokenMissingError', Message);
%% Show a nicer message for missing end tokens
parse_error(Meta, File, <<"syntax error before: ">>, <<"'end'">>) ->
raise(Meta, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
parse_error(Line, File, <<"syntax error before: ">>, <<"'end'">>) ->
do_raise(Line, File, 'Elixir.SyntaxError', <<"unexpected token: end">>);
%% Binaries are wrapped in [<<...>>], so we need to unwrap them
parse_error(Meta, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) ->
parse_error(Line, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) ->
Rest =
case binary:split(Full, <<"<<">>) of
[Lead, Token] ->
@@ -66,27 +67,27 @@ parse_error(Meta, File, Error, <<"[", _/binary>> = Full) when is_binary(Error) -
[_] ->
<<$">>
end,
raise(Meta, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary >>);
do_raise(Line, File, 'Elixir.SyntaxError', <<Error/binary, Rest/binary >>);
%% Everything else is fine as is
parse_error(Meta, File, Error, Token) when is_binary(Error), is_binary(Token) ->
Message = <<Error / binary, Token / binary >>,
raise(Meta, File, 'Elixir.SyntaxError', Message).
parse_error(Line, File, Error, Token) when is_binary(Error), is_binary(Token) ->
Message = <<Error/binary, Token/binary >>,
do_raise(Line, File, 'Elixir.SyntaxError', Message).
%% Handle warnings and errors (called during module compilation)
%% Handle warnings and errors from Erlang land (called during module compilation)
%% Ignore on bootstrap
handle_file_warning(true, _File, {_Line, sys_core_fold, nomatch_guard}) -> [];
handle_file_warning(true, _File, {_Line, sys_core_fold, {nomatch_shadow, _}}) -> [];
handle_file_warning(true, _File, {_Line, sys_core_fold, nomatch_guard}) -> ok;
handle_file_warning(true, _File, {_Line, sys_core_fold, {nomatch_shadow, _}}) -> ok;
%% Ignore always
handle_file_warning(_, _File, {_Line, sys_core_fold, useless_building}) -> [];
handle_file_warning(_, _File, {_Line, sys_core_fold, useless_building}) -> ok;
%% This is an Erlang bug, it considers {tuple, _}.call to always fail
handle_file_warning(_, _File, {_Line, v3_kernel, bad_call}) -> [];
handle_file_warning(_, _File, {_Line, v3_kernel, bad_call}) -> ok;
%% We handle unused local warnings ourselves
handle_file_warning(_, _File, {_Line, erl_lint, {unused_function, _}}) -> [];
handle_file_warning(_, _File, {_Line, erl_lint, {unused_function, _}}) -> ok;
%% Make no_effect clauses pretty
handle_file_warning(_, File, {Line, sys_core_fold, {no_effect, {erlang, F, A}}}) ->
@@ -116,7 +117,7 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour_func,{Fun,Arity
handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
case elixir_compiler:get_opt(internal) of
true -> [];
true -> ok;
false ->
Message = io_lib:format("behaviour ~ts undefined", [elixir_aliases:inspect(Module)]),
warn(Line, File, Message)
@@ -124,11 +125,15 @@ handle_file_warning(_, File, {Line,erl_lint,{undefined_behaviour,Module}}) ->
%% Ignore unused vars at "weird" lines (<= 0)
handle_file_warning(_, _File, {Line,erl_lint,{unused_var,_Var}}) when Line =< 0 ->
[];
ok;
%% Ignore shadowed vars as we guarantee no conflicts ourselves
handle_file_warning(_, _File, {_Line,erl_lint,{shadowed_var,_Var,_Where}}) ->
[];
ok;
%% Rewrite nomatch_guard to be more generic it can happen inside if, unless, etc
handle_file_warning(_, File, {Line, sys_core_fold, nomatch_guard}) ->
warn(Line, File, "this check/guard will always yield the same result");
%% Properly format other unused vars
handle_file_warning(_, File, {Line,erl_lint,{unused_var,Var}}) ->
@@ -152,39 +157,27 @@ handle_file_error(File, {Line,erl_lint,{unsafe_var,Var,{In,_Where}}}) ->
_ -> In
end,
Message = io_lib:format("cannot define variable ~ts inside ~ts", [format_var(Var), Translated]),
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message));
do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message));
handle_file_error(File, {Line,erl_lint,{spec_fun_undefined,{M,F,A}}}) ->
Message = io_lib:format("spec for undefined function ~ts.~ts/~B", [elixir_aliases:inspect(M), F, A]),
raise(Line, File, 'Elixir.CompileError', iolist_to_binary(Message));
do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message));
handle_file_error(File, {Line,Module,Desc}) ->
form_error(Line, File, Module, Desc).
Message = format_error(Module, Desc),
do_raise(Line, File, 'Elixir.CompileError', elixir_utils:characters_to_binary(Message)).
%% Helpers
raise(Meta, File, Kind, Message) when is_list(Meta) ->
raise(?line(Meta), File, Kind, Message);
{MetaLine, MetaFile} = meta_location(Meta, File),
do_raise(MetaLine, MetaFile, Kind, Message).
raise(none, File, Kind, Message) ->
raise(0, File, Kind, Message);
file_format(0, File) ->
io_lib:format("~ts: ", [elixir_utils:relative_to_cwd(File)]);
raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File) ->
%% Populate the stacktrace so we can raise it
try
throw(ok)
catch
ok -> ok
end,
Stacktrace = erlang:get_stacktrace(),
Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]),
erlang:raise(error, Exception, tl(Stacktrace)).
file_format(0, File, Message) when is_binary(File) ->
io_lib:format("~ts: ~ts~n", [elixir_utils:relative_to_cwd(File), Message]);
file_format(Line, File, Message) when is_binary(File) ->
io_lib:format("~ts:~w: ~ts~n", [elixir_utils:relative_to_cwd(File), Line, Message]).
file_format(Line, File) ->
io_lib:format("~ts:~w: ", [elixir_utils:relative_to_cwd(File), Line]).
format_var(Var) ->
list_to_atom(lists:takewhile(fun(X) -> X /= $@ end, atom_to_list(Var))).
@@ -215,3 +208,37 @@ translate_comp_op('=<') -> '<=';
translate_comp_op('=:=') -> '===';
translate_comp_op('=/=') -> '!==';
translate_comp_op(Other) -> Other.
meta_location(Meta, File) ->
case lists:keyfind(file, 1, Meta) of
{file, MetaFile} when is_binary(MetaFile) ->
case lists:keyfind(keep, 1, Meta) of
{keep, MetaLine} when is_integer(MetaLine) -> ok;
_ -> MetaLine = 0
end,
{MetaLine, MetaFile};
_ ->
{?line(Meta), File}
end.
do_warn(Warning) ->
CompilerPid = get(elixir_compiler_pid),
if
CompilerPid =/= undefined ->
elixir_code_server:cast({register_warning, CompilerPid});
true -> false
end,
io:put_chars(standard_error, Warning),
ok.
do_raise(none, File, Kind, Message) ->
do_raise(0, File, Kind, Message);
do_raise(Line, File, Kind, Message) when is_integer(Line), is_binary(File), is_binary(Message) ->
try
throw(ok)
catch
ok -> ok
end,
Stacktrace = erlang:get_stacktrace(),
Exception = Kind:exception([{description, Message}, {file, File}, {line, Line}]),
erlang:raise(error, Exception, tl(Stacktrace)).
+19 -12
View File
@@ -169,7 +169,7 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote")
end,
ValidOpts = [context, location, line, unquote, bind_quoted],
ValidOpts = [context, location, line, file, unquote, bind_quoted],
{EKV, ET} = expand_opts(Meta, quote, ValidOpts, KV, E),
Context = case lists:keyfind(context, 1, EKV) of
@@ -185,8 +185,20 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
end
end,
Keep = lists:keyfind(location, 1, EKV) == {location, keep},
Line = proplists:get_value(line, EKV, false),
{File, Line} = case lists:keyfind(location, 1, EKV) of
{location, keep} ->
{elixir_utils:relative_to_cwd(?m(E, file)), false};
false ->
{ case lists:keyfind(file, 1, EKV) of
{file, F} -> F;
false -> nil
end,
case lists:keyfind(line, 1, EKV) of
{line, L} -> L;
false -> false
end }
end,
{Binding, DefaultUnquote} = case lists:keyfind(bind_quoted, 1, EKV) of
{bind_quoted, BQ} -> {BQ, false};
@@ -194,11 +206,11 @@ expand({quote, Meta, [KV, Do]}, E) when is_list(Do) ->
end,
Unquote = case lists:keyfind(unquote, 1, EKV) of
{unquote, Bool} when is_boolean(Bool) -> Bool;
{unquote, U} when is_boolean(U) -> U;
false -> DefaultUnquote
end,
Q = #elixir_quote{line=Line, keep=Keep, unquote=Unquote, context=Context},
Q = #elixir_quote{line=Line, file=File, unquote=Unquote, context=Context},
{Quoted, _Q} = elixir_quote:quote(Exprs, Binding, Q, ET),
expand(Quoted, ET);
@@ -296,13 +308,8 @@ expand({Name, Meta, Kind} = Var, #{vars := Vars} = E) when is_atom(Name), is_ato
VarMeta = lists:keyfind(var, 1, Meta),
if
VarMeta == {var, true} ->
Extra = case Kind of
nil -> "";
_ -> io_lib:format(" (context ~ts)", [elixir_aliases:inspect(Kind)])
end,
compile_error(Meta, ?m(E, file), "expected var ~ts~ts to expand to an existing "
"variable or be a part of a match", [Name, Extra]);
compile_error(Meta, ?m(E, file), "expected var \"~ts\"~ts to expand to an existing variable "
"or be part of a match", [Name, elixir_scope:context_info(Kind)]);
true ->
expand({Name, Meta, []}, E)
end
+5 -5
View File
@@ -33,11 +33,8 @@ extract(Line, Scope, Interpol, [$\\, $\r, $\n|Rest], Buffer, Output, Last) ->
extract(Line, Scope, Interpol, [$\n|Rest], Buffer, Output, Last) ->
extract(Line+1, Scope, Interpol, Rest, [$\n|Buffer], Output, Last);
extract(Line, Scope, Interpol, [$\\, $#, ${|Rest], Buffer, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [${,$#|Buffer], Output, Last);
extract(Line, Scope, Interpol, [$\\,Char|Rest], Buffer, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [Char,$\\|Buffer], Output, Last);
extract(Line, Scope, true, [$\\, $#, ${|Rest], Buffer, Output, Last) ->
extract(Line, Scope, true, Rest, [${,$#|Buffer], Output, Last);
extract(Line, Scope, true, [$#, ${|Rest], Buffer, Output, Last) ->
Output1 = build_string(Line, Buffer, Output),
@@ -52,6 +49,9 @@ extract(Line, Scope, true, [$#, ${|Rest], Buffer, Output, Last) ->
{error, {string, Line, "missing interpolation terminator:}", []}}
end;
extract(Line, Scope, Interpol, [$\\,Char|Rest], Buffer, Output, Last) ->
extract(Line, Scope, Interpol, Rest, [Char,$\\|Buffer], Output, Last);
%% Catch all clause
extract(Line, Scope, Interpol, [Char|Rest], Buffer, Output, Last) ->
+14 -27
View File
@@ -27,51 +27,38 @@ run(File, Callback) ->
%% RECORD
record_alias(Module, Line, Warn, Ref) ->
if_tracker(Ref, fun(Pid) ->
?tracker:add_alias(Pid, Module, Line, Warn),
true
end).
if_tracker(Ref, fun(Pid) -> ?tracker:add_alias(Pid, Module, Line, Warn), ok end).
record_import(Module, Line, Warn, Ref) ->
if_tracker(Ref, fun(Pid) ->
?tracker:add_import(Pid, Module, Line, Warn),
true
end).
if_tracker(Ref, fun(Pid) -> ?tracker:add_import(Pid, Module, Line, Warn), ok end).
record_alias(Module, Ref) ->
if_tracker(Ref, fun(Pid) ->
?tracker:alias_dispatch(Pid, Module),
true
end).
if_tracker(Ref, fun(Pid) -> ?tracker:alias_dispatch(Pid, Module), ok end).
record_import(Module, Ref) ->
if_tracker(Ref, fun(Pid) ->
?tracker:import_dispatch(Pid, Module),
true
end).
if_tracker(Ref, fun(Pid) -> ?tracker:import_dispatch(Pid, Module), ok end).
record_remote(Module, Ref) ->
if_tracker(Ref, fun(Pid) ->
?tracker:remote_dispatch(Pid, Module),
true
end).
if_tracker(Ref, fun(Pid) -> ?tracker:remote_dispatch(Pid, Module), ok end).
%% HELPERS
if_tracker(nil, _Callback) -> false;
if_tracker(nil, _Callback) -> ok;
if_tracker(Pid, Callback) when is_pid(Pid) -> Callback(Pid).
%% ERROR HANDLING
warn_unused_imports(File, Pid) ->
[ begin
elixir_errors:handle_file_warning(File, {L, ?MODULE, {unused_import, M}})
end || {M, L} <- ?tracker:collect_unused_imports(Pid)].
[begin
elixir_errors:form_warn([{line, L}], File, ?MODULE, {unused_import, M})
end || {M, L} <- ?tracker:collect_unused_imports(Pid)],
ok.
warn_unused_aliases(File, Pid) ->
[ begin
elixir_errors:handle_file_warning(File, {L, ?MODULE, {unused_alias, M}})
end || {M, L} <- ?tracker:collect_unused_aliases(Pid)].
[begin
elixir_errors:form_warn([{line, L}], File, ?MODULE, {unused_alias, M})
end || {M, L} <- ?tracker:collect_unused_aliases(Pid)],
ok.
format_error({unused_alias, Module}) ->
io_lib:format("unused alias ~ts", [elixir_aliases:inspect(Module)]);
+33 -41
View File
@@ -4,7 +4,7 @@
setup/1, cleanup/1, cache_env/1, get_cached_env/1,
record_local/2, record_local/3, record_import/4,
record_definition/3, record_defaults/4,
ensure_no_function_conflict/4, warn_unused_local/3, format_error/1
ensure_no_import_conflict/4, warn_unused_local/3, format_error/1
]).
-export([macro_for/3, local_for/3, local_for/4]).
@@ -73,53 +73,41 @@ rewrite_clause(Else, _) -> Else.
setup(Module) ->
case code:is_loaded(?tracker) of
{file, _} -> ets:insert(Module, {?attr, ?tracker:start_link()});
{file, _} -> ets:insert(Module, {?attr, ?tracker:start_link()}), ok;
false -> ok
end.
cleanup(Module) ->
if_tracker(Module, fun(Pid) -> unlink(Pid), ?tracker:stop(Pid) end).
if_tracker(Module, fun(Pid) -> unlink(Pid), ?tracker:stop(Pid), ok end).
record_local(Tuple, Module) when is_atom(Module) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_local(Pid, Tuple),
true
end).
if_tracker(Module, fun(Pid) -> ?tracker:add_local(Pid, Tuple), ok end).
record_local(Tuple, _Module, Function)
when Function == nil; Function == Tuple -> false;
when Function == nil; Function == Tuple -> ok;
record_local(Tuple, Module, Function) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_local(Pid, Function, Tuple),
true
end).
if_tracker(Module, fun(Pid) -> ?tracker:add_local(Pid, Function, Tuple), ok end).
record_import(_Tuple, Receiver, Module, _Function)
when Module == nil; Module == Receiver -> false;
record_import(Tuple, Receiver, Module, Function) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_import(Pid, Function, Receiver, Tuple),
true
end).
if_tracker(Module, fun(Pid) -> ?tracker:add_import(Pid, Function, Receiver, Tuple), ok end).
record_definition(Tuple, Kind, Module) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_definition(Pid, Kind, Tuple),
true
end).
if_tracker(Module, fun(Pid) -> ?tracker:add_definition(Pid, Kind, Tuple), ok end).
record_defaults(_Tuple, _Kind, _Module, 0) ->
true;
ok;
record_defaults(Tuple, Kind, Module, Defaults) ->
if_tracker(Module, fun(Pid) ->
?tracker:add_defaults(Pid, Kind, Tuple, Defaults),
true
end).
if_tracker(Module, fun(Pid) -> ?tracker:add_defaults(Pid, Kind, Tuple, Defaults), ok end).
if_tracker(Module, Callback) ->
if_tracker(Module, ok, Callback).
if_tracker(Module, Default, Callback) ->
try ets:lookup_element(Module, ?attr, 2) of
Pid -> Callback(Pid)
catch
error:badarg -> false
error:badarg -> Default
end.
%% CACHING
@@ -140,25 +128,29 @@ get_cached_env(Env) -> Env.
%% ERROR HANDLING
ensure_no_function_conflict(Meta, File, Module, AllDefined) ->
if_tracker(Module, fun(Pid) ->
[ begin
elixir_errors:form_error(Meta, File, ?MODULE, {function_conflict, Error})
end || Error <- ?tracker:collect_imports_conflicts(Pid, AllDefined) ]
end),
ok.
ensure_no_import_conflict(_Line, _File, 'Elixir.Kernel', _All) ->
ok;
ensure_no_import_conflict(Line, File, Module, All) ->
if_tracker(Module, ok, fun(Pid) ->
_ = [ begin
elixir_errors:form_error([{line, Line}], File, ?MODULE, {function_conflict, Error})
end || Error <- ?tracker:collect_imports_conflicts(Pid, All) ],
ok
end).
warn_unused_local(File, Module, Private) ->
if_tracker(Module, fun(Pid) ->
Args = [ {Fun, Kind, Defaults} ||
{Fun, Kind, _Line, true, Defaults} <- Private],
if_tracker(Module, [], fun(Pid) ->
Args = [{Fun, Kind, Defaults} ||
{Fun, Kind, _Line, true, Defaults} <- Private],
Unused = ?tracker:collect_unused_locals(Pid, Args),
{Unreachable, Warnings} = ?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 ]
[begin
{_, _, Line, _, _} = lists:keyfind(element(2, Error), 1, Private),
elixir_errors:form_warn([{line, Line}], File, ?MODULE, Error)
end || Error <- Warnings ],
Unreachable
end).
format_error({function_conflict,{Receivers, Name, Arity}}) ->
+15 -19
View File
@@ -22,19 +22,8 @@ expand_struct(Meta, Left, Right, E) ->
EMeta =
case lists:member(ELeft, ?m(E, context_modules)) of
true ->
case (ELeft == ?m(E, module)) and
(?m(E, function) == nil) of
true ->
compile_error(Meta, ?m(E, file),
"cannot access struct ~ts in body of the module that defines it as "
"the struct fields are not yet accessible",
[elixir_aliases:inspect(ELeft)]);
false ->
[{struct, context}|Meta]
end;
false ->
Meta
true -> [{struct, context}|Meta];
false -> Meta
end,
case ERight of
@@ -90,12 +79,11 @@ translate_struct(Meta, Name, {'%{}', MapMeta, Args}, S) ->
%% Helpers
load_struct(Meta, Name, S) ->
Context = lists:keyfind(struct, 1, Meta) == {struct, context},
Local =
elixir_module:is_open(Name) andalso
(case lists:keyfind(struct, 1, Meta) of
{struct, context} -> true;
_ -> wait_for_struct(Name)
end),
(Context orelse wait_for_struct(Name)),
try
case Local of
@@ -112,8 +100,16 @@ load_struct(Meta, Name, S) ->
catch
error:undef ->
Inspected = elixir_aliases:inspect(Name),
compile_error(Meta, S#elixir_scope.file, "~ts.__struct__/0 is undefined, "
"cannot expand struct ~ts", [Inspected, Inspected])
case Context of
true ->
compile_error(Meta, S#elixir_scope.file,
"cannot access struct ~ts in the same context that defines it as "
"the struct fields are not yet accessible", [Inspected]);
false ->
compile_error(Meta, S#elixir_scope.file, "~ts.__struct__/0 is undefined, "
"cannot expand struct ~ts", [Inspected, Inspected])
end
end.
wait_for_struct(Module) ->
+87 -77
View File
@@ -42,35 +42,40 @@ compile(Module, Block, Vars, #{line := Line} = Env) when is_atom(Module) ->
end;
compile(Module, _Block, _Vars, #{line := Line, file := File}) ->
elixir_errors:form_error(Line, File, ?MODULE, {invalid_module, Module}).
elixir_errors:form_error([{line, Line}], File, ?MODULE, {invalid_module, Module}).
do_compile(Line, Module, Block, Vars, E) ->
File = ?m(E, file),
check_module_availability(Line, File, Module),
build(Line, File, Module, ?m(E, lexical_tracker)),
Docs = elixir_compiler:get_opt(docs),
build(Line, File, Module, Docs, ?m(E, lexical_tracker)),
try
{Result, NE} = eval_form(Line, Module, Block, Vars, E),
{Base, Export, Private, Def, Defmacro, Functions} = elixir_def:unwrap_definitions(Module),
{All, Forms0} = functions_form(Line, File, Module, Base, Export, Def, Defmacro, Functions),
Forms1 = specs_form(Module, Private, Defmacro, Forms0),
Forms2 = types_form(Module, Forms1),
Forms3 = attributes_form(Line, File, Module, Forms2),
case ets:lookup(data_table(Module), 'on_load') of
_ = case ets:lookup(data_table(Module), 'on_load') of
[] -> ok;
[{on_load,OnLoad}] ->
[elixir_locals:record_local(Tuple, Module) || Tuple <- OnLoad]
end,
AllFunctions = Def ++ [T || {T, defp, _, _, _} <- Private],
elixir_locals:ensure_no_function_conflict(Line, File, Module, AllFunctions),
elixir_locals:warn_unused_local(File, Module, Private),
warn_invalid_clauses(Line, File, Module, All),
warn_unused_docs(Line, File, Module),
{Def, Defp, Defmacro, Defmacrop, Functions} =
elixir_def:unwrap_definitions(File, Module),
Location = {elixir_utils:relative_to_cwd(elixir_utils:characters_to_list(File)), Line},
{All, Forms0} = functions_form(Line, File, Module, Def, Defp, Defmacro, Defmacrop, Functions),
Forms1 = specs_form(Module, Defmacro, Defmacrop, Forms0),
Forms2 = types_form(Line, File, Module, Forms1),
Forms3 = attributes_form(Line, File, Module, Forms2),
elixir_locals:ensure_no_import_conflict(Line, File, Module, All),
case Docs of
true -> warn_unused_docs(Line, File, Module, doc);
false -> false
end,
Location = {elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line},
Final = [
{attribute, Line, file, Location},
@@ -88,7 +93,7 @@ do_compile(Line, Module, Block, Vars, E) ->
%% Hook that builds both attribute and functions and set up common hooks.
build(Line, File, Module, Lexical) ->
build(Line, File, Module, Docs, Lexical) ->
%% Table with meta information about the module.
DataTable = data_table(Module),
@@ -97,16 +102,16 @@ build(Line, File, Module, Lexical) ->
true ->
[{OldFile, OldLine}] = ets:lookup_element(OldTable, ?location_attr, 2),
Error = {module_in_definition, Module, OldFile, OldLine},
elixir_errors:form_error(Line, File, ?MODULE, Error);
elixir_errors:form_error([{line, Line}], File, ?MODULE, Error);
false ->
[]
end,
ets:new(DataTable, [set, named_table, public]),
DataTable = ets:new(DataTable, [set, named_table, public]),
ets:insert(DataTable, {before_compile, []}),
ets:insert(DataTable, {after_compile, []}),
case elixir_compiler:get_opt(docs) of
case Docs of
true -> ets:insert(DataTable, {on_definition, [{'Elixir.Module', compile_doc}]});
_ -> ets:insert(DataTable, {on_definition, []})
end,
@@ -121,7 +126,8 @@ build(Line, File, Module, Lexical) ->
%% Setup other modules
elixir_def:setup(Module),
elixir_locals:setup(Module).
elixir_locals:setup(Module),
ok.
%% Receives the module representation and evaluates it.
@@ -143,16 +149,15 @@ eval_callbacks(Line, Module, Name, Args, E) ->
%% Return the form with exports and function declarations.
functions_form(Line, File, Module, BaseAll, BaseExport, Def, Defmacro, BaseFunctions) ->
{InfoSpec, Info} = add_info_function(Line, File, Module, BaseExport, Def, Defmacro),
functions_form(Line, File, Module, Def, Defp, Defmacro, Defmacrop, Body) ->
All = Def ++ Defp ++ Defmacro ++ Defmacrop,
{Spec, Info} = add_info_function(Line, File, Module, All, Def, Defmacro),
All = [{'__info__', 1}|BaseAll],
Export = [{'__info__', 1}|BaseExport],
Functions = [InfoSpec,Info|BaseFunctions],
NewBody = [Spec, Info|Body],
Export = [{Name, Arity} || {function, _, Name, Arity, _} <- NewBody],
{All, [
{attribute, Line, export, lists:sort(Export)} | Functions
]}.
{[{'__info__',1}|All],
[{attribute, Line, export, lists:sort(Export)} | NewBody]}.
%% Add attributes handling to the form
@@ -188,12 +193,12 @@ process_attribute(_Line, _File, _Key, Values) ->
process_external_resource(_Line, _File, Value) when is_binary(Value) ->
Value;
process_external_resource(Line, File, Value) ->
elixir_errors:handle_file_error(File,
{Line, ?MODULE, {invalid_external_resource, Value}}).
elixir_errors:form_error([{line, Line}], File,
?MODULE, {invalid_external_resource, Value}).
%% Types
types_form(Module, Forms0) ->
types_form(Line, File, Module, Forms0) ->
case code:ensure_loaded('Elixir.Kernel.Typespec') of
{module, 'Elixir.Kernel.Typespec'} ->
Types0 = 'Elixir.Module':get_attribute(Module, type) ++
@@ -207,6 +212,8 @@ types_form(Module, Forms0) ->
'Elixir.Module':delete_attribute(Module, typep),
'Elixir.Module':delete_attribute(Module, opaque),
warn_unused_docs(Line, File, Module, typedoc),
Forms1 = types_attributes(Types1, Forms0),
Forms2 = export_types_attributes(Types1, Forms1),
typedocs_attributes(Types1, Forms2);
@@ -241,11 +248,9 @@ typedocs_attributes(Types, Forms) ->
%% Specs
specs_form(Module, Private, Defmacro, Forms) ->
specs_form(Module, Defmacro, Defmacrop, Forms) ->
case code:ensure_loaded('Elixir.Kernel.Typespec') of
{module, 'Elixir.Kernel.Typespec'} ->
Defmacrop = [Tuple || {Tuple, defmacrop, _, _, _} <- Private],
Specs0 = 'Elixir.Module':get_attribute(Module, spec) ++
'Elixir.Module':get_attribute(Module, callback),
@@ -274,10 +279,10 @@ specs_attributes(Forms, Specs) ->
end, Forms, Dict).
translate_macro_spec({{spec, NameArity, Spec}, Line}, Defmacro, Defmacrop) ->
case ordsets:is_element(NameArity, Defmacrop) of
case lists:member(NameArity, Defmacrop) of
true -> [];
false ->
case ordsets:is_element(NameArity, Defmacro) of
case lists:member(NameArity, Defmacro) of
true ->
{Name, Arity} = NameArity,
[{{spec, {elixir_utils:macro_name(Name), Arity + 1}, spec_for_macro(Spec)}, Line}];
@@ -314,14 +319,14 @@ load_form(Line, Forms, Opts, #{file := File} = E) ->
put(elixir_compiled, [{Module,Binary}|Current]),
case get(elixir_compiler_pid) of
undefined -> [];
undefined -> ok;
PID ->
Ref = make_ref(),
PID ! {module_available, self(), Ref, File, Module, Binary},
receive {Ref, ack} -> ok end
end;
_ ->
[]
ok
end,
Binary
@@ -345,13 +350,12 @@ get_docs(Module) ->
get_moduledoc(Line, Module) ->
{Line, 'Elixir.Module':get_attribute(Module, moduledoc)}.
check_module_availability(Line, File, Module) ->
Reserved = ['Elixir.Any', 'Elixir.BitString', 'Elixir.Function', 'Elixir.PID',
'Elixir.Reference', 'Elixir.Elixir', 'Elixir'],
case lists:member(Module, Reserved) of
true -> elixir_errors:handle_file_error(File, {Line, ?MODULE, {module_reserved, Module}});
true -> elixir_errors:form_error([{line, Line}], File, ?MODULE, {module_reserved, Module});
false -> ok
end,
@@ -359,7 +363,7 @@ check_module_availability(Line, File, Module) ->
false ->
case code:ensure_loaded(Module) of
{module, _} ->
elixir_errors:handle_file_warning(File, {Line, ?MODULE, {module_defined, Module}});
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {module_defined, Module});
{error, _} ->
ok
end;
@@ -367,56 +371,64 @@ check_module_availability(Line, File, Module) ->
ok
end.
warn_invalid_clauses(_Line, _File, 'Elixir.Kernel.SpecialForms', _All) -> ok;
warn_invalid_clauses(_Line, File, Module, All) ->
ets:foldl(fun
({_, _, Kind, _, _}, _) when Kind == type; Kind == opaque ->
ok;
({Tuple, Line, _, _, _}, _) ->
case lists:member(Tuple, All) of
false ->
elixir_errors:handle_file_warning(File, {Line, ?MODULE, {invalid_clause, Tuple}});
true ->
ok
end
end, ok, docs_table(Module)).
warn_unused_docs(Line, File, Module, Attribute) ->
case ets:member(data_table(Module), Attribute) of
true ->
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {unused_doc, Attribute});
_ ->
ok
end.
warn_unused_docs(Line, File, Module) ->
lists:foreach(fun(Attribute) ->
case ets:member(data_table(Module), Attribute) of
true ->
elixir_errors:handle_file_warning(File, {Line, ?MODULE, {unused_doc, Attribute}});
_ ->
ok
end
end, [typedoc]).
% __INFO__
% EXTRA FUNCTIONS
add_info_function(Line, File, Module, Export, Def, Defmacro) ->
add_info_function(Line, File, Module, All, Def, Defmacro) ->
Pair = {'__info__', 1},
case lists:member(Pair, Export) of
case lists:member(Pair, All) of
true ->
elixir_errors:form_error(Line, File, ?MODULE, {internal_function_overridden, Pair});
elixir_errors:form_error([{line, Line}], File, ?MODULE, {internal_function_overridden, Pair});
false ->
{
{attribute, Line, spec, {{'__info__', 1},
[{type, Line, 'fun', [{type, Line, product, [ {type, Line, atom, []}]}, {type, Line, term, []} ]}]
Spec =
{attribute, Line, spec, {Pair,
[{type, Line, 'fun', [
{type, Line, product, [
{type, Line, union, [
{atom, Line, attributes},
{atom, Line, compile},
{atom, Line, exports},
{atom, Line, functions},
{atom, Line, imports},
{atom, Line, macros},
{atom, Line, module}
]}
]},
{type, Line, union, [
{type, Line, atom, []},
{type, Line, list, [
{type, Line, tuple, [
{type, Line, atom, []},
{type, Line, any, []}
]}
]}
]}
]}]
}},
Info =
{function, 0, '__info__', 1, [
functions_clause(Def),
macros_clause(Defmacro),
module_clause(Module),
else_clause()
]}
}
]},
{Spec, Info}
end.
functions_clause(Def) ->
{clause, 0, [{atom, 0, functions}], [], [elixir_utils:elixir_to_erl(Def)]}.
{clause, 0, [{atom, 0, functions}], [], [elixir_utils:elixir_to_erl(lists:sort(Def))]}.
macros_clause(Defmacro) ->
{clause, 0, [{atom, 0, macros}], [], [elixir_utils:elixir_to_erl(Defmacro)]}.
{clause, 0, [{atom, 0, macros}], [], [elixir_utils:elixir_to_erl(lists:sort(Defmacro))]}.
module_clause(Module) ->
{clause, 0, [{atom, 0, module}], [], [{atom, 0, Module}]}.
@@ -476,8 +488,6 @@ prune_stacktrace(Info, []) ->
% ERROR HANDLING
format_error({invalid_clause, {Name, Arity}}) ->
io_lib:format("empty clause provided for nonexistent function or macro ~ts/~B", [Name, Arity]);
format_error({invalid_external_resource, Value}) ->
io_lib:format("expected a string value for @external_resource, got: ~p",
['Elixir.Kernel':inspect(Value)]);
+31 -26
View File
@@ -1,10 +1,10 @@
-module(elixir_quote).
-export([escape/2, linify/2, linify/3, linify_with_context_counter/3, quote/4]).
-export([dot/6, tail_list/3, list/2]). %% Quote callbacks
-export([dot/5, tail_list/3, list/2]). %% Quote callbacks
-include("elixir.hrl").
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop).
-define(lexical(Kind), Kind == import; Kind == alias; Kind == '__aliases__').
-define(lexical(Kind), Kind == import; Kind == alias; Kind == require).
-compile({inline, [keyfind/2, keystore/3, keydelete/2, keyreplace/3, keynew/3]}).
%% Apply the line from site call on quoted contents.
@@ -23,7 +23,8 @@ do_linify(Line, Key, {Receiver, Counter} = Var, {Left, Meta, Receiver})
when is_atom(Left), is_list(Meta), Left /= '_' ->
do_tuple_linify(Line, Key, Var, keynew(counter, Meta, Counter), Left, Receiver);
do_linify(Line, Key, {_, Counter} = Var, {Lexical, [_|_] = Meta, [_|_] = Args}) when ?lexical(Lexical) ->
do_linify(Line, Key, {_, Counter} = Var, {Lexical, [_|_] = Meta, [_|_] = Args})
when ?lexical(Lexical); Lexical == '__aliases__' ->
do_tuple_linify(Line, Key, Var, keynew(counter, Meta, Counter), Lexical, Args);
do_linify(Line, Key, Var, {Left, Meta, Right}) when is_list(Meta) ->
@@ -61,8 +62,8 @@ do_linify_meta(Line, Key, Meta) ->
%% Some expressions cannot be unquoted at compilation time.
%% This function is responsible for doing runtime unquoting.
dot(Meta, Left, Right, Args, Context, File) ->
annotate(dot(Meta, Left, Right, Args), Context, File).
dot(Meta, Left, Right, Args, Context) ->
annotate(dot(Meta, Left, Right, Args), Context).
dot(Meta, Left, {'__aliases__', _, Args}, nil) ->
{'__aliases__', Meta, [Left|Args]};
@@ -117,28 +118,29 @@ validate_list(List) when not is_list(List) ->
argument_error(Message) ->
error('Elixir.ArgumentError':exception([{message,Message}])).
%% Annotates the AST with context and other info
%% Annotates the AST with context and other info.
%%
%% Note we need to delete the counter because linify
%% adds the counter recursively, even inside quoted
%% expressions, so we need to clean up the forms to
%% allow them to get a new counter on the next expansion.
annotate({Def, Meta, [{H, M, A}|T]}, Context, File) when ?defs(Def) ->
%% Store the context information in the first element of the
%% definition tuple so we can access it later on.
MM = keystore(context, keystore(file, M, File), Context),
{Def, Meta, [{H, MM, A}|T]};
annotate({{'.', _, [_, Def]} = Target, Meta, [{H, M, A}|T]}, Context, File) when ?defs(Def) ->
MM = keystore(context, keystore(file, M, File), Context),
{Target, Meta, [{H, MM, A}|T]};
annotate({Def, Meta, [{H, M, A}|T]}, Context) when ?defs(Def) ->
{Def, Meta, [{H, keystore(context, M, Context), A}|T]};
annotate({{'.', _, [_, Def]} = Target, Meta, [{H, M, A}|T]}, Context) when ?defs(Def) ->
{Target, Meta, [{H, keystore(context, M, Context), A}|T]};
annotate({Lexical, Meta, [_|_] = Args}, Context, _File) when Lexical == import; Lexical == alias ->
annotate({Lexical, Meta, [_|_] = Args}, Context) when ?lexical(Lexical) ->
NewMeta = keystore(context, keydelete(counter, Meta), Context),
{Lexical, NewMeta, Args};
annotate(Tree, _Context, _File) -> Tree.
annotate(Tree, _Context) -> Tree.
%% Escapes the given expression. It is similar to quote, but
%% lines are kept and hygiene mechanisms are disabled.
escape(Expr, Unquote) ->
{Res, Q} = quote(Expr, nil, #elixir_quote{
line=true,
keep=false,
file=nil,
vars_hygiene=false,
aliases_hygiene=false,
imports_hygiene=false,
@@ -221,11 +223,11 @@ do_quote({Name, Meta, ArgsOrAtom}, #elixir_quote{imports_hygiene=true} = Q, E) w
keystore(import, keystore(context, Meta, Q#elixir_quote.context), Receiver)
end,
Annotated = annotate({Name, NewMeta, ArgsOrAtom}, Q#elixir_quote.context, file(E, Q)),
Annotated = annotate({Name, NewMeta, ArgsOrAtom}, Q#elixir_quote.context),
do_quote_tuple(Annotated, Q, E);
do_quote({_, _, _} = Tuple, #elixir_quote{escape=false} = Q, E) ->
Annotated = annotate(Tuple, Q#elixir_quote.context, file(E, Q)),
Annotated = annotate(Tuple, Q#elixir_quote.context),
do_quote_tuple(Annotated, Q, E);
%% Literals
@@ -267,7 +269,7 @@ do_quote(Other, Q, _) ->
do_quote_call(Left, Meta, Expr, Args, Q, E) ->
All = [meta(Meta, Q), Left, {unquote, Meta, [Expr]}, Args,
Q#elixir_quote.context, file(E, Q)],
Q#elixir_quote.context],
{TAll, TQ} = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, E) end, Q, All),
{{{'.', Meta, [elixir_quote, dot]}, Meta, TAll}, TQ}.
@@ -285,16 +287,19 @@ do_quote_tuple({Left, Meta, Right}, Q, E) ->
{TRight, RQ} = do_quote(Right, LQ, E),
{{'{}', [], [TLeft, meta(Meta, Q), TRight]}, RQ}.
file(#{file := File}, #elixir_quote{keep=true}) -> File;
file(_, _) -> nil.
meta(Meta, Q) ->
file(line(Meta, Q), Q).
meta(Meta, #elixir_quote{keep=true}) ->
file(Meta, #elixir_quote{file=nil}) -> Meta;
file(Meta, #elixir_quote{file=File}) -> [{file, File}|Meta].
line(Meta, #elixir_quote{file=File}) when File /= nil ->
[case KV of {line, V} -> {keep, V}; _ -> KV end || KV <- Meta];
meta(Meta, #elixir_quote{line=true}) ->
line(Meta, #elixir_quote{line=true}) ->
Meta;
meta(Meta, #elixir_quote{line=false}) ->
line(Meta, #elixir_quote{line=false}) ->
keydelete(line, Meta);
meta(Meta, #elixir_quote{line=Line}) ->
line(Meta, #elixir_quote{line=Line}) ->
keystore(line, Meta, Line).
reverse_improper(L) -> reverse_improper(L, []).
+26 -2
View File
@@ -1,9 +1,10 @@
%% Convenience functions used to manipulate scope and its variables.
-module(elixir_scope).
-export([translate_var/4, build_var/2,
-export([translate_var/4, build_var/2, context_info/1,
load_binding/2, dump_binding/2,
mergev/2, mergec/2, mergef/2,
merge_vars/2, merge_opt_vars/2
merge_vars/2, merge_opt_vars/2,
format_error/1
]).
-include("elixir.hrl").
@@ -25,6 +26,7 @@ translate_var(Meta, Name, Kind, S) when is_atom(Kind); is_integer(Kind) ->
case Exists andalso ordsets:is_element(Tuple, MatchVars) of
true ->
warn_underscored_var(Line, S#elixir_scope.file, Name, Kind),
{{var, Line, Current}, S};
false ->
%% We attempt to give vars a nice name because we
@@ -60,6 +62,19 @@ build_var(Key, S) ->
Cnt = orddict:fetch(Key, New),
{elixir_utils:atom_concat([Key, "@", Cnt]), Cnt, S#elixir_scope{counter=New}}.
context_info(Kind) when Kind == nil; is_integer(Kind) -> "";
context_info(Kind) -> io_lib:format(" (context ~ts)", [elixir_aliases:inspect(Kind)]).
warn_underscored_var(Line, File, Name, Kind) ->
case atom_to_list(Name) of
"_@" ++ _ ->
ok; %% Automatically generated variables
"_" ++ _ ->
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {unused_match, Name, Kind});
_ ->
ok
end.
%% SCOPE MERGING
%% Receives two scopes and return a new scope based on
@@ -138,3 +153,12 @@ dump_binding([{{Var, Kind} = Key, {InternalName,_}}|T], Binding, Acc) when is_at
dump_binding([_|T], Binding, Acc) ->
dump_binding(T, Binding, Acc);
dump_binding([], _Binding, Acc) -> Acc.
%% Errors
format_error({unused_match, Name, Kind}) ->
io_lib:format("the underscored variable \"~ts\"~ts appears more than once in a "
"match. This means the pattern will only match if all \"~ts\" bind "
"to the same value. If this is the intended behaviour, please "
"remove the leading underscore from the variable name, otherwise "
"give the variables different names", [Name, context_info(Kind), Name]).
+8 -3
View File
@@ -72,7 +72,7 @@ translate({fn, Meta, Clauses}, S) ->
%% Cond
translate({'cond', _Meta, [[{do, Pairs}]]}, S) ->
translate({'cond', CondMeta, [[{do, Pairs}]]}, S) ->
[{'->', Meta, [[Condition], Body]}|T] = lists:reverse(Pairs),
Case =
case Condition of
@@ -88,7 +88,7 @@ translate({'cond', _Meta, [[{do, Pairs}]]}, S) ->
Acc = {'case', Meta, [Condition, [{do, [Truthy, Falsy]}]]},
build_cond_clauses(T, Acc, Meta)
end,
translate(Case, S);
translate(replace_case_meta(CondMeta, Case), S);
%% Case
@@ -249,7 +249,7 @@ translate({{'.', _, [Left, Right]}, Meta, Args}, S)
{atom, Line, 'true'}},
{map_field_assoc, Line,
{atom, Line, key},
{atom, Line, TRight}},
TRight},
{map_field_assoc, Line,
{atom, Line, term},
TVar}]},
@@ -387,6 +387,11 @@ build_cond_clauses([{'->', NewMeta, [[Condition], Body]}|T], Acc, OldMeta) ->
build_cond_clauses([], Acc, _) ->
Acc.
replace_case_meta(Meta, {'case', _, Args}) ->
{'case', Meta, Args};
replace_case_meta(_Meta, Other) ->
Other.
build_truthy_clause(Meta, Condition, Body) ->
case elixir_utils:returns_boolean(Condition) of
true ->
+1 -1
View File
@@ -46,7 +46,7 @@ read_link_type(File) ->
relative_to_cwd(Path) ->
case elixir_compiler:get_opt(internal) of
true -> Path;
false -> 'Elixir.String':to_char_list('Elixir.Path':relative_to_cwd(Path))
false -> 'Elixir.Path':relative_to_cwd(Path)
end.
characters_to_list(Data) when is_list(Data) ->
+3 -1
View File
@@ -20,7 +20,9 @@ defmodule AccessTest do
test "for nil" do
assert nil[:foo] == nil
assert Access.get(nil, :foo) == nil
assert Access.get_and_update(nil, :foo, fn nil -> {:ok, :bar} end) == {:ok, :bar}
assert_raise ArgumentError, "could not put/update key :foo on a nil value", fn ->
Access.get_and_update(nil, :foo, fn nil -> {:ok, :bar} end)
end
end
test "for keywords" do
+10
View File
@@ -28,6 +28,16 @@ defmodule CodeTest do
assert Code.eval_string("var!(a, Sample) = 1") == {1, [{{:a,Sample},1}]}
end
test :eval_binary_errors do
msg = "nofile:2: a binary field without size is only allowed at the end of a binary pattern"
assert_raise CompileError, msg, fn ->
Code.eval_string("""
foo = "foo"
"\\"" <> bar <> "\\"" = foo
""")
end
end
test :eval_with_unnamed_scopes do
assert {%RuntimeError{}, [a: %RuntimeError{}]} =
Code.eval_string("a = (try do (raise \"hello\") rescue e -> e end)")
+22
View File
@@ -280,6 +280,28 @@ defmodule EnumTest.List do
assert Enum.sort([5, 3, 2, 4, 1], &(&1 > &2)) == [5, 4, 3, 2, 1]
end
test :sort_by do
collection = [
[other_data: 1, sorted_data: 5],
[other_data: 3, sorted_data: 4],
[other_data: 4, sorted_data: 3],
[other_data: 2, sorted_data: 2],
[other_data: 5, sorted_data: 1]
]
assert Enum.sort_by(
collection,
&(&1[:sorted_data])
) == [
[other_data: 5, sorted_data: 1],
[other_data: 2, sorted_data: 2],
[other_data: 4, sorted_data: 3],
[other_data: 3, sorted_data: 4],
[other_data: 1, sorted_data: 5]
]
assert Enum.sort_by(collection, &(&1[:sorted_data]), &>=/2) == collection
end
test :split do
assert Enum.split([1, 2, 3], 0) == {[], [1, 2, 3]}
assert Enum.split([1, 2, 3], 1) == {[1], [2, 3]}
+1 -1
View File
@@ -5,4 +5,4 @@ defmodule AtExit do
end
System.at_exit fn(status) -> IO.puts "cruel world with status #{status}" end
AtExit.at_exit("goodbye ")
exit(0)
exit({:shutdown, 1})
+44 -26
View File
@@ -28,35 +28,53 @@ defmodule FloatTest do
end
test :floor do
assert Float.floor(12) === 12
assert Float.floor(-12) === -12
assert Float.floor(12.524235) === 12
assert Float.floor(-12.5) === -13
assert Float.floor(-12.524235) === -13
assert Float.floor(7.5e3) === 7500
assert Float.floor(7.5432e3) === 7543
assert Float.floor(7.5e-3) === 0
assert Float.floor(-12.32453e4) === -123246
assert Float.floor(-12.32453e-10) === -1
assert Float.floor(0.32453e-10) === 0
assert Float.floor(-0.32453e-10) === -1
assert Float.floor(1.32453e-10) === 0
assert Float.floor(12.524235) === 12.0
assert Float.floor(-12.5) === -13.0
assert Float.floor(-12.524235) === -13.0
assert Float.floor(7.5e3) === 7500.0
assert Float.floor(7.5432e3) === 7543.0
assert Float.floor(7.5e-3) === 0.0
assert Float.floor(-12.32453e4) === -123246.0
assert Float.floor(-12.32453e-10) === -1.0
assert Float.floor(0.32453e-10) === 0.0
assert Float.floor(-0.32453e-10) === -1.0
assert Float.floor(1.32453e-10) === 0.0
end
test :floor_with_precision do
assert Float.floor(12.524235, 0) === 12.0
assert Float.floor(-12.524235, 0) === -13.0
assert Float.floor(12.52, 2) === 12.52
assert Float.floor(-12.52, 2) === -12.52
assert Float.floor(12.524235, 2) === 12.52
assert Float.floor(-12.524235, 3) === -12.525
end
test :ceil do
assert Float.ceil(12) === 12
assert Float.ceil(-12) === -12
assert Float.ceil(12.524235) === 13
assert Float.ceil(-12.5) === -12
assert Float.ceil(-12.524235) === -12
assert Float.ceil(7.5e3) === 7500
assert Float.ceil(7.5432e3) === 7544
assert Float.ceil(7.5e-3) === 1
assert Float.ceil(-12.32453e4) === -123245
assert Float.ceil(-12.32453e-10) === 0
assert Float.ceil(0.32453e-10) === 1
assert Float.ceil(-0.32453e-10) === 0
assert Float.ceil(1.32453e-10) === 1
assert Float.ceil(12.524235) === 13.0
assert Float.ceil(-12.5) === -12.0
assert Float.ceil(-12.524235) === -12.0
assert Float.ceil(7.5e3) === 7500.0
assert Float.ceil(7.5432e3) === 7544.0
assert Float.ceil(7.5e-3) === 1.0
assert Float.ceil(-12.32453e4) === -123245.0
assert Float.ceil(-12.32453e-10) === 0.0
assert Float.ceil(0.32453e-10) === 1.0
assert Float.ceil(-0.32453e-10) === 0.0
assert Float.ceil(1.32453e-10) === 1.0
end
test :ceil_with_precision do
assert Float.ceil(12.524235, 0) === 13.0
assert Float.ceil(-12.524235, 0) === -12.0
assert Float.ceil(12.52, 2) === 12.52
assert Float.ceil(-12.52, 2) === -12.52
assert Float.ceil(12.524235, 2) === 12.53
assert Float.ceil(-12.524235, 3) === -12.524
end
test :round do
+6 -5
View File
@@ -68,7 +68,7 @@ defmodule Inspect.BitStringTest do
use ExUnit.Case, async: true
test :bitstring do
assert inspect(<<1 :: [size(12), integer, signed]>>) == "<<0, 1::size(4)>>"
assert inspect(<<1 :: size(12)-integer-signed>>) == "<<0, 1::size(4)>>"
end
test :binary do
@@ -256,15 +256,16 @@ defmodule Inspect.MapTest do
end
defimpl Inspect do
def inspect(_, _) do
raise "failing"
def inspect(struct, _) do
struct.unknown
end
end
end
test :bad_implementation do
msg = "Got RuntimeError with message \"failing\" " <>
"while inspecting %{__struct__: Inspect.MapTest.Failing, key: 0}"
msg = "Got KeyError with message \"key :unknown not found in: " <>
"%{__struct__: Inspect.MapTest.Failing, key: 0}\" while " <>
"inspecting %{__struct__: Inspect.MapTest.Failing, key: 0}"
assert_raise ArgumentError, msg, fn ->
inspect(%Failing{})
+21
View File
@@ -58,6 +58,20 @@ defmodule IOTest do
assert File.close(file) == :ok
end
test :readall do
{:ok, file} = File.open(Path.expand('fixtures/file.txt', __DIR__))
assert "FOO\n" == IO.read(file, :all)
assert "" == IO.read(file, :all)
assert File.close(file) == :ok
end
test :readall_with_utf8_and_binary do
{:ok, file} = File.open(Path.expand('fixtures/utf8.txt', __DIR__), [:utf8])
assert "Русский\n日\n" == IO.read(file, :all)
assert "" == IO.read(file, :all)
assert File.close(file) == :ok
end
test :readline do
{:ok, file} = File.open(Path.expand('fixtures/file.txt', __DIR__))
assert "FOO\n" == IO.read(file, :line)
@@ -72,6 +86,13 @@ defmodule IOTest do
assert File.close(file) == :ok
end
test :binreadall do
{:ok, file} = File.open(Path.expand('fixtures/utf8.txt', __DIR__))
assert "Русский\n日\n" == IO.binread(file, :all)
assert "" == IO.binread(file, :all)
assert File.close(file) == :ok
end
test :binreadline do
{:ok, file} = File.open(Path.expand('fixtures/utf8.txt', __DIR__))
assert "Русский\n" == IO.binread(file, :line)
+14 -14
View File
@@ -60,22 +60,22 @@ bar """
<<"f", "oo">> <> x = "foobar"
assert x == "bar"
<<x :: [binary, size(3)]>> <> _ = "foobar"
<<x :: binary-size(3)>> <> _ = "foobar"
assert x == "foo"
size = 3
<<x :: [binary, size(size)]>> <> _ = "foobar"
<<x :: binary-size(size)>> <> _ = "foobar"
assert x == "foo"
<<x :: [binary, size(6), unit(4)]>> <> _ = "foobar"
<<x :: 6*4-binary>> <> _ = "foobar"
assert x == "foo"
assert_raise ErlangError, fn ->
Code.eval_string(~s{<<x :: [binary, size(3), unit(4)]>> <> _ = "foobar"})
assert_raise CompileError, fn ->
Code.eval_string(~s{<<x :: binary-size(3)-unit(4)>> <> _ = "foobar"})
end
assert_raise ErlangError, fn ->
Code.eval_string(~s{<<x :: [integer, size(4)]>> <> _ = "foobar"})
assert_raise CompileError, fn ->
Code.eval_string(~s{<<x :: integer-size(4)>> <> _ = "foobar"})
end
end
@@ -136,7 +136,7 @@ bar """
assert <<106,111,115,195,169>> == << "josé" :: utf8 >>
assert <<0,106,0,111,0,115,0,233>> == << "josé" :: utf16 >>
assert <<106,0,111,0,115,0,233,0>> == << "josé" :: [utf16, little] >>
assert <<106,0,111,0,115,0,233,0>> == << "josé" :: little-utf16 >>
assert <<0,0,0,106,0,0,0,111,0,0,0,115,0,0,0,233>> == << "josé" :: utf32 >>
end
@@ -167,26 +167,26 @@ bar """
test :bitsyntax_translation do
refb = "sample"
sec_data = "another"
<< byte_size(refb) :: [size(1), big, signed, integer, unit(8)],
<< byte_size(refb) :: size(1)-big-signed-integer-unit(8),
refb :: binary,
byte_size(sec_data) :: [size(1), big, signed, integer, unit(16)],
byte_size(sec_data) :: 1*16-big-signed-integer,
sec_data :: binary >>
end
test :bitsyntax_size_shorcut do
assert << 1 :: 3 >> == << 1 :: size(3) >>
assert << 1 :: [unit(8), 3] >> == << 1 :: [unit(8), size(3)] >>
assert << 1 :: 3*8 >> == << 1 :: size(3)-unit(8) >>
end
defmacrop signed_16 do
quote do
[big, signed, integer, unit(16)]
big-signed-integer-unit(16)
end
end
defmacrop refb_spec do
quote do
[size(1), big, signed, integer, unit(8)]
1*8-big-signed-integer
end
end
@@ -195,7 +195,7 @@ bar """
sec_data = "another"
<< byte_size(refb) :: refb_spec,
refb :: binary,
byte_size(sec_data) :: [size(1), signed_16],
byte_size(sec_data) :: size(1)-signed_16,
sec_data :: binary >>
end
end
+5 -7
View File
@@ -59,7 +59,7 @@ defmodule Kernel.CLI.AtExitTest do
test "invokes at_exit callbacks" do
assert elixir(fixture_path("at_exit.exs") |> to_char_list) ==
'goodbye cruel world with status 0\n'
'goodbye cruel world with status 1\n'
end
end
@@ -69,9 +69,7 @@ defmodule Kernel.CLI.ErrorTest do
test "properly format errors" do
assert :string.str('** (throw) 1', elixir('-e "throw 1"')) == 0
assert :string.str('** (ErlangError) erlang error: 1', elixir('-e "error 1"')) == 0
# It does not catch exits with integers nor strings...
assert elixir('-e "exit 1"') == ''
assert elixir('-e "IO.puts(Process.flag(:trap_exit, false)); exit({:shutdown, 1})"') == 'false\n'
end
end
@@ -133,7 +131,7 @@ defmodule Kernel.CLI.ParallelCompilerTest do
test "does not hang on missing dependencies" do
fixtures = [fixture_path("parallel_compiler/bat.ex")]
assert capture_io(fn ->
assert catch_exit(Kernel.ParallelCompiler.files(fixtures)) == 1
assert catch_exit(Kernel.ParallelCompiler.files(fixtures)) == {:shutdown, 1}
end) =~ "Compilation error"
end
@@ -142,7 +140,7 @@ defmodule Kernel.CLI.ParallelCompilerTest do
fixture_path("parallel_deadlock/bar.ex")]
msg = capture_io(fn ->
assert catch_exit(Kernel.ParallelCompiler.files fixtures) == 1
assert catch_exit(Kernel.ParallelCompiler.files fixtures) == {:shutdown, 1}
end)
assert msg =~ ~r"== Compilation error on file .+parallel_deadlock/foo\.ex =="
@@ -157,7 +155,7 @@ defmodule Kernel.CLI.ParallelCompilerTest do
Code.compiler_options(warnings_as_errors: true)
capture_io :stderr, fn ->
assert catch_exit(Kernel.ParallelCompiler.files fixtures) == 1
assert catch_exit(Kernel.ParallelCompiler.files fixtures) == {:shutdown, 1}
end
after
Code.compiler_options(warnings_as_errors: warnings_as_errors)
+15 -8
View File
@@ -137,6 +137,19 @@ defmodule Kernel.ErrorsTest do
def hello(arg \\ 1), do: nil
end
'''
assert_compile_fail CompileError,
"nofile:6: def hello/1 has default values and multiple clauses, " <>
"define a function head with the defaults",
~C'''
defmodule ErrorsTest do
def bye(arg \\ 0)
def bye(arg), do: arg
def hello(arg \\ 0), do: nil
def hello(arg), do: arg
end
'''
end
test :invalid_match_pattern do
@@ -219,7 +232,7 @@ defmodule Kernel.ErrorsTest do
test :struct_access_on_body do
assert_compile_fail CompileError,
"nofile:3: cannot access struct TZ in body of the module that defines it " <>
"nofile:3: cannot access struct TZ in the same context that defines it " <>
"as the struct fields are not yet accessible",
'''
defmodule TZ do
@@ -499,7 +512,7 @@ defmodule Kernel.ErrorsTest do
test :duplicated_bitstring_size do
assert_compile_fail CompileError,
"nofile:1: duplicated size definition in bitstring",
'<<1 :: [size(12), size(13)]>>'
'<<1 :: size(12)-size(13)>>'
end
test :invalid_bitstring_specified do
@@ -524,12 +537,6 @@ defmodule Kernel.ErrorsTest do
'<<1 :: unit(:x)>>'
end
test :invalid_var! do
assert_compile_fail CompileError,
"nofile:1: expected var x to expand to an existing variable or be a part of a match",
'var!(x)'
end
test :invalid_alias do
assert_compile_fail CompileError,
"nofile:1: invalid value for keyword :as, expected an alias, got nested alias: Sample.Lists",
@@ -127,10 +127,10 @@ defmodule Kernel.ExpansionTest do
test "vars: forces variable to exist" do
assert expand(quote do: (var!(a) = 1; var!(a)))
message = ~r"expected var a to expand to an existing variable or be a part of a match"
message = ~r"expected var \"a\" to expand to an existing variable or be part of a match"
assert_raise CompileError, message, fn -> expand(quote do: var!(a)) end
message = ~r"expected var a \(context Unknown\) to expand to an existing variable or be a part of a match"
message = ~r"expected var \"a\" \(context Unknown\) to expand to an existing variable or be part of a match"
assert_raise CompileError, message, fn -> expand(quote do: var!(a, Unknown)) end
end
@@ -424,18 +424,37 @@ defmodule Kernel.ExpansionTest do
## Binaries
test "bitstrings: size * unit" do
import Kernel, except: [-: 2]
assert expand(quote do: << x :: 13 >>) ==
quote do: << x() :: size(13) >>
assert expand(quote do: << x :: 13 * 6 >>) ==
quote do: << x() :: unit(6)-size(13) >>
assert expand(quote do: << x :: _ * 6 >>) ==
quote do: << x() :: unit(6) >>
assert expand(quote do: << x :: 13 * 6 - binary >>) ==
quote do: << x() :: unit(6)-binary()-size(13) >>
assert expand(quote do: << x :: binary - 13 * 6 >>) ==
quote do: << x() :: binary()-unit(6)-size(13) >>
end
test "bitstrings: expands modifiers" do
assert expand(quote do: (import Kernel.ExpansionTarget; << x :: seventeen >>)) ==
quote do: (import :"Elixir.Kernel.ExpansionTarget", []; << x() :: [unquote(:size)(17)] >>)
quote do: (import :"Elixir.Kernel.ExpansionTarget", []; << x() :: size(17) >>)
assert expand(quote do: (import Kernel.ExpansionTarget; << seventeen :: seventeen, x :: size(seventeen) >> = 1)) ==
quote do: (import :"Elixir.Kernel.ExpansionTarget", [];
<< seventeen :: [unquote(:size)(17)], x :: [unquote(:size)(seventeen)] >> = 1)
<< seventeen :: size(17), x :: size(seventeen) >> = 1)
end
test "bitstrings: expands modifiers args" do
assert expand(quote do: (require Kernel.ExpansionTarget; << x :: size(Kernel.ExpansionTarget.seventeen) >>)) ==
quote do: (require :"Elixir.Kernel.ExpansionTarget", []; << x() :: [unquote(:size)(17)] >>)
quote do: (require :"Elixir.Kernel.ExpansionTarget", []; << x() :: size(17) >>)
end
## Invalid
+5 -4
View File
@@ -13,7 +13,8 @@ defmodule Kernel.QuoteTest do
test :keep_line do
## DO NOT MOVE THIS LINE
assert quote(location: :keep, do: bar(1, 2, 3)) == {:bar, [keep: 16], [1, 2, 3]}
assert quote(location: :keep, do: bar(1, 2, 3)) ==
{:bar, [file: Path.relative_to_cwd(__ENV__.file), keep: 16], [1, 2, 3]}
end
test :fixed_line do
@@ -23,7 +24,7 @@ defmodule Kernel.QuoteTest do
test :quote_line_var do
## DO NOT MOVE THIS LINE
line = __ENV__.line
assert quote(line: line, do: bar(1, 2, 3)) == {:bar, [line: 25], [1, 2, 3]}
assert quote(line: line, do: bar(1, 2, 3)) == {:bar, [line: 26], [1, 2, 3]}
end
test :unquote_call do
@@ -201,7 +202,7 @@ defmodule Kernel.QuoteTest.ErrorsTest do
mod = Kernel.QuoteTest.ErrorsTest
file = __ENV__.file |> Path.relative_to_cwd |> String.to_char_list
assert [{^mod, :add, 2, [file: ^file, line: 181]}|_] = System.stacktrace
assert [{^mod, :add, 2, [file: ^file, line: 182]}|_] = System.stacktrace
end
test :outside_function_error do
@@ -211,7 +212,7 @@ defmodule Kernel.QuoteTest.ErrorsTest do
mod = Kernel.QuoteTest.ErrorsTest
file = __ENV__.file |> Path.relative_to_cwd |> String.to_char_list
assert [{^mod, _, _, [file: ^file, line: 209]}|_] = System.stacktrace
assert [{^mod, _, _, [file: ^file, line: 210]}|_] = System.stacktrace
end
end
@@ -25,6 +25,7 @@ defmodule Kernel.SigilsTest do
assert ~S/foo/ == "foo"
assert ~S|foo| == "foo"
assert ~S(f#{o}o) == "f\#{o}o"
assert ~S(f\#{o}o) == "f\\\#{o}o"
assert ~S(f\no) == "f\\no"
end
+81 -39
View File
@@ -3,6 +3,10 @@ Code.require_file "../test_helper.exs", __DIR__
defmodule Kernel.TypespecTest do
use ExUnit.Case, async: true
alias Kernel.TypespecTest.TestTypespec
defstruct [:hello]
# This macro allows us to focus on the result of the
# definition and not on the hassles of handling test
# module
@@ -174,28 +178,86 @@ defmodule Kernel.TypespecTest do
test "@type with a struct" do
module = test_module do
@type mytype :: %User{hello: :world}
defstruct [hello: nil, eric: nil]
@type mytype :: %TestTypespec{hello: :world}
end
assert [type: {:mytype,
{:type, _, :map, [
{:type, _, :map_field_assoc, {:atom, _, :__struct__}, {:atom, _, User}},
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}}
{:type, _, :map_field_assoc, {:atom, _, :__struct__}, {:atom, _, TestTypespec}},
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}},
{:type, _, :map_field_assoc, {:atom, _, :eric}, {:type, _, :term, []}}
]},
[]}] = types(module)
end
test "@type with a tuple" do
module = test_module do
@type mytype :: tuple
@type mytype1 :: {}
@type mytype2 :: {1, 2}
test "@type with undefined struct" do
assert_raise UndefinedFunctionError, fn ->
test_module do
@type mytype :: %ThisModuleDoesNotExist{}
end
end
assert [type: {:mytype, {:type, _, :tuple, :any}, []},
type: {:mytype1, {:type, _, :tuple, []}, []},
type: {:mytype2, {:type, _, :tuple, [{:integer, _, 1}, {:integer, _, 2}]}, []}] =
types(module)
assert_raise CompileError, ~r"struct is not defined for TestTypespec", fn ->
test_module do
@type mytype :: %TestTypespec{}
end
end
end
test "@type with a struct with undefined field" do
assert_raise CompileError, ~r"undefined field no_field on struct TestTypespec", fn ->
test_module do
defstruct [hello: nil, eric: nil]
@type mytype :: %TestTypespec{no_field: :world}
end
end
end
test "@type with public record" do
module = test_module do
require Record
Record.defrecord :timestamp, [date: 1, time: 2]
@type mytype :: record(:timestamp, time: :foo)
end
assert [type: {:mytype,
{:type, _, :tuple, [
{:atom, 0, :timestamp}, {:atom, 0, :foo}, {:type, 0, :term, []}
]},
[]}] = types(module)
end
test "@type with private record" do
module = test_module do
require Record
Record.defrecordp :timestamp, [date: 1, time: 2]
@type mytype :: record(:timestamp, time: :foo)
end
assert [type: {:mytype,
{:type, _, :tuple, [
{:atom, 0, :timestamp}, {:atom, 0, :foo}, {:type, 0, :term, []}
]},
[]}] = types(module)
end
test "@type with undefined record" do
assert_raise CompileError, ~r"unknown record :this_record_does_not_exist", fn ->
test_module do
@type mytype :: record(:this_record_does_not_exist, [])
end
end
end
test "@type with a record with undefined field" do
assert_raise CompileError, ~r"undefined field no_field on record :timestamp", fn ->
test_module do
require Record
Record.defrecord :timestamp, [date: 1, time: 2]
@type mytype :: record(:timestamp, no_field: :foo)
end
end
end
test "@type with list shortcuts" do
@@ -320,31 +382,6 @@ defmodule Kernel.TypespecTest do
types(module)
end
test "@type from structs" do
module = test_module do
defstruct name: nil, age: 0 :: non_neg_integer
end
assert [type: {:t, {:type, _, :map, [
{:type, _, :map_field_assoc, {:atom, _, :name}, {:type, _, :term, []}},
{:type, _, :map_field_assoc, {:atom, _, :age}, {:type, _, :non_neg_integer, []}},
{:type, _, :map_field_assoc, {:atom, _, :__struct__}, {:atom, _, TestTypespec}}
]}, []}] = types(module)
end
test "@type from dynamic structs" do
module = test_module do
fields = [name: nil, age: 0]
defstruct fields
end
assert [type: {:t, {:type, _, :map, [
{:type, _, :map_field_assoc, {:atom, _, :name}, {:type, _, :term, []}},
{:type, _, :map_field_assoc, {:atom, _, :age}, {:type, _, :term, []}},
{:type, _, :map_field_assoc, {:atom, _, :__struct__}, {:atom, _, TestTypespec}}
]}, []}] = types(module)
end
test "@type unquote fragment" do
module = test_module do
quoted = quote unquote: false do
@@ -473,11 +510,16 @@ defmodule Kernel.TypespecTest do
(quote do: @type opts() :: [first: integer(), step: integer(), last: integer()]),
(quote do: @type ops() :: {+1,-1}),
(quote do: @type my_map() :: %{hello: :world}),
(quote do: @type my_struct() :: %User{hello: :world}),
(quote do: @type my_struct() :: %Kernel.TypespecTest{hello: :world}),
(quote do: @type list1() :: list()),
(quote do: @type list2() :: [0]),
(quote do: @type list3() :: [...]),
(quote do: @type list4() :: [0, ...]),
(quote do: @type nil_list() :: []),
] |> Enum.sort
module = test_module do
Module.eval_quoted __MODULE__, quote do: (unquote_splicing(quoted))
Module.eval_quoted __MODULE__, quoted
end
types = types(module)
+25 -3
View File
@@ -20,6 +20,16 @@ defmodule Kernel.WarningTest do
purge Sample
end
test :underscored_variable_on_match do
assert capture_err(fn ->
Code.eval_string """
{_arg, _arg} = {1, 1}
"""
end) =~ "warning: the underscored variable \"_arg\" appears more than once in a match"
after
purge Sample
end
test :unused_function do
assert capture_err(fn ->
Code.eval_string """
@@ -195,7 +205,7 @@ defmodule Kernel.WarningTest do
end
end
"""
end) =~ "nofile:5: warning: the guard for this clause evaluates to 'false'"
end) =~ "nofile:5: warning: this check/guard will always yield the same result"
assert capture_err(fn ->
Code.eval_string """
@@ -221,7 +231,7 @@ defmodule Kernel.WarningTest do
def hello
end
"""
end) =~ "warning: empty clause provided for nonexistent function or macro hello/0"
end) =~ "warning: bodyless clause provided for nonexistent def hello/0"
after
purge [Sample1]
end
@@ -347,7 +357,7 @@ defmodule Kernel.WarningTest do
def a(x) when x in [], do: x
end
"""
end) =~ "warning: the guard for this clause evaluates to 'false'"
end) =~ "nofile:2: warning: this check/guard will always yield the same result"
after
purge Sample
end
@@ -482,6 +492,18 @@ defmodule Kernel.WarningTest do
purge [Sample]
end
test :doc_with_no_function do
assert capture_err(fn ->
Code.eval_string """
defmodule Sample do
@doc "Something"
end
"""
end) =~ "nofile:1: warning: @doc provided but no definition follows it"
after
purge [Sample]
end
defp purge(list) when is_list(list) do
Enum.each list, &purge/1
end
+41 -34
View File
@@ -173,7 +173,7 @@ defmodule KernelTest do
dynamic = :dynamic_flatten
defdelegate unquote(dynamic)(list), to: List, as: :flatten
test "defdelefate/2" do
test "defdelegate/2" do
assert my_flatten([[1]]) == [1]
end
@@ -191,6 +191,11 @@ defmodule KernelTest do
assert get_in(users, ["dave", :age]) == nil
assert get_in(nil, ["josé", :age]) == nil
map = %{"fruits" => ["banana", "apple", "orange"]}
assert get_in(map, ["fruits", by_index(0)]) == "banana"
assert get_in(map, ["fruits", by_index(3)]) == nil
assert get_in(map, ["unknown", by_index(3)]) == :oops
assert_raise FunctionClauseError, fn ->
get_in(users, [])
end
@@ -199,18 +204,16 @@ defmodule KernelTest do
test "put_in/3" do
users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
assert put_in(nil, ["josé", :age], 28) ==
%{"josé" => %{age: 28}}
assert put_in(users, ["josé", :age], 28) ==
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
assert put_in(users, ["dave", :age], 19) ==
%{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 19}}
assert_raise FunctionClauseError, fn ->
put_in(users, [], %{})
end
assert_raise ArgumentError, "could not put/update key \"josé\" on a nil value", fn ->
put_in(nil, ["josé", :age], 28)
end
end
test "put_in/2" do
@@ -222,14 +225,10 @@ defmodule KernelTest do
assert put_in(users["josé"][:age], 28) ==
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
assert put_in(users["dave"][:age], 19) ==
%{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 19}}
assert put_in(users["josé"].age, 28) ==
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
assert_raise ArgumentError, fn ->
assert_raise ArgumentError, "could not put/update key :age. Expected map/struct, got: nil", fn ->
put_in(users["dave"].age, 19)
end
@@ -241,36 +240,28 @@ defmodule KernelTest do
test "update_in/3" do
users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
assert update_in(nil, ["josé", :age], fn nil -> 28 end) ==
%{"josé" => %{age: 28}}
assert update_in(users, ["josé", :age], &(&1 + 1)) ==
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
assert update_in(users, ["dave", :age], fn nil -> 19 end) ==
%{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 19}}
assert_raise FunctionClauseError, fn ->
update_in(users, [], fn _ -> %{} end)
end
assert_raise ArgumentError, "could not put/update key \"josé\" on a nil value", fn ->
update_in(nil, ["josé", :age], fn _ -> %{} end)
end
end
test "update_in/2" do
users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
assert update_in(nil["josé"][:age], fn nil -> 28 end) ==
%{"josé" => %{age: 28}}
assert update_in(users["josé"][:age], &(&1 + 1)) ==
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
assert update_in(users["dave"][:age], fn nil -> 19 end) ==
%{"josé" => %{age: 27}, "eric" => %{age: 23}, "dave" => %{age: 19}}
assert update_in(users["josé"].age, &(&1 + 1)) ==
%{"josé" => %{age: 28}, "eric" => %{age: 23}}
assert_raise ArgumentError, fn ->
assert_raise ArgumentError, "could not put/update key :age. Expected map/struct, got: nil", fn ->
update_in(users["dave"].age, &(&1 + 1))
end
@@ -282,12 +273,19 @@ defmodule KernelTest do
test "get_and_update_in/3" do
users = %{"josé" => %{age: 27}, "eric" => %{age: 23}}
assert get_and_update_in(nil, ["josé", :age], fn nil -> {:ok, 28} end) ==
{:ok, %{"josé" => %{age: 28}}}
assert get_and_update_in(users, ["josé", :age], &{&1, &1 + 1}) ==
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}}
map = %{"fruits" => ["banana", "apple", "orange"]}
assert get_and_update_in(map, ["fruits", by_index(0)], &{&1, String.reverse(&1)}) ==
{"banana", %{"fruits" => ["ananab", "apple", "orange"]}}
assert get_and_update_in(map, ["fruits", by_index(3)], &{&1, &1}) ==
{nil, %{"fruits" => ["banana", "apple", "orange"]}}
assert get_and_update_in(map, ["unknown", by_index(3)], &{&1, []}) ==
{:oops, %{"fruits" => ["banana", "apple", "orange"], "unknown" => []}}
assert_raise FunctionClauseError, fn ->
update_in(users, [], fn _ -> %{} end)
end
@@ -302,7 +300,7 @@ defmodule KernelTest do
assert get_and_update_in(users["josé"].age, &{&1, &1 + 1}) ==
{27, %{"josé" => %{age: 28}, "eric" => %{age: 23}}}
assert_raise ArgumentError, fn ->
assert_raise ArgumentError, "could not put/update key :age. Expected map/struct, got: nil", fn ->
get_and_update_in(users["dave"].age, &{&1, &1 + 1})
end
@@ -319,7 +317,7 @@ defmodule KernelTest do
assert put_in(KernelTest.empty_map()[:foo], "bar") == %{foo: "bar"}
assert put_in(__MODULE__.empty_map()[:foo], "bar") == %{foo: "bar"}
assert_raise ArgumentError, ~r"access at least one field,", fn ->
assert_raise ArgumentError, ~r"access at least one element,", fn ->
Code.eval_quoted(quote(do: put_in(map, "bar")), [])
end
@@ -330,6 +328,18 @@ defmodule KernelTest do
def empty_map, do: %{}
def by_index(index) do
fn
_, nil, next ->
next.(:oops)
:get, data, next ->
next.(Enum.at(data, index))
:get_and_update, data, next ->
{get, update} = next.(Enum.at(data, index))
{get, List.replace_at(data, index, update)}
end
end
defmodule PipelineOp do
use ExUnit.Case, async: true
@@ -349,12 +359,9 @@ defmodule KernelTest do
assert Enum.map([1, 2, 3], &(&1 |> twice |> twice)) == [4, 8, 12]
end
test "non-call" do
test "anonymous functions" do
assert 1 |> (&(&1*2)).() == 2
assert [1] |> (&hd(&1)).() == 1
import CompileAssertion
assert_compile_fail ArgumentError, "cannot pipe 1 into 2", "1 |> 2"
end
defp twice(a), do: a * 2
+11 -1
View File
@@ -275,6 +275,16 @@ defmodule MacroTest do
assert Macro.to_string(quote do: Foo.Bar.baz([1, 2, 3])) == "Foo.Bar.baz([1, 2, 3])"
end
test :sigil_call_to_string do
assert Macro.to_string(quote do: ~r"123") == ~s/~r"123"/
assert Macro.to_string(quote do: ~r"123"u) == ~s/~r"123"u/
assert Macro.to_string(quote do: ~r"\n123") == ~s/~r"\\\\n123"/
assert Macro.to_string(quote do: ~R"123") == ~s/~R"123"/
assert Macro.to_string(quote do: ~R"123"u) == ~s/~R"123"u/
assert Macro.to_string(quote do: ~R"\n123") == ~s/~R"\\\\n123"/
end
test :arrow_to_string do
assert Macro.to_string(quote do: foo(1, (2 -> 3))) == "foo(1, (2 -> 3))"
end
@@ -479,7 +489,7 @@ defmodule MacroTest do
assert Macro.pipe(1, quote(do: foo), -1) == quote(do: foo(1))
assert Macro.pipe(2, quote(do: foo(1)), -1) == quote(do: foo(1, 2))
assert_raise ArgumentError, "cannot pipe 1 into 2", fn ->
assert_raise ArgumentError, ~r"cannot pipe 1 into 2", fn ->
Macro.pipe(1, 2, 0)
end
end
@@ -54,6 +54,19 @@ defmodule Module.LocalsTrackerTest do
assert {:private, 1} in D.reachable(config[:pid])
end
test "can yank and reattach nodes", config do
D.add_definition(config[:pid], :def, {:foo, 1})
D.add_local(config[:pid], {:foo, 1}, {:bar, 1})
D.add_definition(config[:pid], :defp, {:bar, 1})
{infoo, outfoo} = D.yank(config[:pid], {:foo, 1})
{inbar, outbar} = D.yank(config[:pid], {:bar, 1})
D.reattach(config[:pid], :defp, {:bar, 1}, {inbar, outbar})
D.reattach(config[:pid], :def, {:foo, 1}, {infoo, outfoo})
assert {:bar, 1} in D.reachable(config[:pid])
end
@unused [
{{:private, 1}, :defp, 0}
]
@@ -62,11 +75,11 @@ defmodule Module.LocalsTrackerTest do
D.add_definition(config[:pid], :def, {:public, 1})
unused = D.collect_unused_locals(config[:pid], @unused)
assert unused == [{:unused_def, {:private, 1}, :defp}]
assert unused == {[private: 1], [{:unused_def, {:private, 1}, :defp}]}
D.add_local(config[:pid], {:public, 1}, {:private, 1})
unused = D.collect_unused_locals(config[:pid], @unused)
refute unused == [{:unused_def, {:private, 1}, :defp}]
assert unused == {[], []}
end
@unused [
@@ -77,25 +90,25 @@ defmodule Module.LocalsTrackerTest do
D.add_definition(config[:pid], :def, {:public, 1})
unused = D.collect_unused_locals(config[:pid], @unused)
assert unused == [{:unused_def, {:private, 3}, :defp}]
assert unused == {[private: 3], [{:unused_def, {:private, 3}, :defp}]}
D.add_local(config[:pid], {:public, 1}, {:private, 3})
unused = D.collect_unused_locals(config[:pid], @unused)
assert unused == [{:unused_args, {:private, 3}}]
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}]
assert unused == {[private: 3], [{: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 == []
assert unused == {[private: 3], []}
end
## Defaults
@@ -147,4 +160,15 @@ defmodule Module.LocalsTrackerTest do
D.add_import(config[:pid], {:foo, 2}, Module, {:conflict, 1})
assert {[Module], :conflict, 1} in D.collect_imports_conflicts(config[:pid], [conflict: 1])
end
defmodule NoPrivate do
defmacrop foo(), do: bar()
defp bar(), do: :baz
def baz(), do: foo()
end
test "does not include unreachable locals" do
assert NoPrivate.module_info(:functions) ==
[__info__: 1, baz: 0, module_info: 0, module_info: 1]
end
end
+40 -16
View File
@@ -48,7 +48,7 @@ defmodule OptionParserTest do
assert OptionParser.parse(["--no-bool"], strict: [])
== {[], [], [{"--no-bool", nil}]}
assert OptionParser.parse(["--no-bool=...", "other"])
== {[], ["other"], [{"--no-bool", "..."}]}
== {[no_bool: "..."], ["other"], []}
end
test "does not parse -- as an alias" do
@@ -103,7 +103,7 @@ defmodule OptionParserTest do
assert OptionParser.parse(["--value"], switches: [value: :string])
== {[], [], [{"--value", nil}]}
assert OptionParser.parse(["--no-value"], switches: [value: :string])
== {[], [], [{"--no-value", nil}]}
== {[no_value: true], [], []}
end
test "parses configured integers" do
@@ -134,11 +134,6 @@ defmodule OptionParserTest do
== {[], ["foo"], [{"--value", "WAT"}]}
end
test "parses no switches as flags" do
assert OptionParser.parse(["--no-docs", "foo"])
== {[no_docs: true], ["foo"], []}
end
test "overrides options by default" do
assert OptionParser.parse(["--require", "foo", "--require", "bar", "baz"])
== {[require: "bar"], ["baz"], []}
@@ -157,17 +152,17 @@ defmodule OptionParserTest do
end
test "stops on --" do
options = OptionParser.parse(["--source", "from_docs/", "--", "1", "2", "3"])
assert options == {[source: "from_docs/"], ["1", "2", "3"], []}
options = OptionParser.parse(["--source", "foo", "--", "1", "2", "3"])
assert options == {[source: "foo"], ["1", "2", "3"], []}
options = OptionParser.parse_head(["--source", "from_docs/", "--", "1", "2", "3"])
assert options == {[source: "from_docs/"], ["1", "2", "3"], []}
options = OptionParser.parse_head(["--source", "foo", "--", "1", "2", "3"])
assert options == {[source: "foo"], ["1", "2", "3"], []}
options = OptionParser.parse(["--no-dash", "foo", "bar", "--", "-x"])
assert options == {[no_dash: true], ["foo", "bar", "-x"], []}
options = OptionParser.parse(["--source", "foo", "bar", "--", "-x"])
assert options == {[source: "foo"], ["bar", "-x"], []}
options = OptionParser.parse_head(["--no-dash", "foo", "bar", "--", "-x"])
assert options == {[no_dash: true], ["foo", "bar", "--", "-x"], []}
options = OptionParser.parse_head(["--source", "foo", "bar", "--", "-x"])
assert options == {[source: "foo"], ["bar", "--", "-x"], []}
end
test "goes beyond the first non option arguments" do
@@ -288,6 +283,35 @@ defmodule OptionParserTest do
assert OptionParser.next(["--bool=", "..."], config)
== {:invalid, "--bool", "", ["..."]}
assert OptionParser.next(["--no-bool=", "..."], config)
== {:undefined, "--no-bool", "", ["..."]}
== {:invalid, "--no-bool", "", ["..."]}
end
test "split" do
assert OptionParser.split(~S[])
== []
assert OptionParser.split(~S[foo])
== ["foo"]
assert OptionParser.split(~S[foo bar])
== ["foo", "bar"]
assert OptionParser.split(~S[ foo bar ])
== ["foo", "bar"]
assert OptionParser.split(~S[foo\ bar])
== ["foo bar"]
assert OptionParser.split(~S[foo" bar"])
== ["foo bar"]
assert OptionParser.split(~S[foo\" bar\"])
== ["foo\"", "bar\""]
assert OptionParser.split(~S[foo "\ bar\""])
== ["foo", "\\ bar\""]
assert OptionParser.split(~S[foo '\"bar"\'\ '])
== ["foo", "\\\"bar\"'\\ "]
end
end
+10 -5
View File
@@ -30,7 +30,7 @@ defmodule PathTest do
end
if is_win? do
test :relative do
test :relative_win do
assert Path.relative("C:/usr/local/bin") == "usr/local/bin"
assert Path.relative("C:\\usr\\local\\bin") == "usr\\local\\bin"
assert Path.relative("C:usr\\local\\bin") == "usr\\local\\bin"
@@ -40,7 +40,7 @@ defmodule PathTest do
assert Path.relative("../usr/local/bin") == "../usr/local/bin"
end
test :type do
test :type_win do
assert Path.type("C:/usr/local/bin") == :absolute
assert Path.type('C:\\usr\\local\\bin') == :absolute
assert Path.type("C:usr\\local\\bin") == :volumerelative
@@ -49,15 +49,20 @@ defmodule PathTest do
assert Path.type('usr/local/bin') == :relative
assert Path.type("../usr/local/bin") == :relative
end
test :split_win do
assert Path.split("C:\\foo\\bar") == ["c:/", "foo", "bar"]
assert Path.split("C:/foo/bar") == ["c:/", "foo", "bar"]
end
else
test :relative do
test :relative_unix do
assert Path.relative("/usr/local/bin") == "usr/local/bin"
assert Path.relative("usr/local/bin") == "usr/local/bin"
assert Path.relative("../usr/local/bin") == "../usr/local/bin"
assert Path.relative(['/usr', ?/, "local/bin"]) == "usr/local/bin"
end
test :type do
test :type_unix do
assert Path.type("/usr/local/bin") == :absolute
assert Path.type("usr/local/bin") == :relative
assert Path.type("../usr/local/bin") == :relative
@@ -201,7 +206,7 @@ defmodule PathTest do
assert Path.join([?/, "foo"], "./bar") == "/foo/bar"
end
test :split_with_binary do
test :split do
assert Path.split("") == []
assert Path.split("foo") == ["foo"]
assert Path.split("/foo/bar") == ["/", "foo", "bar"]
+12
View File
@@ -67,6 +67,8 @@ defmodule RecordTest do
user(name: name) = record
assert name == "Eric"
assert user(:name) == 1
end
test "records with no tag" do
@@ -76,6 +78,16 @@ defmodule RecordTest do
test "records with dynamic arguments" do
record = file_info()
assert file_info(record, :size) == :undefined
record = user()
assert user(record) == [name: "José", age: 25]
assert user(user()) == [name: "José", age: 25]
msg = "expected argument to be a literal atom, literal keyword or a :file_info record, " <>
"got runtime: {RecordTest, \"José\", 25}"
assert_raise ArgumentError, msg, fn ->
file_info(record)
end
end
test "records visibility" do
+5
View File
@@ -29,6 +29,7 @@ defmodule RegexTest do
assert Regex.regex?(regex)
assert {:error, _} = Regex.compile("*foo")
assert {:error, _} = Regex.compile("foo", "y")
assert {:error, _} = Regex.compile("foo", "uy")
end
test :compile_with_erl_opts do
@@ -80,6 +81,10 @@ defmodule RegexTest do
assert "josé" =~ ~r"\p{Latin}$"u
refute "£" =~ ~r/\p{Lu}/u
# Non breaking space matches [[:space:]] with unicode
assert <<0xA0::utf8>> =~ ~r/[[:space:]]/u
assert <<0xA0::utf8>> =~ ~r/\s/u
assert <<?<, 255, ?>>> =~ ~r/<.>/
refute <<?<, 255, ?>>> =~ ~r/<.>/u
end
+10
View File
@@ -282,6 +282,16 @@ defmodule StreamTest do
assert Process.get(:stream_flat_map)
end
test "flat_map/2 with inner flat_map/2" do
stream = Stream.flat_map(1..5, fn x ->
Stream.flat_map([x], fn x ->
x .. x * x
end) |> Stream.map(& &1 * 1)
end)
assert Enum.take(stream, 5) == [1, 2, 3, 4, 3]
end
test "into/2 and run/1" do
Process.put(:stream_cont, [])
Process.put(:stream_done, false)
+1 -1
View File
@@ -81,7 +81,7 @@ defmodule String.Chars.ErrorsTest do
assert_raise Protocol.UndefinedError,
"protocol String.Chars not implemented for <<0, 1::size(4)>>, " <>
"cannot convert a bitstring to a string", fn ->
to_string(<<1 :: [size(12), integer, signed]>>)
to_string(<<1 :: size(12)-integer-signed>>)
end
end
+14 -17
View File
@@ -4,20 +4,20 @@ defmodule SystemTest do
use ExUnit.Case
import PathHelpers
test "build_info" do
test "build_info/0" do
assert is_map System.build_info
assert not nil?(System.build_info[:version])
assert not nil?(System.build_info[:tag])
assert not nil?(System.build_info[:date])
end
test "cwd" do
test "cwd/0" do
assert is_binary System.cwd
assert is_binary System.cwd!
end
if :file.native_name_encoding == :utf8 do
test "cwd_with_utf8" do
test "cwd/0 with utf8" do
File.mkdir_p(tmp_path("héllò"))
File.cd!(tmp_path("héllò"), fn ->
@@ -28,17 +28,17 @@ defmodule SystemTest do
end
end
test "user_home" do
test "user_home/0" do
assert is_binary System.user_home
assert is_binary System.user_home!
end
test "tmp_dir" do
test "tmp_dir/0" do
assert is_binary System.tmp_dir
assert is_binary System.tmp_dir!
end
test "argv" do
test "argv/0" do
list = elixir('-e "IO.inspect System.argv" -- -o opt arg1 arg2 --long-opt 10')
{args, _} = Code.eval_string list, []
assert args == ["-o", "opt", "arg1", "arg2", "--long-opt", "10"]
@@ -46,7 +46,7 @@ defmodule SystemTest do
@test_var "SYSTEM_ELIXIR_ENV_TEST_VAR"
test "env" do
test "*_env/*" do
assert System.get_env(@test_var) == nil
System.put_env(@test_var, "SAMPLE")
assert System.get_env(@test_var) == "SAMPLE"
@@ -59,20 +59,17 @@ defmodule SystemTest do
assert System.get_env(@test_var) == "OTHER_SAMPLE"
end
test "cmd" do
assert is_binary(System.cmd "echo hello")
assert is_list(System.cmd 'echo hello')
test "cmd/1" do
assert {"hello\n", 0} = System.cmd "echo", ["hello"]
opts = [into: [], cd: System.cwd!, env: %{"foo" => "bar"},
arg0: "hecho", stderr_to_stdout: true, parallelism: true]
assert {["hello\n"], 0} = System.cmd "echo", ["hello"], opts
end
test "find_executable with binary" do
test "find_executable/1" do
assert System.find_executable("erl")
assert is_binary System.find_executable("erl")
assert !System.find_executable("does-not-really-exist-from-elixir")
end
test "find_executable with list" do
assert System.find_executable('erl')
assert is_list System.find_executable('erl')
assert !System.find_executable('does-not-really-exist-from-elixir')
end
end
+9 -3
View File
@@ -253,13 +253,19 @@ oror_test() ->
end,
test_helper:run_and_remove(F, ['Elixir.Bar']).
cond_line_test() ->
{'case',1,_,
[{clause,2,_,_,_},
{clause,3,_,_,_}]
} = to_erl("cond do\n 1 -> :ok\n 2 -> :ok\nend").
% Optimized
optimized_if_test() ->
{'case', _, _,
[{clause,_,[{atom,_,false}],[],[{atom,_,else}]},
{clause,_,[{atom,_,true}],[],[{atom,_,do}]}]
} = to_erl("if is_list([]), do: :do, else: :else").
} = to_erl("if is_list([]), do: :do, else: :else").
optimized_andand_test() ->
{'case', _, _,
@@ -268,7 +274,7 @@ optimized_andand_test() ->
[[{op,_,'orelse',_,_}]],
[{var,_,Var}]},
{clause,_,[{var,_,'_'}],[],[{atom,0,done}]}]
} = to_erl("is_list([]) && :done").
} = to_erl("is_list([]) && :done").
optimized_oror_test() ->
{'case', _, _,
@@ -277,7 +283,7 @@ optimized_oror_test() ->
[[{op,1,'orelse',_,_}]],
[{atom,0,done}]},
{clause,1,[{var,1,Var}],[],[{var,1,Var}]}]
} = to_erl("is_list([]) || :done").
} = to_erl("is_list([]) || :done").
no_after_in_try_test() ->
{'try', _, [_], [_], _, []} = to_erl("try do :foo.bar() else _ -> :ok end").
+22 -11
View File
@@ -74,11 +74,18 @@ defmodule ExUnit do
* `:tags` - the test tags
"""
defstruct name: nil :: atom,
case: nil :: module,
state: nil :: ExUnit.state,
time: 0 :: non_neg_integer,
tags: %{} :: map
defstruct name: nil,
case: nil,
state: nil,
time: 0,
tags: %{}
@type t :: %__MODULE__{
name: atom,
case: module,
state: ExUnit.state,
time: non_neg_integer,
tags: map}
end
defmodule TestCase do
@@ -92,9 +99,14 @@ defmodule ExUnit do
* `:tests` - all tests for this case
"""
defstruct name: nil :: module,
state: nil :: ExUnit.state,
tests: [] :: [ExUnit.Test.t]
defstruct name: nil,
state: nil,
tests: []
@type t :: %__MODULE__{
name: module,
state: ExUnit.state,
tests: [ExUnit.Test.t]}
end
use Application
@@ -120,8 +132,7 @@ defmodule ExUnit do
If you want to run tests manually, you can set `:autorun` to `false`.
"""
def start(options \\ []) do
Application.start(:elixir)
Application.start(:ex_unit)
{:ok, _} = Application.ensure_all_started(:ex_unit)
configure(options)
@@ -132,7 +143,7 @@ defmodule ExUnit do
0 ->
%{failures: failures} = ExUnit.run
System.at_exit fn _ ->
if failures > 0, do: System.halt(1)
if failures > 0, do: exit({:shutdown, 1})
end
_ ->
:ok
+7
View File
@@ -339,6 +339,13 @@ defmodule ExUnit.DocTest do
defp extract(module) do
all_docs = Code.get_docs(module, :all)
unless all_docs do
raise Error, message:
"could not retrieve the documentation for module #{inspect module}. " <>
"The module was not compiled with documentation or its beam file cannot be accessed"
end
moduledocs = extract_from_moduledoc(all_docs[:moduledoc])
docs = for doc <- all_docs[:docs] do
+1 -1
View File
@@ -255,7 +255,7 @@ defmodule ExUnit.Runner do
end
defp shuffle(%{seed: seed}, list) do
:random.seed(3172, 9814, seed)
_ = :random.seed(3172, 9814, seed)
Enum.shuffle(list)
end

Some files were not shown because too many files have changed in this diff Show More