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

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