Compare commits

...
639 Commits
Author SHA1 Message Date
José Valim 327063cc84 Release v1.16.3 2024-05-21 00:14:25 +02:00
José Valim 795d0a9583 More fixes 2024-05-20 16:51:13 +02:00
José Valim 1150ebad03 Update CHANGELOG 2024-05-06 12:50:25 +02:00
José Valim 18feec4cd8 Add brackets around keyword lists when formatting with when, closes #13503 2024-05-06 12:44:54 +02:00
José Valim 7d5920206b Check that docs are not hidden in suite, closes #13516 2024-05-02 01:26:48 +02:00
José Valim a694447601 Ensure translators are persisted across logger restarts 2024-05-01 11:40:12 +02:00
José Valim 19ae388dda Add .bat/.com disclaimers to System.cmd and Port 2024-04-17 11:29:02 +02:00
José Valim 6b5edea5f1 Fix --dbg handling in bin/elixir, closes #13482 2024-04-08 09:31:20 +02:00
Arne Bedurftig b736c23af9 Fix typo in basic types (#13481)
The exclamation mark was missing in the result of the concatenation.
2024-04-07 20:58:08 +02:00
Theodor Fiedler 63f0000ace Translate :undefined URI port to nil (#13464)
Resolves #13462

When a url string is schema less and the host is followed by a colon
without setting the actual port, `:uri_string.parse/1` returns the
port to be `:undefined`. As long as the url string is parseable,
we should translate `:undefined` to `nil`, in order to ensure we return
a valid URI struct.
2024-04-04 12:43:16 +02:00
José Valim 5e8b187d08 Ensure compile paths are available during compilation, closes #13458 2024-04-01 12:18:01 +02:00
Jean Klingler aea1a47b8d Only infer size in pinned variable when needed (#13423) 2024-03-20 19:00:17 +09:00
Jean Klingler eeba8e992d Fix Enum.slide/3 example in cheatsheet (#13054) 2024-03-16 17:09:52 +01:00
Steven C dd42dd8620 Fix typo around Enum.slide/3 in the Enum cheatsheet (#13053) 2024-03-16 17:09:45 +01:00
José Valim ed1ddfd338 Add parens to private macro example, closes #13411 2024-03-13 12:28:36 +01:00
José Valim d17df054e2 Skip tests if Erlang was compiled without docs, closes #13322 2024-03-10 19:07:22 +01:00
José Valim 41f6ee3f2e Release v1.16.2 2024-03-10 11:54:45 +01:00
José Valim 9d31768f42 Update CHANGELOG 2024-03-07 11:25:06 +01:00
José Valim 558e0ba6a6 Discard mermaid fenced blocks from ansi docs 2024-03-07 11:06:07 +01:00
Jean Klingler ab1e3448f0 Replace deprecated :code.lib_dir/2 usages (#13395) 2024-03-06 13:00:19 +01:00
Jean Klingler 17cd103a28 Logger handles :process_label from OTP27 (#13392) 2024-03-06 13:00:19 +01:00
José Valim 60e6ceae10 Correct task link in docs, closes #13388 2024-03-06 12:59:20 +01:00
José Valim f64ccd0871 Improve capture_log docs with latest Logger 2024-03-05 18:37:17 +01:00
Jonatan Kłosko 6c463b2310 Ensure install-related functions do not crash when Mix is not started (#13391) 2024-03-04 20:45:03 +01:00
H e310dd91d6 Remove duplicate line in formatter_callback doc (#13387) 2024-03-04 15:46:47 +09:00
Jonatan Kłosko 29ef147a22 Remove consolidated when restoring Mix.install/2 dir (#13382) 2024-03-01 09:34:26 +01:00
Jonatan Kłosko 74ab38439e Fix Mix.install_project_dir/0 spec (#13381) 2024-03-01 09:34:26 +01:00
José Valim 5654752026 Add @doc since to install_project_dir 2024-02-29 20:30:48 +01:00
Jonatan Kłosko e78538a42b Add environment variable for reusing Mix.install/2 installation (#13378) 2024-02-29 19:20:17 +01:00
Jonatan Kłosko 079b92e98e Support recompiling local Mix.install/2 dependencies (#13375) 2024-02-29 08:41:11 +01:00
José Valim 325877a224 Clarify the meaning of --overwrite, closes #13371 2024-02-26 20:16:27 +01:00
José Valim 097c4cf672 Emit defmodule tracing event 2024-02-26 18:28:47 +01:00
Łukasz Samson fe16fd3442 Add documentation to Exception callbacks (#13367) 2024-02-24 16:23:16 +01:00
Jean Klingler e520e8ce96 Fix unexpected rounding signs on OPT26- (#13365) 2024-02-24 16:05:09 +01:00
Jean Klingler dd004539c6 Fixes to support 0TP27 (#13351)
* Fix non-deterministic key-value tests

* Fix non-deterministic Enum tests

* Fix :only option when deriving Inspect

* Float.ceil and Float.floor return -0.0 for negative numbers

* Fix non-deterministic Registry doctest

* Simplify check
2024-02-24 16:03:58 +01:00
Jean Klingler 2512cbaff0 Fix doctest example since Inspect.MapSet changed (#13366) 2024-02-24 22:21:18 +09:00
sabiwara a99a9cf47a Fix test due to backport 2024-02-24 00:11:43 +09:00
Jean Klingler 677ad618e9 Fix charlist formatting issue on '\"' (#13364) 2024-02-24 00:02:38 +09:00
Philip Munksgaard ff309747b7 Escape pinned values when computing diff (#13354)
Take these two tests:

```elixir
  test "correctly colored" do
    assert [{:foo}] = [{:bar}]
  end

  test "incorrectly colored" do
    val = [{:foo}]
    assert ^val = [{:bar}]
  end
```

That's because when diffing a pin, we were not converting the underlying
diff context from match to ===.

This fixes #13348
2024-02-17 12:51:27 +01:00
José Valim e5ec5ce7f6 Add API for deleting SCM
This is used by Hex when its application is stopped
to revert its changes to Mix state.
2024-02-15 12:22:53 +01:00
Jean Klingler f3e669c471 Fix :trim_doc example in doc (#13340) 2024-02-13 07:39:31 +09:00
Jean Klingler bf915aa2e7 Fix <> match test (#13327) 2024-02-09 17:03:42 +09:00
Jean KlinglerandJosé Valim ef3e2796ef Update argument error message when matching with <> (#13325)
* Update argument error message when matching with <>

* Update lib/elixir/lib/kernel.ex

Co-authored-by: José Valim <jose.valim@gmail.com>

---------

Co-authored-by: José Valim <jose.valim@gmail.com>
2024-02-09 16:50:08 +09:00
Mitchell Hanberg d8cc841ab0 Include from_brackets metadata in all cases (#13317) 2024-02-05 09:49:40 +01:00
José Valim 7098ac8a16 Preserve . semantics in Path.relative_to, closes #13310 2024-02-01 12:41:26 +01:00
José Valim e0658bb55a Release v1.16.1 2024-01-31 10:24:07 +01:00
José Valim cf8c28c34b Fix autocompletion on Erlang/OTP 26, closes #13307 2024-01-31 10:12:58 +01:00
José Valim 60dcd14390 Fix Rebar3 env var with spaces (#13303) 2024-01-30 20:21:33 +01:00
Steve Johns 8344e218a6 docs: fix grammar errors in syntax reference docs (#13302) 2024-01-30 19:57:23 +01:00
José Valim 740b2d74df Escape rebar3 paths 2024-01-29 10:19:10 +01:00
José Valim da0189e641 Fix docs link
Related to #13284.
2024-01-27 11:46:53 +01:00
José Valim ad778a6f29 Fix capitalize for single codepoint (#13268) 2024-01-19 19:43:03 +01:00
José Valim c35edd1ee8 Improve end_of_expression docs 2024-01-18 08:14:04 +09:00
José Valim bb8ad4406b Improve ast metadata docs 2024-01-18 08:13:57 +09:00
Jean Klingler 111b48dcf4 Resolve relative paths in exunit filter (#13258) 2024-01-18 08:12:32 +09:00
Mitchell Hanberg 6dccefe687 Fix :from_interpolation docs (#13251) 2024-01-18 08:12:03 +09:00
Roman 9ea950c9af Macro anti-patterns: fix incorrect error message in code examples (#13259) 2024-01-18 08:09:13 +09:00
José Valim 7d100fff9f Always log errors at the end of compilation 2024-01-17 23:16:04 +01:00
Roman 6b69c7f5ac Fix the explanation to match the explained code example in docs (#13255)
The description incorrectly states that both processes are initialized with 0, while in the code the second process receives a non-default initial value.
2024-01-15 13:42:51 +01:00
Jean Klingler 691d402341 Fix Code.Normalizer for keyword operand with :do key (#13250) 2024-01-13 17:59:16 +09:00
José Valim b96211a325 Do not assume there is a $HOME, closes #13127 2024-01-10 13:01:55 +01:00
José Valim 17b9ba61d9 Do not crash parallel compiler on external reports, closes #13224 2024-01-10 10:17:03 +01:00
Hussien Liban f83b4e4e84 Update design-anti-patterns.md (#13210)
Not setting an option returns just the integer
2024-01-08 20:39:08 +09:00
Adebisi AdeyeyeandAdebisi Adeyeye 4de331597a Fix typo in docs: initial_valye -> initial_value (#13211)
Co-authored-by: Adebisi Adeyeye <adebisi.adeyeye@proebb.com>
2024-01-08 20:39:08 +09:00
Tomás Grüner 6e295998ef Fix typo in docs: Keywoird -> Keyword (#13205) 2024-01-08 20:39:08 +09:00
Will DouglasandWill Douglas f2dae095f8 Fix typo in docs: for -> force (#13215)
Co-authored-by: Will Douglas <will.cavalcanti@vmtecnologia.io>
2024-01-08 20:39:08 +09:00
Jean Klingler 6f5715fc30 Replace single quotes in charlist in doc (#13233) 2024-01-08 20:39:08 +09:00
José Valim 1ece71a831 Handle Windows separators on mix test (#13232)
Closes #13225.
2024-01-07 23:07:20 +01:00
Ryan B. Harvey 197351dd73 Fix a comment typo in the Code Anti-Patterns doc page (#13219) 2024-01-02 18:03:37 +01:00
José Valim 0d671dafea Improve anti-pattern titles 2024-01-02 16:42:17 +01:00
José Valim 9ae7c39125 Update CHANGELOG, closes #13217 2023-12-31 18:20:20 +01:00
José Valim ba8fb4dff1 Improve yecc/leex warnings, closes #13213 2023-12-28 17:12:51 +01:00
José Valim 194661197e Add more examples to app config anti-pattern (#13204) 2023-12-26 09:57:32 +01:00
Travis Vander Hoop 298acd1cdb Fix typos and tweak language in anti-pattern docs (#13208) 2023-12-26 09:57:32 +01:00
Artem Solomatin 97c608c346 Fix typo in design-anti-patterns doc (#13207) 2023-12-26 09:57:32 +01:00
José Valim f84bc19bf2 Additional clarity on long list of parameters anti-patterns 2023-12-24 14:54:45 +01:00
Alex Martsinovich 91375778cb Fix example in non-assertive map access antipattern (#13201) 2023-12-23 10:31:57 +01:00
José Valim 38ddc98fef Release v1.16.0 2023-12-22 17:45:12 +01:00
José Valim 1b7c73273d Last pass over anti-patterns 2023-12-22 14:31:44 +01:00
José Valim 5bd5e75c3b Extract snippet in elixir_errors to simplify exception handling 2023-12-21 21:14:55 +01:00
José Valim 1cba584ac8 Fix column precision in test 2023-12-21 19:48:44 +01:00
José Valim dfafea092e Consider column in snippets, closes #13199 2023-12-21 19:30:15 +01:00
José Valim 0ae4bfb222 Preserve diagnostics based on source field, closes #13142 2023-12-21 17:22:43 +01:00
José Valim 7db4413476 Add source field to diagnostics 2023-12-21 17:22:43 +01:00
José Valim 4d52d18ef7 Do not reset column state in tests 2023-12-21 17:22:43 +01:00
José Valim 39ff86c551 Indent lists 2023-12-21 15:47:59 +01:00
Tobias Pfeiffer 12fdd8ea9b Document the process anti pattern of sending large data (#13194)
Follow up to/extension of #13173
2023-12-21 15:47:59 +01:00
José Valim 28f8eba9a2 Improve docs for URI.encode/2 2023-12-17 13:05:48 +01:00
José Valim b3b14202d9 Normalize exception handling in diagnostics 2023-12-17 11:40:49 +01:00
José Valim ffa3ef1ccf Remove unused function 2023-12-14 17:19:58 +01:00
José Valim d13f4155af Do not warn unused imports twice, closes #13178 2023-12-14 17:19:58 +01:00
José Valim d23e42e27f Normalize token missing and mismatched delimiter exceptions
Closes #13183.
Closes #13185.
Closes #13186.
Closes #13187.
2023-12-14 17:07:44 +01:00
José Valim 872efb180d Document diagnostic span 2023-12-14 12:07:20 +01:00
José Valim 2e61f32b6d Update CHANGELOG 2023-12-14 11:23:56 +01:00
José Valim 7abb3bdddc Unify position handling and improve docs
See #13179.
See #13184.
2023-12-14 11:15:05 +01:00
Vinícius Müller 67fd9824cc Improve diagnostics for unclosed heredocs (#13182) 2023-12-14 09:24:59 +01:00
José Valim 1878e30bdb Improve docs and support column in IO.warn, closes #13179 2023-12-13 23:31:49 +01:00
Vinícius Müller 88bbffd614 Add info about unclosed delimiters diagnostic to 1.16 changelog (#13180) 2023-12-13 23:31:38 +01:00
Maarten van Vliet a50a2fd983 Fix typo (#13181) 2023-12-14 07:17:11 +09:00
Tobias Pfeiffer c950a57a3e Fix top level functions changelog (enabled --> disabled) (#13177) 2023-12-13 22:56:19 +09:00
Tobias Pfeiffer 80404da8f6 Mention the fixed regression (#13176)
As best as I learned this was introduced in #11420 and fixed in aabe465
It shouldn't affect almost anyone except for scripting usage of
elixir and (probably most notably) benchee benchmarks that don't
call functions in a module.
2023-12-13 14:36:33 +01:00
José Valim 010d516b11 Fix indentation in mix release 2023-12-13 11:56:41 +01:00
José Valim fb42733873 Improve anonymous functions guide 2023-12-13 06:43:20 +01:00
Tobias Pfeiffer 1070b2f434 Mention dangers around Task and sending a lot of data along (#13173)
The `Task` module is one of the coolest modules in elixir and
is probably the first contact and experience of a lot of
beginners with parallelism in elixir. I hence find it worthwhile
to warn about the memory copying and its impacts here as it might
easily lead to unwelcome results, so it's worth pointing out.
2023-12-12 16:45:00 +01:00
Wojtek Mach 9675e2285a elixir.bat: Quote file paths (#13172) 2023-12-12 14:32:06 +01:00
José Valim a09ddbb0a6 Fix typo in CHANGELOG 2023-12-12 12:43:34 +01:00
José Valim 81a12b7774 Release v1.16.0-rc.1 2023-12-12 12:36:20 +01:00
José Valim 88ba7766e0 Use Macro.Env in more warnings 2023-12-11 11:13:39 +01:00
José Valim 6475917b3f Use Macro.Env to record attribute warnings
Closes #13162.
Closes #13164.
2023-12-11 07:34:16 +01:00
José Valim 5b6e04cb4d Clean up failed deletion warning 2023-12-10 09:03:54 +01:00
Daven 2bfb14751f Add warning when deps clean fails (#13161) 2023-12-10 09:03:54 +01:00
José Valim b1998960bb Disable compiler optimizations only in module body 2023-12-09 20:49:40 +11:00
José Valim c4dcbf379f Pass original exception down to details in diagnostic, closes #13142 2023-12-09 20:49:40 +11:00
Himanshu 3b5bd39743 Update case-cond-and-if.md (#13158) 2023-12-08 21:58:51 +11:00
Wojtek Mach 4e4cde1118 Update Windows installer to write Elixir install root to registry (#13157)
We don't need this right now but it could be useful in the future, if
anything to detect if Elixir was installed using this installer.

Demo:

    iex> {:ok, r} = :win32reg.open([:read])
    iex> :win32reg.change_key(r, ~c"\\hklm\\software\\wow6432node\\elixir\\elixir")
    iex> :win32reg.value(r, ~c"installroot")
    {:ok, ~c"C:\\Program Files\\Elixir"}
2023-12-07 10:28:23 +11:00
Wojtek Mach e1db6d8831 Update Windows installer to register in Add/Remove Programs (#13156) 2023-12-07 08:44:39 +11:00
José Valim db8a1cdc7d Revert "Consider surround context until end whenever possible"
This reverts commit a65dae971f.
2023-12-04 22:34:56 +10:00
José Valim d25aacb207 Simplify offset handling in TokenMissingError 2023-12-04 22:08:53 +10:00
Vinícius Müller 106539b5d2 Improve unclosed delimiter messages (#13123) 2023-12-04 22:08:53 +10:00
José Valim d716bc2703 Include both priv and include in releases, closes #13145 2023-11-25 10:34:58 +08:00
José Valim aa0dcb9a70 Fix prying functions with only literals, closes #13133 2023-11-23 22:33:39 +08:00
Zeke Douandc4710n 01366ef526 Add Logger.levels/0 (#13136)
Co-authored-by: c4710n <c4710n@users.noreply.github.com>
2023-11-23 22:09:09 +08:00
Andrea Leopardi 6145599638 Add t/0 types to remaining ExUnit exceptions (#13139) 2023-11-23 12:17:02 +01:00
Andrea Leopardi abd4f54d78 Fix typo in Logger docs 2023-11-23 12:11:07 +01:00
Andrea Leopardi adff7f6d34 Add t/0 types for some ExUnit exceptions (#13134) 2023-11-23 17:15:59 +08:00
Andrea Leopardi 2364f04991 Add callback docs to ExUnit.Formatter (#13135) 2023-11-23 17:15:59 +08:00
Łukasz Samson 46a5c54844 Properly escape \ in Path.wildcard docs (#13137) 2023-11-23 17:15:59 +08:00
Andrea Leopardi 2caacae2e4 Add some specs and types to ExUnit.Formatter (#13130) 2023-11-23 17:15:59 +08:00
José Valim 1ff5e0c88b Improve Logger docs, closes #13119 2023-11-22 09:24:37 +08:00
José Valim c150876e5f Remove warning on non-ambiguous nullary remote call 2023-11-22 08:55:00 +08:00
Wojtek Mach 758da1e311 Update Mix.Task.preferred_cli_env/1 docs (#13114) 2023-11-16 14:00:50 +01:00
Wojtek Mach 250b48aa3b Update Mix.Config mentions (#13115)
ExDoc main emitted these warnings on Elixir main:

```
    warning: documentation references module "Mix.Config" but it is hidden
    │
 49 │   `Mix.Config`, which was specific to Mix and has been deprecated.
    │   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    │
    └─ lib/elixir/lib/config.ex:49: Config (module)

    warning: documentation references module "Mix.Config" but it is hidden
    │
 51 │   You can leverage `Config` instead of `Mix.Config` in three steps. The first
    │   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    │
    └─ lib/elixir/lib/config.ex:51: Config (module)
```
2023-11-16 14:00:50 +01:00
Jean Klingler 0a9ee79fbb Formatter keeps quotes in atom keys (#13108) 2023-11-15 14:03:28 +01:00
Jean Klingler 58b8b93ee6 Auto infer size of matched variable in bitstrings (#13106) 2023-11-15 14:03:28 +01:00
Wojtek Mach aef8673666 Preserve column when translating typespecs (#13101) 2023-11-14 09:59:01 +01:00
José Valim 901ec18aa7 Handle error in Macro.to_string/1, closes #13102 2023-11-14 00:53:00 +01:00
José Valim 56b62b64cb Consider start line in MismatchedDelimiterError 2023-11-13 12:28:48 +01:00
Artem Solomatin abbf9061ea Fix links references (#13099) 2023-11-12 10:07:26 +01:00
José Valim 8f28bd9c3f Fix GenServer cheatsheet link
Closes #13098.
2023-11-11 21:55:42 +01:00
Christopher Keele 68782004a7 Produce better error messages for non-binary mix git deps refspecs. (#13088)
When using git dependencies, a branch/ref/tag specifier
is passed verbatim to `System.cmd/3`. This can lead to
intimidating error messages when they are not provided
as a binary (for instance, an atom like `tag: :stable`):

```
** (ArgumentError) all arguments for System.cmd/3 must be binaries
    (elixir 1.15.6) lib/system.ex:1083: System.cmd/3
    (mix 1.15.6) lib/mix/scm/git.ex:287: Mix.SCM.Git.git!/2
```

This PR adds a check during git opts verification time to provide
better feedback.
2023-11-11 09:20:21 +01:00
José Valim 0a146d10cc Fix link, closes #13095 2023-11-11 09:00:07 +01:00
José Valim beaa33d088 Handle nil values in IO.warn 2023-11-10 12:53:47 +01:00
José Valim 2a94668e6e Let's not deprecate ...foo as the API may be useful
for the type system in the future.

This reverts commit f97d8585e8.
2023-11-10 12:36:38 +01:00
Cameron Duley 56e6494b36 Use in/2 in String.replace_invalid/2 guards (#13093) 2023-11-10 09:09:29 +01:00
Cameron Duley 033a177078 Fix String.replace_invalid/2 perf regressions (#13090) 2023-11-10 09:09:29 +01:00
Minh Daoandminhqdao b9fffa3b41 Fix typo in getting-started guide (#13085)
Co-authored-by: minhqdao <hello@minhdao.de>
2023-11-07 23:05:51 +01:00
Juan Barrios 7be85838fa Update float.ex description of ceil/2 and floor/2 (#13084) 2023-11-07 20:11:24 +01:00
Jacob Swanner 37f8832229 Fix Enum cheatsheet for drop/2 and take/2 with negative index (#13080) 2023-11-07 10:51:31 +01:00
José Valim 7ebf5c3032 Add :emit_warnings to Code.string_to_quoted 2023-11-06 16:31:05 +01:00
José Valim 7d4d42097d Restore code paths in archive.install/escript.install
Closes #13079.
2023-11-06 15:56:05 +01:00
Lucas Francisco da Matta Vegi 33e1b570bf Additional remarks for maintaining research history (#13078)
Similar to what we had already done with other anti-patterns that changed names
2023-11-06 15:56:05 +01:00
José Valim 8920cc1432 Fix case clause error on tokenizer 2023-11-06 13:29:19 +01:00
Erik André Jakobsen 9b480f985b [docs] Clean up sigils intro (#13077) 2023-11-06 13:29:19 +01:00
Łukasz Samson daab3f80a0 Fix crashes when :beam_lib.info(beam) returns error (#13075) 2023-11-05 20:15:33 +01:00
Tony Dang ec8782a486 Fix typo in "Getting Started - Enumerables and Streams" docs (#13073) 2023-11-04 11:14:48 +01:00
Ioannis Kyriazis 6745f775b2 is -> us (#13072) 2023-11-03 23:23:56 +01:00
Łukasz Samson 7923e4f383 Elixir 1.14.5 supports Erlang/OTP 26 (#13071) 2023-11-03 11:54:02 +01:00
Michał Łępicki d280846843 Fix typo: an dread -> and read (#13069) 2023-11-03 08:08:23 +01:00
Rich Morin 178b654a1b Fix typo (#13068) 2023-11-02 21:04:21 +01:00
José Valim 3a516a3f2b Docs to new options and functions 2023-11-02 19:32:25 +01:00
Cameron Duley d5316d558f Add String.replace_invalid/2 (#13067) 2023-11-02 19:32:25 +01:00
Jonatan Kłosko a7216decad Add offset option to File.stream! (#13063) 2023-11-02 17:58:22 +01:00
Michał Łępicki 8d545610d2 Fix Path.absname/2 spec (#13065) 2023-11-02 13:08:52 +01:00
José Valim 23c27b629d Warn if both :applications and :extra_applications are used 2023-11-02 10:19:11 +01:00
Panagiotis Nezis 0220d81513 Support --sparse in archive.install and escript.install (#13059) 2023-11-02 08:57:51 +01:00
José Valim aabe5dad76 Do not use Erlang/OTP 26.1 on CI (#13062)
It has a bug when looking up mismatched module names.
2023-11-02 08:30:01 +01:00
Łukasz Samson 741757f2a7 Lazily evaluate File.cwd! in Path.expand and Path.absname (#13061)
do not crash with File.Error with already absolute paths if File.cwd returns error or nil
2023-11-02 08:11:44 +01:00
José Valim f5e543576e Use explicit/implicit vs manual/automatic 2023-11-01 16:33:17 +01:00
José Valim e6b641a075 Fix typo on docs 2023-11-01 16:11:26 +01:00
rktjmp 3822609621 Restore GenServer introduction mermaid graph (#13058)
Restores graph removed in f5a61d1, with correct request -> reply arrow
ordering.
2023-11-01 16:11:26 +01:00
Panagiotis Nezis 0f65cb065a Additional remarks for application config anti-pattern for Mix tasks (#13057) 2023-11-01 12:42:56 +01:00
José Valim ae19236e08 Bring behaviour section from website 2023-11-01 11:17:59 +01:00
José Valim f422b77aaa Update docs 2023-11-01 10:34:59 +01:00
José Valim 43b3e94506 Add more examples to unrelated clauses 2023-10-31 20:57:30 +01:00
José Valim 2d86cb0027 Handle warnings from unquote functions 2023-10-31 12:00:04 +01:00
José Valim 792d4cc631 Release v1.16.0-rc.0 2023-10-31 09:16:09 +01:00
José Valim f8016bca48 Describe them as potential anti-patterns 2023-10-31 08:52:14 +01:00
José Valim 82818f7126 Improve complex extraction example 2023-10-31 07:49:48 +01:00
José Valim 5c8f9aac64 Fix getting started links 2023-10-30 23:40:15 +01:00
José Valim 1a8bad1bf2 Streamline unrelated introduction 2023-10-30 20:10:53 +01:00
José Valim 4b795871b6 Improve examples and docs 2023-10-30 15:50:53 +01:00
José Valim 3036401c7c Clarify best practices and update anti-patterns list 2023-10-30 15:12:41 +01:00
José Valim 8bbba572de Describe pattern matching as simpler 2023-10-30 09:18:25 +01:00
José Valim 968b51319e Clarify scope of anti-patterns 2023-10-30 08:11:25 +01:00
José Valim 900f25f832 Branch out v1.16 2023-10-29 12:30:32 +01:00
José Valim bb1f1af113 Trim trailing whitespace on heredocs with \r\n 2023-10-29 08:51:48 +01:00
José Valim a32ce8b252 Provide more rationale on do-end 2023-10-29 08:41:15 +01:00
José Valim 331e565c64 Add a link to Erlang application specification, closes #13042 2023-10-29 08:31:37 +01:00
Artem Solomatin 0dc4d7d9f8 Add usage example for Keyword.from_keys (#13041) 2023-10-28 13:53:48 +02:00
José Valim 86eebc5b3f Do not provide default value for profiling 2023-10-28 09:16:37 +02:00
José Valim 0bb98d431b Upadte CHANGELOG 2023-10-27 18:39:33 +02:00
José Valim 6634773145 Optimize mix compile.elixir when changing one file between thousands 2023-10-27 14:08:13 +02:00
José Valim b5704cc7d0 Add MIX_PROFILE 2023-10-27 14:00:01 +02:00
José Valim 8923da00e3 Ensure duplicate modules are recompiled, closes #13040 2023-10-27 11:06:07 +02:00
José Valim 0e1a5d3cfe More improvements to anti-pattern 2023-10-27 09:02:47 +02:00
Serge Aleynikov 1b1bbf1ad9 Fix typos in documentation (#13039) 2023-10-27 08:17:51 +02:00
José Valim dc79914182 Add :limit option to Task.yield_many/2 2023-10-26 23:19:21 +02:00
José Valim b86b8ba47e Clarify primitive obsession 2023-10-26 18:33:16 +02:00
Philip Munksgaard e88377f517 Add missing dialyzer attributes (#13037)
Taken from this list:
https://www.erlang.org/doc/man/dialyzer.html#type-warn_option

Fixes #13036
2023-10-26 14:45:01 +02:00
José Valim 9a3c732dde Also exclusive optional applications to Mix load path 2023-10-26 13:34:43 +02:00
Serge Aleynikov 784f6eda93 Document the prune_code_paths option for the compiler (#13035) 2023-10-26 09:58:49 +02:00
José Valim 35619fd892 Clarify "known keys" and struct usage 2023-10-26 09:25:22 +02:00
José Valim 7cfe015564 Update CHANGELOG 2023-10-26 00:34:53 +02:00
José Valim 62759e4bac Reorder clauses 2023-10-26 00:30:42 +02:00
José Valim 08cc3015b5 Add a test to stab as not an operator 2023-10-26 00:29:50 +02:00
Łukasz Samson 3781a4dc3a Fix crash in surround_context (#13033) 2023-10-26 00:19:29 +02:00
José Valim 08f315016f Add @nifs attributes 2023-10-25 23:53:24 +02:00
Łukasz Samson cbf4b8c85d add missing :expr to surround_context typespec (#13034) 2023-10-25 23:49:21 +02:00
Gilbert Bishop-White 0a7a41cc2c Add example of inserting into Map that already has key (#13013) 2023-10-24 20:34:51 +02:00
Aziz Köksal af9c0aaeb6 Change the word "duplicated" to "duplicate" (#13008) 2023-10-24 20:11:48 +02:00
teiko af1e2b89f4 Fix typos in DateTime.add/4 docs (#13031) 2023-10-24 16:12:29 +02:00
José Valim 9581ba791a Annotate function definition and not when guard 2023-10-24 00:50:58 +02:00
José Valim c35d002dc2 Fix column information in tests 2023-10-24 00:36:24 +02:00
José Valim 09946ee776 Include column information on definition metadata
Closes #13029.
2023-10-23 20:12:20 +02:00
José Valim e94ff76527 Avoid traversing the file system twice on parallel checker
Closes #13024.
2023-10-23 11:00:31 +02:00
bo0tzz 92d46d0069 Fix typos in anti-patterns documentation (#13027) 2023-10-21 22:43:22 +02:00
Artem Solomatin 4b85aa2cec Add AST link to quote-and-unquote guide (#13026) 2023-10-21 05:56:23 +02:00
Dino d332f262e7 Fix IEx doc printing for headings with newline characters (#13018)
Update `IEx.introspection.print_doc/5` to trasverse the list of headings
and split each heading into multiple headings, in case the heading has
any newline characters (`\n`). This fixes an issue where using the `h`
macro with some functions ended up with a broken output for the function
signature, as the padding done by `IO.ANSI.Docs.print_headings/2` does
not take into consideration the fact the the headings can contain
newline characters, and thus end up in multiple lines.
2023-10-20 07:01:38 +02:00
samanera 2f9061e8e0 Fix typos in elixir/pages/ (#13022)
`is an a float` → `is a float`
`valuesare`     → `values are`
`specificatio`  → `specification`
2023-10-20 06:25:12 +02:00
Josh Bones f2d2522721 Fix typo in code-smells/non-assertive truthiness documentation (#13021) 2023-10-19 20:22:52 +02:00
Gary Rennie 5fa100b997 Mention capture_log with a level in the ExUnit config (#13020) 2023-10-18 16:18:35 +02:00
Adam Millerchip ddf00f779c Speed up _ interspersing by using bitstrings (#13019) 2023-10-18 15:38:17 +02:00
José Valim 69255ecbc8 Do not leak alias from Elixir root and simplify defmodule implementation 2023-10-17 11:09:21 +02:00
Jean Klingler ec4dafae0c Fix overriding with Elixir. prefixed defmodule (#13011)
Closes #12456.
2023-10-17 10:53:10 +02:00
José Valim ddb2434357 Emit warning if True/False/Nil are used also outside of alias 2023-10-17 10:03:28 +02:00
José Valim da3e093a1d Improve titles to reduce ambiguity 2023-10-16 14:20:23 +02:00
Jean Klingler 8a0961c9dc Fix known callbacks suggestions (#13007) 2023-10-15 21:11:23 +09:00
Jean Klingler 88ade1d312 Fix warnings on conflicting callbacks (#13006)
* Improve imprecise message

* Remove false positive warnings on conflicting behaviours
2023-10-15 20:10:46 +09:00
José Valim 87b5ee077d Update CHANGELOG 2023-10-15 11:40:45 +02:00
José Valim d7013b19c1 Update CHANGELOG 2023-10-14 12:46:03 +02:00
José Valim bd5f02ad8d Address compilation on Erlang/OTP 24 2023-10-14 11:39:02 +02:00
José Valim 03605cc3ed Address compilation on Erlang/OTP 24 2023-10-14 11:36:22 +02:00
José Valim 218d2e1a09 Use remote version of erl_eval callbacks
This allows us to execute erl eval instructions across nodes
with different Elixir versions.
2023-10-14 11:29:13 +02:00
José Valim 3e47910f39 Address compilation warnings on Erlang/OTP 24 2023-10-13 22:48:16 +02:00
Juha b09758bcfe Document need of Tasks to trap exits for Task.Supervisor :shutdown to have effect (#13004) 2023-10-13 12:11:50 +02:00
José Valim c7d2c37565 Update sigil_r example 2023-10-13 11:23:34 +02:00
José Valim 388ce16d86 Link to #security-wg EEF work 2023-10-13 11:14:54 +02:00
Michał Łępicki 8c24718090 Clean up unreachable function clause in ex_unit/filters.ex (#13003)
The parse_kv function always gets called with a binary second argument, from line 139
This function clause was kept after refactoring in https://github.com/elixir-lang/elixir/commit/4971af9fc9848b3c8e081a62f586014eda23f446
2023-10-12 21:59:53 +02:00
José Valim 44d3faad45 No longer track module as context inside after_compile 2023-10-11 15:53:41 +02:00
José Valim ee7f0c22b8 Do not tie eval functions to Elixir version 2023-10-11 10:33:08 +02:00
José Valim a2bd1f29e2 No longer version per minor branch 2023-10-10 23:38:26 +02:00
José Valim a029cd22bd Clarify docs on tasks requirements 2023-10-09 16:57:01 +02:00
José Valim d732ee4851 Non-assertive anti-patterns (#12997) 2023-10-09 13:54:44 +02:00
Jean Klingler 320477cf5e Add local_handler to erl_eval (#12998) 2023-10-08 21:59:46 +09:00
José Valim a7adda21fd Remove always true otp_release checks 2023-10-08 13:40:37 +02:00
José Valim 4971af9fc9 Parse filter values on ExUnit.Filters.parse/1 2023-10-08 11:06:50 +02:00
Aziz Köksal a0b42e8cc1 Allow mix test multi locations (#12959) 2023-10-08 10:45:39 +02:00
Vinícius Müller 47d706734c Add error span for unknown local call 2023-10-07 18:12:15 +02:00
José Valim a4c700b23b Unify caret position in diagnostics
Closes #12995.
2023-10-07 17:23:20 +02:00
Adam Millerchip e3d5bb718e Add example of seeking within a file to File docs (#12996) 2023-10-06 18:28:43 +02:00
José Valim 1f4f0aba3b Delegate and document escape sequences to regexes 2023-10-06 14:43:58 +02:00
José Valim b710c8822c Delegate escape characters to regex 2023-10-06 14:43:58 +02:00
Michał Łępicki 4d69bd7059 Fix Exception.format_mfa spec again (#12994)
> The arity may also be a list of arguments.
2023-10-06 08:35:27 +02:00
Artem Solomatin a19140a479 Add typespec for Module.reserved_attributes (#12993) 2023-10-06 06:36:34 +03:00
Michał Łępicki e1edece58c Fix Exception.format_mfa/3 spec (#12990) 2023-10-05 09:55:08 +02:00
Artem Solomatin f1d7f09648 Add missing typespecs IO.stream and IO.binstream (#12989) 2023-10-05 09:50:00 +02:00
Michał Łępicki 0886847c4c Fix Exception.format_stacktrace/1 spec (#12991) 2023-10-05 09:49:08 +02:00
Artem Solomatin d1a218b893 Add missing typespecs for String.normalize (#12988) 2023-10-04 22:37:57 +03:00
José Valim 1bbcf67366 Keep File.stream! options as last argument 2023-10-04 17:08:06 +02:00
Artem Solomatin 8f7416d9d9 Add typespecs to Exception module (#12983) 2023-10-04 16:12:31 +02:00
José Valim f9cca8fd13 Remove duplicate sigil docs now that guides are included 2023-10-04 09:56:50 +02:00
Bruce Wong 61b5aafd24 Update code-anti-patterns.md (#12981)
Correct "string according to with the planned"
2023-10-02 23:05:48 +02:00
Jonatan Kłosko 7410cd370a Update docs on binary syntax modifiers (#12980) 2023-10-02 22:01:42 +02:00
Stevo-S e4431b8589 Clarify Any Protocol implementation derivation (#12978)
A small edit on the "Deriving" section of the "Protocols" chapter in "Getting Started".

Changing "we should be fine with the implementation of Any" to
"should we be fine with the implementation of any" hopefully
better communicates that we can use the Any implementation of
the protocol if we wish to but not that we have to.
2023-10-02 14:44:23 +02:00
Art Kay 78c9666ea1 Fix typo in the Enum cheatsheet (#12976) 2023-10-01 23:49:11 +02:00
José Valim 60efbf751d Address tests on Erlang/OTP 26.1, closes #12975 2023-10-01 15:46:17 +02:00
José Valim 3afe4d6dfc Provide tips for custom inspect, closes #12974 2023-10-01 13:57:54 +02:00
Jean Klingler 186d1ab201 Improve Macro.path/2 docs (#12973) 2023-10-01 19:29:56 +09:00
Artem Solomatin 8033c90778 Add missing spec and test for Macro.path (#12972) 2023-10-01 11:36:42 +02:00
José Valim c2abb107d6 Order anti-patterns alphabetically 2023-09-30 20:36:08 +02:00
José Valim 3ed3bf9108 Deprecate ~R/.../ in favor of ~r/.../ 2023-09-30 11:14:41 +02:00
Artem Solomatin 883631f5a6 Add typespec for rel_templates_path (#12970) 2023-09-30 09:34:25 +02:00
José Valim d89a2a448e Ensure full directory match on mix format, closes #12969 2023-09-28 21:36:13 +02:00
José Valim bd6ada3be8 Update SECURITY.md 2023-09-28 18:14:08 +02:00
Wojtek Mach dc5f8de3f9 Update elixir/pages/anti-patterns/code-anti-patterns.md (#12968) 2023-09-28 11:11:09 +02:00
José Valim 0ba36abc97 Add namespace trespassing anti-pattern (#12966) 2023-09-28 09:01:50 +02:00
José Valim f23b28374b Move unescape map to Kernel for simplicity 2023-09-27 22:54:19 +02:00
José Valim bc3c915563 Boolean obsession (#12965) 2023-09-26 22:22:05 +02:00
Lucas Francisco da Matta Vegi fd7ea5beb0 "Primitive Obsession" added (Anti-pattern documentation) (#12962) 2023-09-26 18:35:46 +02:00
dependabot[bot] e23c34db97 Bump DavidAnson/markdownlint-cli2-action from 11.0.0 to 13.0.0 (#12963)
Bumps [DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action) from 11.0.0 to 13.0.0.
- [Release notes](https://github.com/davidanson/markdownlint-cli2-action/releases)
- [Commits](https://github.com/davidanson/markdownlint-cli2-action/compare/v11.0.0...v13.0.0)

---
updated-dependencies:
- dependency-name: DavidAnson/markdownlint-cli2-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 23:52:14 +02:00
José Valim c7bd0fe7e7 Do not emit duplicate warnings from tokenizer, closes #12961 2023-09-25 12:55:23 +02:00
Andrea Leopardi 6935f747e3 Fix Markdown in docs for the Config module 2023-09-25 08:39:12 +02:00
Vinícius Müller 9a74a73a00 Normalize hint labels (#12958) 2023-09-23 18:59:08 +02:00
Vinícius Müller af915cfc20 Uncomment assertions (#12960) 2023-09-23 17:24:36 +02:00
Lucas Francisco da Matta Vegi 3f6c748b1f "Feature envy" added (Anti-pattern documentation) (#12952) 2023-09-23 14:15:31 +02:00
Jean Klingler 6ee16616c0 Fix OTP 26.1 warning in tests (#12954) 2023-09-23 18:15:09 +09:00
Jean Klingler 55fbc528f9 Always rewrite signed numbers during expansion (#12955) 2023-09-23 18:08:52 +09:00
José Valim 7de603134c Perform single pass in ExUnit.Filters 2023-09-23 11:04:34 +02:00
Artem Solomatin d35d60f4c9 Add typespecs and docs to 'Range' module (#12953) 2023-09-23 10:22:38 +02:00
Jean Klingler 9c0fa3cfef Warn when matching on 0.0 and generate erl AST for +/-0.0 (#12949) 2023-09-23 00:27:44 +09:00
Lucas Francisco da Matta Vegi 1ecdd2d736 "Using application configuration for libraries" added (Anti-patterns documentation) (#12944) 2023-09-21 12:56:37 +02:00
José Valim d1014500e8 Optimize Enum.random/1 for ranges 2023-09-20 13:00:34 +02:00
José Valim 4e96f92d1e Update CHANGELOG 2023-09-20 10:59:43 +02:00
Wojtek Mach 6c1138d6f5 Improve Windows Installer (#12945)
1. If we couldn't verify installed OTP, don't quit but let user proceed with installing just Elixir

2. When checking installed OTPs, pick the recent most installed. Previously we were picking the first one which was basically guaranteed to be too old.

3. If we find an existing Elixir installation but it doesn't match the OTP version, offer to download the OTP version this Elixir installer was compiled against (which usually would be the newer version)

4. Add "Verify Erlang/OTP" button which re-checks for installed OTP.

Closes #12678.
2023-09-20 10:24:17 +02:00
EksperimentalandMaintenance App a6b68dd684 Update Unicode to version 15.1.0 (#12947)
This is an automated commit created by the Maintenance project
https://github.com/eksperimental/maintenance

Before merging, please read the release notes by visiting
<http://www.unicode.org/versions/Unicode15.1.0/>
and assess if additional changes are necessary in the code base.

Co-authored-by: Maintenance App <maintenance-beam@autistici.org>
2023-09-20 09:16:13 +02:00
José Valim e66e6efb13 Improve put_env/1 docs 2023-09-19 08:33:46 +02:00
José Valim 6510f25e30 Improve parsing and ast metadata docs 2023-09-18 09:44:10 +02:00
Vinícius Müller f632fc648e Set parser_options[:columns] to true by default (#12941) 2023-09-18 09:42:17 +02:00
José Valim 5225b33bab Add interpolation token metadata 2023-09-17 22:46:26 +02:00
José Valim cb9de080e5 Strip column information on quote 2023-09-17 22:25:33 +02:00
José Valim 77abd0606f Have references come last 2023-09-17 11:29:16 +02:00
José Valim df114285ce Split adjust indent into text and code 2023-09-17 10:13:53 +02:00
José Valim 32690dd7d1 Read snippets as binaries 2023-09-16 20:30:04 +02:00
Vinícius Müller 10077bdb5e Add span to unused/undefined variable diagnostics (#12940) 2023-09-16 20:11:26 +02:00
José Valim abbc3924be Add instructions on how to run examples, closes #12939 2023-09-16 19:15:45 +02:00
Christoph Schmatzler 19451c72e9 docs: fix reference to DateTime.time_zone (#12938) 2023-09-16 15:48:44 +02:00
Lucas Francisco da Matta Vegi f017e109f5 "Unrelated multi-clause function" added (Anti-patterns documentation) (#12931) 2023-09-16 11:29:58 +02:00
Vinícius Müller f1594048a5 Unicode support for mismatched delimiter errors (#12935) 2023-09-16 10:59:59 +02:00
José Valim ce6c16016c Revert "Add <|> to the list of parsable but unused operators (#12932)" (#12937)
This reverts commit a4956eee40.
2023-09-16 10:42:25 +02:00
Jean Klingler 8e6c898fca Remove duplicate word in guide (#12936) 2023-09-16 16:55:53 +09:00
Nathan Long 7c277d083c Clarify that registration is only on the local node (#12934) 2023-09-15 19:22:58 +02:00
Vinícius Müller 3111a5b78a Improve mismatched delimiter message (#12928) 2023-09-15 18:31:48 +02:00
José Valim 646577af50 Add opening_delimiter to token missing error 2023-09-15 18:18:09 +02:00
José Valim 9a3d1a0321 Include open_delimiter in TokenMissingError 2023-09-15 18:18:09 +02:00
Zach Allaun d87aadf8bd Fix spec in Code.Typespec.fetch_types/1 and fetch_callbacks/1 (#12933)
The guards for these functions were already allowing binaries, which
allows either a file name or a binary containing the BEAM object code to
be passed in. The specs, however, only specified `module`. (Note that
`fetch_types/1` already specified `module | binary`.)
2023-09-15 15:05:49 +02:00
Zach Allaun a4956eee40 Add <|> to the list of parsable but unused operators (#12932) 2023-09-14 15:46:13 +02:00
José Valim e56f4c8435 Load plugins on formatter_for_file, closes #12930 2023-09-14 09:13:09 +02:00
José Valim 8ed7b7b5ac Add more docs to trailing bang convention 2023-09-13 10:49:09 +02:00
Lucas Francisco da Matta Vegi 0bd8ad7dc2 "Using exceptions for control-flow" added (Anti-patterns documentation) (#12921) 2023-09-13 10:38:14 +02:00
José Valim 08e02cff84 Do not loop when invalid block is given to formatter, closes #12922 2023-09-13 09:49:56 +02:00
José Valim c6250bc57b Trace functions before they are inlined, closes #12925 2023-09-13 09:38:28 +02:00
José Valim c8c4ec5155 Properly print warnings from tokenizer in EEx, closes #12926 2023-09-13 09:17:36 +02:00
José Valim c365eac5dc Return relative file in EEx, closes #12927 2023-09-13 09:02:42 +02:00
José Valim afa16a69d5 Doc partition supervisor type 2023-09-13 09:02:42 +02:00
Vinícius Müller 0349240827 Add MistmatchedDelimiterError (#12918) 2023-09-12 12:18:51 +02:00
José Valim d46fa4b042 Do not always load applications during convergence, closes #12682 2023-09-12 11:53:44 +02:00
José Valim 34010cf418 Load extra applications for umbrellas 2023-09-12 10:11:51 +02:00
Artem Solomatin 6d82f1c1e6 Fix few grammar issues (#12924) 2023-09-12 06:10:31 +02:00
Jean Klingler 8f9265b7f3 Fix formatter for :* in bitstring modifiers (#12923) 2023-09-12 04:02:23 +09:00
José Valim 0408c97915 Include environment in mix format missing dependency error 2023-09-11 17:36:12 +02:00
Lucas Francisco da Matta Vegi 4774c2759a "Alternative return types" added (Anti-patterns documentation) (#12905) 2023-09-11 10:31:06 +02:00
Vinícius Müller 0d139c15df Save column information on start delimiter tokens (#12916) 2023-09-10 13:09:56 +02:00
chengshq f5a9a65c42 Fix index on split (#12915) 2023-09-08 13:44:55 -04:00
José Valim 29b29c5fbf Add an introduction to module names 2023-09-07 05:46:52 -04:00
dependabot[bot] 1bc8bc5e76 Bump actions/checkout from 3 to 4 (#12911)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-05 08:29:39 +02:00
91d0b9f65c Fix supervisor docs grammar (#12912)
Co-authored-by: Andrea Leopardi <an.leopardi@gmail.com>
Co-authored-by: Jean Klingler <sabiwara@gmail.com>
2023-09-05 08:15:12 +02:00
José Valim e9d548b122 Improve error message on Access module 2023-09-04 14:00:57 +02:00
felipe stival 30c2eae6ae Fix error message for invalid list of plugins (#12909) 2023-09-04 13:47:17 +02:00
felipe stival 6d31f098cc Fix remark in mix format docs (#12908)
The text was true at some point, but now the code is always compiled
 before running the formatter if formatter.exs includes plugins, so
 it doesn't apply anymore.
2023-09-04 12:53:58 +02:00
José Valim bdf66e7d6e Revert "Add Access.at/2 (#12906)"
This reverts commit ce79c2c83d.

See #10158.
2023-09-04 10:50:23 +02:00
Vinícius Müller ce79c2c83d Add Access.at/2 (#12906) 2023-09-03 21:33:02 +02:00
Vinícius Müller 7e9006988d Add span field to diagnostics (#12843) 2023-09-03 21:14:51 +02:00
Iztok Fister Jr a6e310a41a Update man page with package manager (#12904) 2023-08-31 22:40:11 +02:00
Jhan S. Álvarez 92052fbe5a Fix regex inspect for escaped hash (#12902) 2023-08-31 09:35:01 +02:00
Wojtek Mach 3817c18619 Fix PATH handling on Windows Installer (#12900) 2023-08-30 17:04:40 +02:00
José Valim eb97ac6a2c Add Code module to test PLT 2023-08-30 17:04:14 +02:00
José Valim 8dfc35383d Use Code.ensure_compiled/1 when loading protocol implementations 2023-08-30 15:25:13 +02:00
José Valim 22dcba75d2 Introduce Kernel.ParallelCompiler.pmap/2 (#12899) 2023-08-30 14:59:48 +02:00
Masatoshi Nishiguchi bff7f463a1 docs(mix): update mix compile options (#12896) 2023-08-30 10:28:43 +02:00
Kennedy Gilmore 5eae77f8c4 Fix typo in GenServer docs (#12898) 2023-08-30 08:05:01 +02:00
Andrea Leopardi d34b708d53 Add "since" annotations in PartitionSupervisor (#12895) 2023-08-29 22:11:45 +02:00
afloram 9321f2f20b Remove extra space (#12894) 2023-08-29 00:32:31 +02:00
José Valim 518a7f9694 Revert "remove not working markdown for hint/warning (#12893)"
This reverts commit 2389400354.
2023-08-28 18:24:21 +02:00
Uwe Krause 2389400354 remove not working markdown for hint/warning (#12893)
See https://github.com/elixir-lang/elixir-lang.github.com/issues/1712
2023-08-28 18:14:16 +02:00
José Valim bd454e286d Ensure dbg module is a compile-time dependency
Related to #12892.
2023-08-28 13:30:46 +02:00
José Valim 9f984f82d2 Raise on dedented code in doctests 2023-08-27 11:00:57 +02:00
José Valim 2bdd84b37b Speed up loading of struct suggestions, closes #12674 2023-08-27 10:12:11 +02:00
Zach Allaun b50c5eb031 Fix Code.Fragment.surround_context/2 for submodules of non-aliases (#12890) 2023-08-24 20:55:42 +02:00
José Valim 96a41c81c5 Do not store anonymous functions in cache 2023-08-23 14:15:39 +02:00
José Valim 9f977be589 Load and compile plugins from cache, closes #12880 2023-08-23 12:24:10 +02:00
sabiwara 03ef7a1059 Always pass stracktrace when necessary 2023-08-23 12:05:52 +02:00
Jean Klingler 51c2cbff03 Cheatsheet small fixes (#12884) 2023-08-22 23:43:10 +09:00
Jean Klingler a406527510 Mention patter-filtering in comprehensions (#12883) 2023-08-22 22:49:24 +09:00
Po Chen 38a7c77786 Fix with_index/2 example (#12882) 2023-08-22 12:41:52 +02:00
José Valim 682b6d5b21 Add comprehension examples 2023-08-22 11:29:37 +02:00
Po Chen 3b92c0d5a2 Fix map_reduce result (#12881) 2023-08-22 11:27:06 +02:00
José Valim fddb26d43e Add more examples to filtering 2023-08-22 11:01:25 +02:00
José Valim 47d3a18615 Add remaining functions to Enum cheatsheet 2023-08-22 10:55:45 +02:00
José Valim 997d92575b Fix links, add pending images 2023-08-21 14:44:49 +02:00
José Valim 7e273b8443 Generate ePUB format 2023-08-21 14:23:32 +02:00
José Valim 379f38e64e Add Enum cheatsheet 2023-08-21 13:16:59 +02:00
José Valim 9b9ce35894 Move additional remarks to header 2023-08-21 13:16:59 +02:00
Lucas Francisco da Matta Vegi 26d9b44478 "Long parameter list" added (Anti-patterns documentation) (#12875) 2023-08-20 20:56:07 +02:00
Jean Klingler 3f930b4a92 Update other undefined variable errors in docs (#12876) 2023-08-20 15:17:49 +09:00
José Valim f94dac17c6 Validate Mix.install/2 options 2023-08-19 17:32:32 +02:00
José Valim b87b0a6e86 Fixes to guides and diagrams 2023-08-19 12:06:54 +02:00
Jean Klingler 6b1208b035 Minor typo in guide (#12873) 2023-08-18 21:58:09 +09:00
José Valim d538a16479 Update markdownlint rules 2023-08-18 12:10:58 +02:00
Lucas Francisco da Matta Vegi a720c37c20 "Speculative assumptions" added (Anti-patterns documentation) (#12868) 2023-08-18 11:59:31 +02:00
felipe stival c0f5129c3e Fix DateTime.from_iso8601/3 documentation (#12872) 2023-08-18 10:56:50 +02:00
José Valim cb3725234b More fixes, add disclaimer 2023-08-18 00:58:02 +02:00
José Valim 4e76929e59 More renames to avoid ambiguity 2023-08-18 00:35:36 +02:00
José Valim 0cb4b4b9ad Brighten background in diagram 2023-08-18 00:01:38 +02:00
José Valim 22856a95bf More touch ups on Mix and OTP 2023-08-17 23:59:22 +02:00
José Valim 900cdd0f1f Touch ups on Mix & OTP guide 2023-08-17 23:56:34 +02:00
José Valim e505b3aa28 Add diagram to supervisor 2023-08-17 23:56:25 +02:00
José Valim 6151e30874 Rename guides to avoid conflicts 2023-08-17 22:44:18 +02:00
José Valim 625d096517 Add sequence diagram to GenServer 2023-08-17 22:33:54 +02:00
José Valim f7e33c70c8 Add Mermaid diagram to GenServer 2023-08-17 21:46:10 +02:00
José Valim 6a72d2926c Improve docs for with_diagnostics 2023-08-17 09:41:12 +02:00
Jean Klingler a94700f861 Fix unused function warning with overridables (#12865) 2023-08-16 16:18:07 +09:00
Lucas Francisco da Matta Vegi 93dc98946b "Dynamic atom creation" added (Anti-patterns documentation) (#12804) 2023-08-16 09:17:04 +02:00
Nic Nilov 43681f670d Maps AST representation docs fix (#12863) 2023-08-15 14:43:57 +02:00
Dave Lucia 44cce79e00 Improve UndefinedFunctionError for unqualified module (#12859)
This PR builds off of #12839, handling the case where a developer
forgets to alias a module, resulting in UndefinedFunctionError.

Imagine we have the following modules in our Elixir program.

```elixir
def MyAppWeb.Context.Event do
  def foo, do: :bar
end
```

If the developer attempts to reference this module, but forgets to type
the alias, they will now get module suggestions like.

```elixir
** (UndefinedFunctionError) function Event.foo/0 is undefined (module Event is not available). Did you mean:

      * MyAppWeb.Context.Event.foo/0

    Event.foo()
    iex:2: (file)
```
2023-08-15 11:12:34 +02:00
José Valim 0099c2aec5 Add a reference to keywords and maps guide 2023-08-15 10:37:58 +02:00
José Valim e0d04e1ffe Warn on nullary remote calls, related to #9510 2023-08-15 10:30:57 +02:00
Uwe Krause 0c2b763d02 Add hint to needed dependencies for observer (#12861) 2023-08-15 09:15:54 +02:00
José Valim 52158d7de3 More docs for maps/keywords 2023-08-14 12:58:44 +02:00
José Valim ec2733611b Add :observer.start at the end 2023-08-14 12:43:00 +02:00
Mitchell Hanberg fc8793d96a docs: grammar in start_link_supervised/2 (#12858) 2023-08-12 23:19:46 +02:00
José Valim 63fc8b6b9e Improve lists and tuples guides with more and simpler examples 2023-08-12 23:04:30 +02:00
José Valim 17b8b364c0 More tests for invalid sequences 2023-08-12 22:37:50 +02:00
José Valim d5931e4e27 Optimize and auto-synchronize String.capitalize/2 2023-08-12 22:20:38 +02:00
José Valim 415bc8714b Bring String.capitalize/2 back 2023-08-12 22:08:46 +02:00
José Valim fb69ea792e Bring turkic cases back to capitalize 2023-08-12 21:58:24 +02:00
José Valim 1e709961e1 Link to reference documents on function/module naming definition 2023-08-12 10:56:10 +02:00
José Valim ae24200ded Use zero float comparison compatible with Erlang/OTP 27 2023-08-12 10:41:01 +02:00
José Valim 001a64e2a9 Only print all warnings if there are no errors 2023-08-11 21:31:28 +02:00
José Valim 483a1b9f7e Ensure applications have been loaded on config/runtime.exs, closess #12853 2023-08-10 14:30:59 +02:00
magashpillay 284f0b2501 Fix code block in Markdown guides (#12852) 2023-08-10 10:40:56 +02:00
Andrea Leopardi 820ce069a8 Improve module attributes docs in Mix.Task (#12851) 2023-08-10 10:37:09 +02:00
José Valim 88ad5d2c2b Fix internal usage of String.capitalize/1 2023-08-09 17:13:06 +02:00
Nathan Long fb76eb0b84 Quick examples of timetime shifts (#12716) 2023-08-09 17:08:54 +02:00
Dave Lucia 458fcaf50f Improve UndefinedFunctionError for mis-cased module (#12839) 2023-08-09 17:06:35 +02:00
José Valim 37bcb194df Fix example in describe 2023-08-09 17:06:16 +02:00
José Valim 4a39114f06 Fix references to String.capitalize/1 2023-08-09 17:03:41 +02:00
José Valim 76a92b5a80 Deprecate String.capitalize/2 in favor of :string.titlecase/1
When capitalize/2 was written, Erlang did not provide titlecase
functions. capitalize/2 also downcases the rest of the string
while Erlang doesn't, which is a common contention point.

Given our titlecase implementation requires 40kb of additional
code in .beam files, it makes sense to unify it with Erlang's
(which we have already done for most non-critical functionality
in unicode).
2023-08-09 16:15:41 +02:00
magashpillay dced276114 Update binaries-strings-and-charlists.md (#12850) 2023-08-09 10:22:05 +02:00
Jean Klingler f2fe43e85d Update make_custom_env recipe to fix warnings (#12849) 2023-08-08 21:43:06 +09:00
Jean Klingler 6a395a7da1 Fragment handle anonymous calls with extra spaces (#12847) 2023-08-08 20:01:54 +09:00
Jean Klingler 11b2e3e333 Code.Fragment handles anonymous function (#12846) 2023-08-08 19:21:40 +09:00
Panagiotis Nezis 33be0abbf4 Fix misc typos (#12844) 2023-08-07 11:37:03 +02:00
Panagiotis Nezis 717efbeaa9 Consistently raise on invalid ANSI sequences in IO.ANSI.format (#12842)
If `enabled` is set to `true` an invalid ANSI sequence will raise
an `ArgumentError`. On the other hand if `enabled` is set to `false`
no error is raised and the sequence is silently ignored.

This can lead to inconsistent behaviours for CLI tools based on a
configuration setting. Additionally since in tests a common pattern
is to disable ANSI sequences you may have successful tests but
exceptions when executing the code in production with enabled ANSI
sequences.
2023-08-06 18:41:04 +02:00
José Valim ea8f6c8c6b Improve docs example, closes #12840 2023-08-06 15:02:51 +02:00
Jean Klingler 146fb4e3ff Make has_unquote/1 quote-aware (#12836) 2023-08-05 20:39:05 +09:00
Jean Klingler c4b5974d23 Capture negative step slice warnings in tests (#12838) 2023-08-05 20:15:12 +09:00
Jean Klingler cb8abb5ba4 Avoid error message in test (#12837) 2023-08-05 20:14:48 +09:00
Jean Klingler ba4c160080 Warn on unused defp/defmacrop using unquote/1 (#12832)
* Remove unused macro

* Remove unused default arg in test

* Warn on unused defp/defmacrop using unquote/1

* Add tests for metaprogrammed clauses without warnings
2023-08-05 19:07:50 +09:00
Artem Solomatin a6439f42ef Fix typo (#12835) 2023-08-04 18:07:38 +02:00
Mike Schell 0b0da7db51 Fix examples in with documentation (#12833) 2023-08-04 09:38:42 +02:00
Jean Klingler 450aacb88f Fix bug in surround_context alias handling (#12830) 2023-08-03 23:14:08 +09:00
José Valim a9f650a9e9 Fix infinite loop when diffing functions, closes #12828 2023-08-02 20:45:51 +02:00
Adam Kirk cf37d0a676 Add filtering docs to Logger (#12827) 2023-08-02 11:51:51 +02:00
José Valim 73017e18fd Improve ensure_application docs 2023-08-02 01:37:45 +02:00
José Valim d459dd5a4f Force group leader to run as a binary and unicode in IEx 2023-07-31 15:46:29 +02:00
José Valim d3a6395edf Only add spacing to error message if snippet is present 2023-07-31 13:11:33 +02:00
José Valim ec24260ffc Simplify IEx CLI booting 2023-07-28 13:33:26 +02:00
José Valim ed02fa6e3c Also handle charlist returns from stdio
Closes #12687.
2023-07-28 12:23:54 +02:00
José Valim 5aec96818b Improve docs for binary_part/3 2023-07-28 11:26:42 +02:00
Florent Guilleux a1112fe895 Explain why functions returns extra information with tuples (#12824)
Currently the guide says

> One very common use case for tuples is to use them to return extra information from a function.

but I find it does not explicitly say why.
2023-07-28 09:05:52 +02:00
José Valim bc45fabd9c Do not expand aliases recursively, closes #12822 2023-07-26 12:46:35 +02:00
Artem Solomatin feb7dc5b0c Fix supported time units (#12819) 2023-07-24 17:26:13 +02:00
Jean Klingler 998504005b Add test for :zip_input_on_exit in Task.Supervisor (#12816) 2023-07-24 19:35:29 +09:00
B. Burt 9decffaf82 Fix a typo in the Kernel docs (#12815) 2023-07-23 08:29:53 +02:00
Ievgen Pyrogov 27fabde683 Mention :zip_input_on_exit in Task.Supervisor too (#12814)
It's mentioned in docs to Task.async_stream/5, but not here.
2023-07-23 08:28:58 +02:00
Vinícius Müller 48b471d055 Update diagnostics reporting format (#12813) 2023-07-20 18:49:55 +02:00
Jean Klingler c6f55001b5 Cleanup erlang shell docs (#12811)
* Refactor: only fetch docs once

* Handle undefined erlang types

* Handle undefined erlang functions
2023-07-20 17:31:28 +09:00
Jean Klingler 41a21175d2 IEx uses erlang shell rendering for erlang docs (#12803)
* IEx uses erlang shell render for erlang function docs

* Add tests for erlang module docs and types

* Handle erlang module docs with :shell_docs

* Handle erlang module types with :shell_docs

* Avoid fetching docs twice

* Cleanup unused erlang doc handling

* Use example that exists in OTP24
2023-07-19 23:56:14 +09:00
José Valim 4614de646a Optional arguments must be counted from the end, closes #10095 2023-07-19 14:31:53 +02:00
José Valim 3116e6292a Do not assume blake is always available
Closes #12809.
2023-07-19 00:18:52 +02:00
Artem Solomatin 7c1cc52c86 Update typespec for enum find_value (#12789) 2023-07-18 15:02:40 +02:00
B. Burt 3e42b9599c Fix some typos in the docs for List (#12807) 2023-07-18 07:54:16 +02:00
Artem Solomatin f53eaefa87 Add typespecs for min and max float (#12806) 2023-07-17 23:00:10 +02:00
B. Burt 097de75b2b Fix a typo in some docs (#12805) 2023-07-17 21:54:29 +02:00
José Valim 3bee1f2f53 Add code comments to special argument on archive.build 2023-07-17 19:45:17 +02:00
José Valim d772c30099 Disable consolidation on archive.install 2023-07-17 19:14:13 +02:00
José Valim 2904713b04 Remove backticks around Map 2023-07-17 18:03:46 +02:00
José Valim 061d9887c7 Compute remove entries per dependency, closes #12801 2023-07-17 09:36:15 +02:00
José Valim 63d6834524 Consider significant chunks from Erlang/OTP 26 2023-07-17 00:12:10 +02:00
Ievgen Pyrogov 056042bc3e Update io.ex (#12800)
Fix newlines in documentation for IO module
2023-07-17 00:08:57 +02:00
Lucas Francisco da Matta Vegi 675d001e62 More Code-related anti-patterns added (Documentation) (#12790) 2023-07-16 20:19:39 +02:00
Santiago Ferreira f027c66712 Remove duplicated async: true option from example (#12798) 2023-07-16 15:50:18 +02:00
Jean Klingler b2b55d534e Remove erlang doc duplicates in IEx (#12797) 2023-07-16 18:36:18 +09:00
José Valim 234906de8f Avoid adding paths soon to be removed to Mix code path 2023-07-15 09:36:33 +02:00
Andrea Leopardi 47c3390232 Add Supervisor.module_spec/0 type (#12793) 2023-07-15 09:24:53 +02:00
José Valim ed972c66ae Ensure load path is enabled on __mix_recompile__? 2023-07-15 09:23:09 +02:00
Andrea Leopardi 544ff7bb02 Clean up type docs in Supervisor 2023-07-14 23:19:40 +02:00
Artem Solomatin ff4344e973 Add typespec for Range.size (#12792) 2023-07-14 22:26:50 +02:00
José Valim 7858bac32b Revert "Simplify OnExitHandler API"
The private API unfortunately is used by other projects,
which first need to be migrated.
2023-07-13 12:22:56 +02:00
Thibaut Barrère 5a5947c2d9 Clarify upgrade path for Regex.regex? (#12785) (#12786) 2023-07-12 18:04:35 +02:00
José Valim 01d11af42c Return :error tagged tuple 2023-07-12 16:06:54 +02:00
José Valim d34e68e1b0 Fix typos 2023-07-12 16:05:27 +02:00
José Valim c0ea321446 Adjustments to anti-patterns 2023-07-12 15:39:33 +02:00
Lucas Francisco da Matta Vegi a2a6379034 Code-related anti-patterns added (Documentation) (#12776) 2023-07-12 15:14:07 +02:00
José Valim 61e8dee760 Consider optional apps in Mix.ensure_application! 2023-07-12 09:27:52 +02:00
Artem Solomatin 23793ee669 Fix typos and close brackets (#12783) 2023-07-12 00:08:17 +02:00
Artem Solomatin 43b423c61c Fix deprecations link for elixir 1.15 (#12781) 2023-07-11 23:18:59 +02:00
Artem Solomatin 289f4e1da8 Fix link in macros guide (#12780) 2023-07-11 21:33:02 +02:00
Andrea Leopardi 32cd48b398 Lint Markdown guides (#12775) 2023-07-10 11:45:01 +02:00
Andrea Leopardi 15ce8706e2 Fix many small typos 2023-07-09 16:23:18 +02:00
Andrea Leopardi c1f8bae16a Fix spelling of "file system" 2023-07-09 16:12:48 +02:00
Andrea Leopardi dd295f4bc7 Fix spelling of "hard-coded" 2023-07-09 16:08:12 +02:00
Andrea Leopardi 51a9977b79 Move "Introduction to Mix" guide here (#12774) 2023-07-09 16:07:24 +02:00
Andrea Leopardi 2f25dc2a51 Remove all <abbr>s from the guides
Earmark does not support inline HTML.
2023-07-09 09:40:05 +02:00
José Valim 2d2ec036f8 Remove unecessary ExUnit process 2023-07-08 12:24:04 +02:00
José Valim 41a18e9e23 Simplify OnExitHandler API 2023-07-08 12:21:13 +02:00
Jean Klingler 24efbdfb7c Document whitespace rules for keyword syntax (#12773) 2023-07-08 17:24:01 +09:00
Lucas Francisco da Matta Vegi 36e71050cb Remaining macro-anti-patterns (Anti-pattern documentation) (#12769) 2023-07-08 10:17:30 +02:00
Peter Lemenkov e00524ba5b Use PID valid for 32-bit systems, followup to #12741 (#12772)
Signed-off-by: Peter Lemenkov <lemenkov@gmail.com>
2023-07-07 20:47:26 +02:00
Jean Klingler 4411dbe3f2 Expand captured args with regular context (#12763)
* Expand captured args with regular context

* Rewrite test depending on current line

* Display a different warning for captured arguments

* Mention the reason in a comment
2023-07-07 22:22:01 +09:00
José Valim 93ff4a22de Disable tail call optimization on file root entries 2023-07-07 13:37:44 +02:00
Cristiano Piemontese 2ee7b59921 Add missing p in docs (#12768) 2023-07-07 12:10:18 +02:00
José Valim 16d8d1af28 Add tests for Mix.install start_applications: false 2023-07-07 08:42:01 +02:00
Łukasz Samson 9edde54c3b Allow to opt out of starting apps in Mix.install (#12766) 2023-07-07 08:39:16 +02:00
Iztok Fister Jr e374882c03 Revise the description of DSLs (#12762) 2023-07-06 21:50:27 +02:00
José Valim 999ecf73de Fix references in getting started guides 2023-07-06 16:08:36 +02:00
José Valim 1cd7e53875 Tidy up getting started guides conclusion 2023-07-06 16:07:30 +02:00
José Valim e55472e9b8 More guides 2023-07-06 15:49:05 +02:00
José Valim f7fcc160b8 More guides 2023-07-06 15:23:03 +02:00
José Valim 365cd44b1c More guides 2023-07-06 15:01:45 +02:00
José Valim 3a706f7a1a More guides 2023-07-06 13:54:36 +02:00
José Valim e96ca906a4 Clarify --no-pry vs --dbg pry 2023-07-06 12:36:15 +02:00
José Valim 58bb5194d3 Propagate diagnostics from inner compiler process 2023-07-06 09:41:57 +02:00
Lucas Francisco da Matta Vegi 1b33e7259e "Scattered process interfaces" (Anti-pattern documentation) (#12759) 2023-07-06 09:06:55 +02:00
Iztok Fister Jr d82adfad71 Add unbreakable space (references, man page) (#12760) 2023-07-05 23:55:30 +02:00
Andrea LeopardiandJosé Valim 3c0efc1e2a Add "Comments" anti-pattern to the guides (#12758)
Co-authored-by: José Valim <jose.valim@dashbit.co>
2023-07-05 20:37:12 +02:00
José Valim 970691db2d More guides 2023-07-05 19:15:16 +02:00
José Valim 5547e35ffb More guides 2023-07-05 18:45:09 +02:00
José Valim e9e6f23105 Fixes to anti-patterns 2023-07-05 18:24:13 +02:00
José Valim 2a97e7e0fb Initial getting started version 2023-07-05 18:24:04 +02:00
Andrea Leopardi e8409fe8fb Polish some anti-patterns in the guides 2023-07-05 10:54:03 +02:00
Andrea Leopardi 4a9c11def4 Use <h4> titles in anti-patterns guides 2023-07-05 10:42:09 +02:00
Andrea Leopardi a446fcc95c Clean up some process-related anti-patterns 2023-07-05 09:34:22 +02:00
Andrea Leopardi 8e31412bae De-indent anti-pattern guides (#12755) 2023-07-05 09:25:11 +02:00
José Valim 9bdbcbb7f9 Move meta-programming to the bottom 2023-07-05 09:23:14 +02:00
José Valim 189dbcec67 use instead of import + nutrition facts (anti-patterns) 2023-07-05 09:13:42 +02:00
Andrea Leopardi e785d86095 Move meta-programming guide here from elixir-lang.org (#12750) 2023-07-05 08:54:47 +02:00
José Valim d0d80aee53 Apply changes and additional remarks to anti-pattern 2023-07-05 08:45:10 +02:00
Lucas Francisco da Matta Vegi e27dd766ba "Code organization by process" and "Unsupervised processes" (Anti-pattern documentation) (#12754) 2023-07-05 08:27:01 +02:00
Lucas Francisco da Matta Vegi 0d76145e1b Add "Working with invalid data" (Anti-pattern documentation) (#12753) 2023-07-04 21:06:38 +02:00
José Valim 51830c13a1 Provide structure for anti-patterns (#12748) 2023-07-04 20:40:00 +02:00
José Valim 4895540a32 Consistent casing in titles 2023-07-04 19:06:18 +02:00
Andrea Leopardi 0843b401b0 Document almost all remaining stdlib exceptions 2023-07-04 18:56:27 +02:00
José Valim d8d2434c49 Update ExDoc instructions 2023-07-04 17:38:30 +02:00
Andrea Leopardi 2ab9047d12 Document more exceptions 2023-07-04 16:56:34 +02:00
Andrea Leopardi 9382c83d33 Avoid Kernel.SpecialForms when linking Kernel 2023-07-04 16:52:19 +02:00
José Valim 6909684d6b Create a new references section in the sidebar 2023-07-03 20:47:27 +02:00
José Valim 56098b98db Only include location in diagnostic when precise 2023-07-03 17:55:27 +02:00
Andrea Leopardi d4cbba0416 Add docs to more stdlib exceptions (#12739) 2023-07-03 16:36:50 +02:00
José Valim 797789338e Improve error message when IEx cannot boot 2023-07-03 16:33:07 +02:00
José Valim 8074811400 Fix IEx --remsh on Erlang/OTP 25-
Closes #12746.
2023-07-03 16:24:44 +02:00
Jean Klingler 1fcdc52854 Clarify that Code.with_diagnostics/2 does not rescue (#12742) 2023-07-02 21:56:48 +09:00
Vinícius Müller f60d54bbc1 Show snippets on additional diagnostics printing (#12725) 2023-07-02 14:29:41 +02:00
José Valim cd5f45b1c6 Add smoke tests for -e/--eval (#12743) 2023-07-02 11:04:11 +02:00
José Valim 8bc67d384b Use PID valid for 32-bit systems, closes #12741 2023-07-02 10:28:58 +02:00
José Valim ecb3d572f7 Add trailing newline to version 2023-07-01 22:26:17 +02:00
José Valim 2a0b83af2f Check for variable definition instead of expansion 2023-07-01 19:29:17 +02:00
kevinferretti 67207f4b75 Improve default ExUnit seed algorithm and allow changing via config (#12733) 2023-07-01 19:17:38 +02:00
Andrea Leopardi 9b74eceb79 Add more documentation for stdlib exceptions 2023-07-01 10:29:19 +02:00
Andrea Leopardi 0700c8b780 Make some fields of exceptions public (#12735)
In particular, for FunctionClauseError and UndefinedFunctionError.
2023-07-01 09:03:25 +02:00
Wojtek Mach 694b947508 Pass %* to env.bat and document accordingly (#12734)
On UNIX, `$@` was already available in `env.sh`. This patch adds similar
capability for `env.bat`.

For a more complete example, users can:

```sh
export MYAPP_ARGS="$@"
```

and then:

```elixir
def start(_type, _args) do
  if args = System.get_env("MYAPP_ARGS") do
    args
    |> OptionParser.split()
    |> # ...
  end
end
```

Parsing command-line arguments in Elixir is not ideal given various
escaping rules and whitespace handling so depending on their needs,
users might instead leave this up to the shell:

```sh
i=1
export MYAPP_ARGC=$#
for arg; do
  export "MYAPP_ARG$i"="$arg"
  i=$((i + 1))
done
```

```elixir
def start(_type, _args) do
  if argc = System.get_env("MYAPP_ARGC") do
    for i <- 1..System.to_integer(argc) do
      System.fetch_env!("MYAPP_ARG#{i}")
    end
  end
end
```
2023-06-30 22:37:36 +02:00
Wojtek Mach 7795adcd60 Fix .github/workflows/release_pre_built/action.yml (#12737) 2023-06-30 16:04:30 +02:00
José Valim a65dae971f Consider surround context until end whenever possible 2023-06-30 15:16:04 +02:00
Wojtek Mach 2c78c731f7 Build Elixir main docs with ExDoc main (#12736) 2023-06-30 14:23:17 +02:00
Vinícius Müller 99785cc16b Improve File.ls/1 docs (#12732) 2023-06-29 16:58:39 +02:00
José Valim 70bbfd0964 Refactor doctest tests 2023-06-28 22:32:02 +02:00
Rich Cavanaugh 8cbea343ee Allow variables defined in doctests to be used in expectation (#12730) 2023-06-28 22:28:45 +02:00
Artem Solomatin ad5f5c6e37 FIx typo in mix test docs (#12728) 2023-06-28 16:52:46 +02:00
Andrea Leopardi 8fc96d6246 Add CLI opts docs to "mix clean" 2023-06-28 08:37:18 +02:00
José Valim 587a0a9b61 Add test that a directory external resource does not crash 2023-06-27 15:08:25 +02:00
Benjamin Milde 11cfb574c7 Generalize description of multiple matching variables in the same pattern (#12722) 2023-06-27 15:03:38 +02:00
José Valim 028dbd71a2 Do not assume Logger has been loaded at compile-time 2023-06-27 15:02:20 +02:00
José Valim fe95fd035b mix format 2023-06-27 14:58:46 +02:00
José Valim c8827b6cab Remove remaining reference to digest_file! 2023-06-27 14:49:55 +02:00
José Valim 2f506d976d Do not assume external resources are available 2023-06-27 14:29:38 +02:00
Jean Klingler c312b47d08 Always respect options passed to capture_log (#12721) 2023-06-27 20:56:08 +09:00
José Valim 96996cef7d Mention Erlang's :math module in Float 2023-06-27 09:13:13 +02:00
José Valim 0a66246e97 Fix doctest suite 2023-06-26 21:13:22 +02:00
Vinícius Müller 79c90bf8eb Report correct position for duplicate struct fields diagnostic (#12718) 2023-06-26 21:56:30 +03:00
Vinícius Müller 384ca45a16 Fix infinite recursion in get_file_line (#12714) 2023-06-26 11:38:11 +03:00
José Valim 7b67dd4553 Remove trailing space and trailing newlines in snippets 2023-06-25 20:04:11 +02:00
Vinícius Müller 2e87bc8385 Align error snippet with stacktrace on single digit lines (#12709) 2023-06-25 19:46:24 +02:00
Jean Klingler 88d6436a39 Refactoring: use recursion instead of building list (#12711) 2023-06-25 16:54:56 +09:00
Christopher Keele 663ea2e39b Mention on: capture reference in Regex.split/{2..3} (#12710) 2023-06-25 09:50:43 +02:00
José Valim ce9508639b Fix heisentest 2023-06-25 09:49:45 +02:00
Vinícius Müller 86813b4ba6 Reword grouped warnings message (#12708) 2023-06-24 23:59:42 +02:00
Vinícius Müller 774d10b83e Improve compiler warnings visuals (#12683) 2023-06-24 22:18:37 +02:00
José Valim d773207335 make is enough to compile/install from source 2023-06-24 20:41:05 +02:00
José Valim fe92444873 Use --return-errors to simplify tests 2023-06-24 20:19:39 +02:00
José Valim 4d008142d7 Return errors on mix compile instead of raising 2023-06-24 20:18:07 +02:00
José Valim 1a04a8216d Do not add extra newlines after hints 2023-06-24 17:05:50 +02:00
José Valim f3c4c8cb4d Track removed modules and exports across local deps, closes #12707 2023-06-24 16:59:22 +02:00
José Valim cde6d02e34 Remove --werl from Erlang/OTP 26 release scripts 2023-06-24 13:08:03 +02:00
Jean Klingler a7edd6ea47 Parser honors :static_atoms_encoder for multi-letter sigils (#12706)
* Unify sigil token generation

* Parser honors :static_atoms_encoder for multi-letter sigils

* Remove chars from token, reverse engineer from atom
2023-06-24 18:29:24 +09:00
cjschneider2 73b65eca5a Use printf instead of echo to output user flags (#12704)
Fixes #12677.

Using printf here prevents an error when outputting the filtered flag
`-e` as doing with with `echo "-e"` doesn't output anything
(deleting the option we wanted to filter). 

More info: https://unix.stackexchange.com/a/65819
2023-06-23 16:04:29 +02:00
José Valim acca527b05 Ensure included deps transitive from path deps can be loaded, closes #12682 2023-06-23 15:45:12 +02:00
José Valim 7303e58d2b Handle :function metadata properly, closes #12703 2023-06-23 13:07:04 +02:00
José Valim 1459d23925 Include optional dependencies when mode changes, closes #12694 2023-06-23 10:12:01 +02:00
José Valim a6661e4557 Extract app writing into its own function 2023-06-23 10:08:02 +02:00
Aaron Tinio 72044e8ef7 Provide compile_env tip that fixes the error across all environments (#12702) 2023-06-23 09:02:22 +02:00
Vinícius Müller a9e826e3bb Assert on relative lines in doctest tests (#12699) 2023-06-23 00:03:17 +02:00
Vinícius Müller 4cd08e5f65 Check formatting when running tests for specific projects (#12700) 2023-06-22 23:54:37 +02:00
Jannik Becher e23b60b37f :test_type does not affect the test execution (#12701) 2023-06-22 23:53:34 +02:00
José Valim 6620b48fd1 Fix dbg on Erlang/OTP 25- (#12697) 2023-06-22 20:53:17 +02:00
José Valim d80a567532 Fix non-result doctest terminated with fences, closes #12695 2023-06-22 19:05:43 +02:00
José Valim f01df3dcb7 Add doctest_line interpolation 2023-06-22 18:12:29 +02:00
José Valim be4a62aa7e Avoid trailing whitespace on ExDoc messages 2023-06-22 14:46:11 +02:00
José Valim a9b0396f0c Assert current project is in the path before invoking compilers
Closes #12686.
2023-06-21 20:23:03 +02:00
José Valim b02808dd21 Require leex and yecc compilers to be listed 2023-06-21 20:01:29 +02:00
José Valim 3f673136bd Ensure yecc is available to emit warnings, closes #12691 2023-06-21 19:33:32 +02:00
José Valim 79dd9c4af6 Fix race condition on concurrent capture_log, closes #12692 2023-06-21 16:44:37 +02:00
José Valim 698fcdb82c Update CHANGELOG 2023-06-20 16:33:50 +02:00
Vinícius Müller 89ff2a798c Don't color IEx exception if it contains ANSI (#12680) 2023-06-20 15:49:17 +02:00
José Valim 9b132a2e6d Do not expect OTP to be compiled with docs, closes #12677 2023-06-20 12:33:38 +02:00
Vinícius Müller 07ff933f78 Assert on compiler warnings content rather than format (#12679) 2023-06-20 12:17:51 +02:00
José Valim befe07559a Fix path on Windows 2023-06-19 16:10:07 +02:00
José Valim 47a5e30155 Make IO.binwrite consistent with IO.write 2023-06-19 15:27:12 +02:00
José Valim fcc743c47f Simplify whitespace prunning 2023-06-19 13:29:54 +02:00
Vinícius Müller 0da6662b5e Improvide diagnostics on lexer exceptions (#12670) 2023-06-19 11:07:59 +02:00
José Valim 7b3558d030 Improve Logger docs 2023-06-18 17:12:18 +02:00
José Valim 0cdd845004 Update release instructions 2023-06-18 10:44:30 +02:00
Vinícius Müller b7d3a29db8 Assert on error message rather than format (#12675) 2023-06-15 20:06:10 +02:00
sabiwara 44c18a3894 Consistently use backticks when documenting boolean returns (#12671) 2023-06-14 19:25:55 -04:00
Vinícius Müller cba29235ab Improve @file attribute docs (#12668) 2023-06-14 13:10:01 +02:00
sabiwara 2f4a3138cd Fix slicing MapSet with stepped range (#12667) 2023-06-14 12:03:45 +02:00
sabiwara 666c26a789 Add missing typespec for Code.eval_quoted_with_env/4 (#12665) 2023-06-14 18:04:00 +09:00
sabiwara 858dd1706b Name arguments in Macro.Env functions docs (#12664) 2023-06-14 17:54:33 +09:00
Vinícius Müller 24883a476c Use british spelling when referring to elixir behaviour (#12663) 2023-06-14 10:25:43 +02:00
José Valim 9977c7d05a Return the value on each step of dbg+pry+pipe integration, closes #12657 2023-06-13 17:11:28 +02:00
José Valim 04b6cd342c Improve error message for invalid module attribute usage, closes #12656 2023-06-13 16:54:01 +02:00
Vinícius Müller e2d09bcb6d Enable column on unused variable test (#12660) 2023-06-13 16:37:30 +02:00
José Valim 9041fca941 Fix Path suite on Windows 2023-06-13 15:29:04 +02:00
José Valim cacf83a98d Fix erts handling when loading apps 2023-06-13 15:26:24 +02:00
José Valim 8f43be2ca6 Do not deprecate relative paths, instead make sure the paths are proper 2023-06-13 15:08:03 +02:00
nico piderman 14f24b0d52 Extract mark_tests_invalid/2 function in ExUnit.Runner (#12658) 2023-06-13 14:41:37 +02:00
Wojtek Mach 65fb415b11 Bump Ubuntu version for builds.hex.pm.yml (#12652)
We need nsis 3.08 (not 3.06)
2023-06-12 13:04:30 +02:00
nico piderman 79c89c84ae Mark test cases as invalid when an exit occurs during setup_all (#12651) 2023-06-12 12:52:17 +02:00
Wojtek Mach ec0d3d7b19 Add offline Windows installer to releases (#12640) 2023-06-12 12:51:28 +02:00
José Valim bacea2cef6 Keep capture operator on container cursor to quoted 2023-06-12 10:43:51 +02:00
Vinícius Müller 97873b87c9 Remove useless assertions in warnings_test (#12649) 2023-06-11 16:54:29 +02:00
Vinícius Müller 2f6e763ee5 Emit unknown remote call warning with columns (#12646) 2023-06-11 14:29:22 +02:00
Vinícius Müller 486eb46795 Refactor warning_tests to assert on warning position (#12647) 2023-06-11 11:56:18 +02:00
Vinícius Müller 1f79973768 Add column on unknown remote calls (#12641) 2023-06-10 19:31:02 +02:00
Vinícius Müller ee3263e02f Check columns by default on warnings tests (#12643) 2023-06-10 14:49:37 +02:00
José Valim 3509fd9631 Optimize IO.warn with Macro.Env 2023-06-10 13:24:21 +02:00
Vinícius Müller 34f54c0cea Pass column info through meta_location and env_format (#12639) 2023-06-09 19:24:34 +02:00
José Valim 06ed2ae8b2 Keep erts when pruning load paths (#12642) 2023-06-09 18:21:35 +02:00
José Valim 7711aa1919 Improve heredoc warning 2023-06-08 16:14:55 +02:00
pnezis 0872ee5444 Support parent relative paths in Path.relative_to (#12628) 2023-06-07 17:45:07 +02:00
José Valim a6c83879db Support mix xref graph at umbrella roots, closes #12629 2023-06-07 14:12:58 +02:00
José Valim 3521675e75 Mark functions as generated in Docs chunk 2023-06-07 13:12:10 +02:00
Vinícius Müller e1beb778c4 Fix binary match UTF-16 doctest (#12635) 2023-06-05 17:53:21 +02:00
José Valim 9eb39bfe58 Improve utf-modifier docs, closes #12634 2023-06-05 13:47:55 +02:00
José Valim e624c9c25a Expand dots in relative_to and deprecate relative cwd (#12632)
The function already assumes there are no symlinks,
so expanding dots does not add additional uncertainty.
2023-06-05 09:17:39 +02:00
José Valim 9967cbc49e Clarify docs of Path.wildcard and Path.safe_relative_to 2023-06-04 12:21:13 +02:00
José Valim 3930858c3b Optimize dot expansion in paths (#12631) 2023-06-04 12:18:38 +02:00
José Valim ba04d2e0cf Purge Hex before running Mix tests 2023-06-03 20:59:47 +02:00
José Valim d7c6edbe78 Deprecate negative steps in slice and date operations 2023-06-03 20:44:00 +02:00
José Valim edc2326130 Start v1.16-dev 2023-06-03 20:27:23 +02:00
349 changed files with 24143 additions and 6518 deletions
+4 -1
View File
@@ -13,7 +13,10 @@
assert_same: 2,
# Errors tests
assert_eval_raise: 3
assert_eval_raise: 3,
# Float tests
float_assert: 1
],
normalize_bitstring_modifiers: false
]
+2 -2
View File
@@ -30,9 +30,9 @@ jobs:
- otp: 26
otp_version: '26.0'
build_docs: build_docs
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 50
- name: Get tags
+33
View File
@@ -0,0 +1,33 @@
name: CI for Markdown content
on:
push:
branches:
- 'main'
paths:
- 'lib/**/*.md'
pull_request:
paths:
- 'lib/**/*.md'
workflow_dispatch:
jobs:
lint:
name: Lint Markdown content
strategy:
fail-fast: false
runs-on: ubuntu-20.04
steps:
- name: Check out the repository
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Run markdownlint
uses: DavidAnson/markdownlint-cli2-action@v13.0.0
with:
globs: |
lib/elixir/pages/**/*.md
+11 -11
View File
@@ -3,10 +3,10 @@ name: CI
on:
push:
paths-ignore:
- 'lib/**/*.md'
- "lib/**/*.md"
pull_request:
paths-ignore:
- 'lib/**/*.md'
- "lib/**/*.md"
env:
ELIXIR_ASSERT_TIMEOUT: 2000
@@ -24,19 +24,19 @@ jobs:
fail-fast: false
matrix:
include:
- otp_version: '26.0'
- otp_version: "26.0"
otp_latest: true
- otp_version: '25.3'
- otp_version: '25.0'
- otp_version: '24.3'
- otp_version: '24.0'
- otp_version: "25.3"
- otp_version: "25.0"
- otp_version: "24.3"
- otp_version: "24.0"
- otp_version: master
development: true
- otp_version: maint
development: true
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 50
- uses: erlef/setup-beam@v1
@@ -77,12 +77,12 @@ jobs:
name: Windows Server 2019, Erlang/OTP ${{ matrix.otp_version }}
strategy:
matrix:
otp_version: ['24', '25', '26']
otp_version: ["24", "25", "26.0"]
runs-on: windows-2019
steps:
- name: Configure Git
run: git config --global core.autocrlf input
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 50
- uses: erlef/setup-beam@v1
@@ -107,7 +107,7 @@ jobs:
name: Check POSIX-compliant
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 50
- name: Install Shellcheck
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-20.04
name: Notify
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 50
- uses: erlef/setup-beam@v1
+6 -4
View File
@@ -15,7 +15,7 @@ permissions:
jobs:
create_draft_release:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
@@ -40,9 +40,9 @@ jobs:
- otp: 26
otp_version: '26.0'
build_docs: build_docs
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 50
- uses: ./.github/workflows/release_pre_built
@@ -56,7 +56,9 @@ jobs:
run: |
gh release upload --clobber "${{ github.ref_name }}" \
elixir-otp-${{ matrix.otp }}.zip \
elixir-otp-${{ matrix.otp }}.zip.sha{1,256}sum
elixir-otp-${{ matrix.otp }}.zip.sha{1,256}sum \
elixir-otp-${{ matrix.otp }}.exe \
elixir-otp-${{ matrix.otp }}.exe.sha{1,256}sum
- name: Upload Docs to GitHub
if: ${{ matrix.build_docs }}
env:
+22 -4
View File
@@ -22,17 +22,35 @@ runs:
shasum -a 1 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha1sum
shasum -a 256 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha256sum
echo "$PWD/bin" >> $GITHUB_PATH
- name: Get latest stable ExDoc version
- name: Install NSIS
shell: bash
run: |
sudo apt update
sudo apt install -y nsis
- name: Build Elixir Windows Installer
shell: bash
run: |
export OTP_VERSION=${{ inputs.otp_version }}
export ELIXIR_ZIP=$PWD/elixir-otp-${{ inputs.otp }}.zip
(cd lib/elixir/scripts/windows_installer && ./build.sh)
mv lib/elixir/scripts/windows_installer/tmp/elixir-otp-${{ inputs.otp }}.exe .
shasum -a 1 elixir-otp-${{ inputs.otp }}.exe > elixir-otp-${{ inputs.otp }}.exe.sha1sum
shasum -a 256 elixir-otp-${{ inputs.otp }}.exe > elixir-otp-${{ inputs.otp }}.exe.sha256sum
- name: Get ExDoc ref
if: ${{ inputs.build_docs }}
shell: bash
run: |
EX_DOC_LATEST_STABLE_VERSION=$(curl -s https://hex.pm/api/packages/ex_doc | jq --raw-output '.latest_stable_version')
echo "EX_DOC_LATEST_STABLE_VERSION=${EX_DOC_LATEST_STABLE_VERSION}" >> $GITHUB_ENV
if [ "${{ github.ref_name }}" = "main" ]; then
ref=main
else
ref=v$(curl -s https://hex.pm/api/packages/ex_doc | jq --raw-output '.latest_stable_version')
fi
echo "EX_DOC_REF=$ref" >> $GITHUB_ENV
- uses: actions/checkout@v3
if: ${{ inputs.build_docs }}
with:
repository: elixir-lang/ex_doc
ref: v${{ env.EX_DOC_LATEST_STABLE_VERSION }}
ref: ${{ env.EX_DOC_REF }}
path: ex_doc
- name: Build ex_doc
if: ${{ inputs.build_docs }}
+37
View File
@@ -0,0 +1,37 @@
{
// Consecutive header levels (h1 -> h2 -> h3). We don't care about this.
"MD001": false,
// Header style. We use #s.
"MD003": {
"style": "atx"
},
// Style of unordered lists..
"MD007": {
"indent": 2,
"start_indented": true
},
// Line length. Who cares.
"MD013": false,
// This warns if you have "console" or "shell" code blocks with a dollar sign $ that
// don't show output. We use those a lot, so this is fine for us.
"MD014": false,
// Multiple headings with the same content. That's fine.
"MD024": false,
// Allow empty line between block quotes. Used by contiguous admonition blocks.
"MD028": false,
// Allowed HTML inline elements.
"MD033": {
"allowed_elements": [
"a",
"br",
"img",
"noscript",
"p",
"script"
]
},
// This warns if you have spaces in code blocks. Sometimes, that's fine.
"MD038": false,
// Code block style. We don't care if it's fenced or indented.
"MD046": false
}
+184 -251
View File
@@ -1,329 +1,262 @@
# Changelog for Elixir v1.15
# Changelog for Elixir v1.16
This release requires Erlang/OTP 24 and later.
## Code snippets in diagnostics
Elixir v1.15 is a smaller release with focused improvements
on compilation and boot times. This release also completes
our integration process with Erlang/OTP logger, bringing new
features such as log rotation and compaction out of the box.
Elixir v1.15 introduced a new compiler diagnostic format and the ability to print multiple error diagnostics per compilation (in addition to multiple warnings).
You will also find additional convenience functions in `Code`,
`Map`, `Keyword`, all Calendar modules, and others.
With Elixir v1.16, we also include code snippets in exceptions and diagnostics raised by the compiler. For example, a syntax error now includes a pointer to where the error happened:
## Compile and boot-time improvements
The last several releases brought improvements to compilation
time and this version is no different. In particular, Elixir
now caches and prunes load paths before compilation, ensuring your
project (and dependencies!) compile faster and in an environment
closer to production.
In a nutshell the Erlang VM loads modules from code paths. Each
application that ships with Erlang and Elixir plus each dependency
become an entry in your code path. The larger the code path, the
more work Erlang has to do in order to find a module.
In previous versions, Mix would only add entries to the load paths.
Therefore, if you compiled 20 dependencies and you went to compile
the 21st, the code path would have 21 entries (plus all Erlang and
Elixir apps). This allowed modules from unrelated dependencies to
be seen and made compilation slower the more dependencies you had.
With this release, we will now prune the code paths to only the ones
listed as dependencies, bringing the behaviour closer to `mix release`.
Furthermore, Erlang/OTP 26 allows us to start applications
concurrently and cache the code path lookups, decreasing the cost of
booting applications. The combination of Elixir v1.15 and Erlang/OTP 26
should reduce the boot time of applications, such as when starting
`iex -S mix` or running a single test with `mix test`, from 5% to 30%.
The compiler is also smarter in several ways: `@behaviour` declarations
no longer add compile-time dependencies and aliases in patterns and
guards add no dependency whatsoever, as no dispatching happens. Furthermore,
Mix now tracks the digests of `@external_resource` files, reducing the
amount of recompilation when swapping branches. Finally, dependencies
are automatically recompiled when their compile-time configuration changes.
### Potential incompatibilities
Due to the code path pruning, if you have an application or dependency
that does not specify its dependencies on Erlang and Elixir application,
it may no longer compile successfully in Elixir v1.15. You can temporarily
disable code path pruning by setting `prune_code_paths: false` in your
`mix.exs`, although doing so may lead to runtime bugs that are only
manifested inside a `mix release`.
## Compiler warnings and errors
The Elixir compiler can now emit many errors for a single file, making
sure more feedback is reported to developers before compilation is aborted.
In Elixir v1.14, an undefined function would be reported as:
** (CompileError) undefined function foo/0 (there is no such import)
my_file.exs:1
In Elixir v1.15, the new reports will look like:
error: undefined function foo/0 (there is no such import)
my_file.exs:1
** (CompileError) nofile: cannot compile file (errors have been logged)
A new function, called `Code.with_diagnostics/2`, has been added so this
information can be leveraged by editors, allowing them to point to several
errors at once.
## Integration with Erlang/OTP logger
This release provides additional features such as global logger
metadata and file logging (with rotation and compaction) out-of-the-box!
This release also soft-deprecates Elixir's Logger Backends in
favor of Erlang's Logger handlers. Elixir will automatically
convert your `:console` backend configuration into the new
configuration. Previously, you would set:
```elixir
config :logger, :console,
level: :error,
format: "$time $message $metadata"
```
** (SyntaxError) invalid syntax found on lib/my_app.ex:1:17:
error: syntax error before: '*'
│
1 │ [1, 2, 3, 4, 5, *]
│ ^
│
└─ lib/my_app.ex:1:17
```
Which is now translated to the equivalent:
For mismatched delimiters, it now shows both delimiters:
```elixir
config :logger, :default_handler,
level: :error
config :logger, :default_formatter,
format: "$time $message $metadata"
```
** (MismatchedDelimiterError) mismatched delimiter found on lib/my_app.ex:1:18:
error: unexpected token: )
│
1 │ [1, 2, 3, 4, 5, 6)
│ │ └ mismatched closing delimiter (expected "]")
│ └ unclosed delimiter
│
└─ lib/my_app.ex:1:18
```
If you use `Logger.Backends.Console` or other backends, they are
still fully supported and functional. If you implement your own
backends, you want to consider migrating to
[`:logger_backends`](https://github.com/elixir-lang/logger_backends)
in the long term.
For unclosed delimiters, it now shows where the unclosed delimiter starts:
See the new `Logger` documentation for more information on the
new features and on compatibility.
```
** (TokenMissingError) token missing on lib/my_app:8:23:
error: missing terminator: )
│
1 │ my_numbers = (1, 2, 3, 4, 5, 6
│ └ unclosed delimiter
...
8 │ IO.inspect(my_numbers)
│ └ missing closing delimiter (expected ")")
│
└─ lib/my_app:8:23
```
## v1.15.0-rc.1 (2022-05-29)
Errors and warnings diagnostics also include code snippets. When possible, we will show precise spans, such as on undefined variables:
```
error: undefined variable "unknown_var"
│
5 │ a - unknown_var
│ ^^^^^^^^^^^
│
└─ lib/sample.ex:5:9: Sample.foo/1
```
Otherwise the whole line is underlined:
```
error: function names should start with lowercase characters or underscore, invalid name CamelCase
│
3 │ def CamelCase do
│ ^^^^^^^^^^^^^^^^
│
└─ lib/sample.ex:3
```
A huge thank you to Vinícius Müller for working on the new diagnostics.
## Revamped documentation
Elixir's Getting Started guides have been made part of the Elixir repository and incorporated into ExDoc. This was an opportunity to revisit and unify all official guides and references.
We have also incorporated and extended the work on [Understanding Code Smells in Elixir Functional Language](https://github.com/lucasvegi/Elixir-Code-Smells/blob/main/etc/2023-emse-code-smells-elixir.pdf), by Lucas Vegi and Marco Tulio Valente, from [ASERG/DCC/UFMG](http://aserg.labsoft.dcc.ufmg.br/), into the official document in the form of anti-patterns. The anti-patterns are divided into four categories: code-related, design-related, process-related, and meta-programming. Our goal is to give all developers examples of potential anti-patterns, with context and examples on how to improve their codebases.
Another [ExDoc](https://github.com/elixir-lang/ex_doc) feature we have incorporated in this release is the addition of cheatsheets, starting with [a cheatsheet for the Enum module](https://hexdocs.pm/elixir/main/enum-cheat.html). If you would like to contribute future cheatsheets to Elixir itself, feel free to start a discussion with an issue.
Finally, we have started enriching our documentation with [Mermaid.js](https://mermaid.js.org/) diagrams. You can find examples in the [GenServer](https://hexdocs.pm/elixir/main/GenServer.html) and [Supervisor](https://hexdocs.pm/elixir/main/Supervisor.html) docs.
## v1.16.3 (2024-05-21)
### 1. Bug fixes
#### Elixir
* [bin/elixir] Properly handle the `--dbg` flag in Elixir's CLI
* [Code.Formatter] Add brackets around keyword lists when formatting the left-hand side of `when`
* [Kernel] Only infer size in pinned variable in binary strings when needed
* [System] Add a note that arguments are unsafe when invoking .bat/.com scripts on Windows via `System.cmd/3`
* [Port] Add a note that arguments are unsafe when invoking .bat/.com scripts on Windows
* [URI] Ensure `:undefined` fields are properly converted to `nil` when invoking Erlang's API
#### Logger
* [Logger] Ensure translators are persisted across logger restarts
#### Mix
* [mix compile] Ensure compile paths are accessible during compilation
## v1.16.2 (2024-03-10)
### 1. Enhancements
#### Elixir
* [File] Support distributed `File.Stream`
* [Module] Add `Module.get_last_attribute/3`
* [Task] Reduce footprint of tasks by avoiding unecessary work during spawning
* [Code] Emit `:defmodule` tracing event on module definition
#### ExUnit
#### Mix
* [ExUnit.Case] Add `ExUnit.Case.get_last_registered_test/1`
* [Mix] Add `Mix.install_project_dir/0`
* [Mix] Add environment variable for reusing `Mix.install/2` installation
* [Mix.SCM] Add `Mix.SCM.delete/1`
### 2. Bug fixes
#### Elixir
* [Code] Ensure `:on_undefined_variable` option works as advertised (regression)
* [Code] Format paths in `Code.with_diagnostic/2` as relative paths (regression)
* [Kernel] Raise when macros are given to dialyzer
* [Kernel] Support bitstring specifiers as map keys in pattern (regression)
* [Module] Ensure that `Module.get_attribute/3` returns `nil` and not the given default value when an attribute has been explicitly set as `nil`
* [Task] Do not double log Task failure reports
#### ExUnit
* [ExUnit.CaptureLog] Allow capturing deprecated log level (regression)
* [ExUnit.DocTest] Ensure proper line is returned when failing to parse doctest results
* [Code] Fix charlist formatting issue when a single-quoted charlist escapes a double-quote character
* [Path] Fix regression on how `Path.relative_to/2` dealt with "." as input
#### IEx
* [IEx] Fix IO operations not returning when booting IEx (regression)
* [IEx.Helpers] Discard mermaid fenced blocks from ansi docs
#### Mix
#### ExUnit
* [mix deps] Ensure dependencies with `included_applications` can be loaded (regression)
* [mix format] Ensure proper formatter options are returned for files (regression)
* [ExUnit] Properly compared pinned values when building diffs
### 3. Soft deprecations
## v1.16.1 (2024-01-31)
### 1. Bug fixes
#### Elixir
* [Kernel] Require pin variable when accessing variable inside binary size in match
* [Code] Fix `Code.quoted_to_algebra/2` for operator with :do key as operand
* [Kernel.ParallelCompiler] Do not crash parallel compiler when it receives diagnostics from additional code evaluation
* [Kernel.ParallelCompiler] Always log errors at the end of compilation
* [String] Fix `String.capitalize/1` with a single codepoint
## v1.15.0-rc.0 (2022-05-22)
#### IEx
* [IEx] Fix autocompletion of function signatures on Erlang/OTP 26
* [IEx] Do not assume `$HOME` is set
#### Mix
* [mix deps.compile] Handle compilation of rebar3 dependencies when rebar3 is on a path with spaces on Unix
* [mix test] Properly resolve relative paths when running tests from individual files
* [mix test] Properly resolve Windows paths when running tests from individual files
## v1.16.0 (2023-12-22)
### 1. Enhancements
#### EEx
* [EEx] Include source code snippets in syntax errors
* [EEx] Include relative file information in diagnostics
#### Elixir
* [Calendar] Add support for epoch time (`%s`) to `Calendar.strftime/2`
* [Code] `Code.format_string!/2` now converts `'charlists'` into `~c"charlists"` by default
* [Code] Add `:on_undefined_variable` to the compiler options to preserve the warning behaviour which was deprecated back in Elixir v1.4
* [Code] Add `Code.loaded?/1` and `Code.ensure_all_loaded(!)/1`
* [Code] Add `Code.prepend_paths/1`, `Code.append_paths/1`, and `Code.delete_paths/1`
* [Code] Add `Code.with_diagnostics/2` to return diagnostics when compiling and evaluating code
* [Code.Fragment] Support nested expressions in `Code.Fragment.cursor_context/1`
* [Code.Fragment] Keep operators and no paren calls in `Code.Fragment.container_cursor_to_quoted/1`
* [Date] Add `Date.before?/2` and `Date.after?/2`
* [DateTime] Add `DateTime.before?/2` and `DateTime.after?/2`
* [DateTime] Support precision in `DateTime.utc_now/2`
* [Inspect] `Inspect` now renders `'charlists'` as `~c"charlists"` by default
* [Kernel] Break down `case` and `cond` inside `dbg/2`
* [Kernel] Add `t:nonempty_binary/0` and `t:nonempty_bitstring/0`
* [Kernel] Treat `@behaviour`s as runtime dependencies
* [Kernel] Do not add runtime dependencies for alias references in patterns and guards
* [Kernel] Warn for nested calls without parens inside keywords
* [Kernel] Support for multi-letter uppercase sigils
* [Kernel] Introduce mechanism to collect several errors in a module. Previously, as soon as there was a compilation error, compilation would fail. Now the compiler became a bit smarter and will report multiple errors whenever possible as multiple `error: ...` messages, similar to `warning: ...`
* [Kernel] Raise instead of warning on undefined variables. Previously, an undefined variable would attempt to invoke a function of the same name, which led to confusing error messages, especially to newcomers. To enable the previous behaviour, invoke `Code.compiler_options(on_undefined_variable: :warn)` at the top of your `mix.exs`
* [Kernel.CLI] Support `--sname undefined`/`--name undefined` so a name is automatically generated
* [Keyword] Add `Keyword.split_with/2`
* [Macro] Improve error message when piping into an expression ending in bracket-based access
* [Macro.Env] Add `Macro.Env.lookup_alias_as/2`
* [Map] Add `Map.split_with/2`
* [Map] Add `Map.intersect/2` and `Map.intersect/3`
* [MapSet] Add `MapSet.split_with/2`
* [MapSet] Optimize most functions
* [NaiveDateTime] Add `NaiveDateTime.beginning_of_day/1` and `NaiveDateTime.end_of_day/1`
* [NaiveDateTime] Add `NaiveDateTime.before?/2` and `NaiveDateTime.after?/2`
* [NaiveDateTime] Support precision in `NaiveDateTime.utc_now/2`
* [OptionParser] Support `:return_separator` option
* [Process] Add `Process.alias/0,1` and `Process.unalias/1`
* [Range] Add `Range.split/2`
* [String] Update Unicode to version 15.0.0
* [String] Add `:fast_ascii` mode to `String.valid?/2`
* [Supervisor] Add support for automatic shutdown in `Supervisor`
* [System] Support `:lines` in `System.cmd/3` to capture output line by line
* [Task] Remove head of line blocking on `Task.yield_many/2`
* [Task] Enable selective receive optimizations in Erlang/OTP 26+
* [Task.Supervisor] Do not copy args on temporary `Task.Supervisor.start_child/2`
* [Time] Add `Time.before?/2` and `Time.after?/2`
* [URI] Add `URI.append_path/2`
#### ExUnit
* [ExUnit] Add more color configuration to ExUnit CLI formatter
* [ExUnit.Callbacks] Accept `{module, function}` tuples in ExUnit `setup` callbacks
* [ExUnit.Doctest] Add `ExUnit.DocTest.doctest_file/2`
* [ExUnit.Formatter] When comparing two anonymous functions, defined at the same place but capturing a different environment, we will now also diff the environments
#### IEx
* [IEx] Make pry opt-in on dbg with `--dbg pry`
* [IEX] Support `IEX_HOME`
* [IEx.Autocomplete] Only provide aliases when autocompleting `alias`, `import`, and `require`
* [IEx.Autocomplete] Provide field completion on map and struct updates
* [IEx.Helpers] Add `runtime_info(:allocators)`
* [IEx.Info] Implement protocol for `Range`, `DateTime`, and `Regex`
* [Code] Add `:emit_warnings` for `Code.string_to_quoted/2`
* [Code] Automatically include columns in parsing options
* [Code] Introduce `MismatchedDelimiterError` for handling mismatched delimiter exceptions
* [Code.Fragment] Handle anonymous calls in fragments
* [Code.Formatter] Trim trailing whitespace on heredocs with `\r\n`
* [File] Add `:offset` option to `File.stream!/2`
* [Kernel] Auto infer size of matched variable in bitstrings
* [Kernel] Preserve column information when translating typespecs
* [Kernel] Suggest module names based on suffix and casing errors when the module does not exist in `UndefinedFunctionError`
* [Kernel.ParallelCompiler] Introduce `Kernel.ParallelCompiler.pmap/2` to compile multiple additional entries in parallel
* [Kernel.SpecialForms] Warn if `True`/`False`/`Nil` are used as aliases and there is no such alias
* [Macro] Add `Macro.compile_apply/4`
* [Module] Add support for `@nifs` annotation from Erlang/OTP 25
* [Module] Add support for missing `@dialyzer` configuration
* [String] Update to Unicode 15.1.0
* [String] Add `String.replace_invalid/2`
* [Task] Add `:limit` option to `Task.yield_many/2`
#### Logger
* [Logger] Add `Logger.add_handlers/1` and `Logger.default_formatter/1`
* [Logger] Introduce `default_formatter` and `default_handler` configuration for Logger which configures Erlang/OTP logger
* [Logger] Add `:always_evaluate_messages` configuration to Logger
* [Logger.Formatter] Implement the Erlang Logger formatter API
* [Logger.Formatter] Add support for ports in Logger metadata
* [Logger] Add `Logger.levels/0`
#### Mix
* [mix app.start] Allow applications to be started concurrently via the `:start_concurrently` configuration
* [mix compile] Set `--all-warnings` by default
* [mix compile] Reduce the amount of filesystem lookups for path dependencies by storing timestamps in manifests
* [mix compile] Track digests of `@external_resources`
* [mix compile.app] Write `optional_applications` to `.app` file
* [mix compile.elixir] Add `--purge-consolidation-path-if-stale` which will purge the given consolidation path if compilation is required
* [mix deps.compile] Automatically recompile dependencies if their compile env changes
* [mix deps.get] Automatically install Hex and Rebar on `mix deps.get`/`mix deps.update`
* [mix deps.get] Support `--check-locked` which raises if changes to the lockfile are required
* [mix eval] Allow passing additional arguments
* [mix format] Support `--no-exit` option
* [mix format] Allow multiple formatters per file extension and sigil
* [mix format] Show diffs whenever `--check-formatted` fails
* [mix format] Allow the formatting root to be configured
* [mix loadpaths] Cache deps and archive loadpaths in Erlang/OTP 26
* [mix profile.fprof] Support `--trace-to-file` to improve performance when working with large outputs
* [mix release] Allow passing additional arguments to the `eval` command
* [mix xref graph] Support `--output` flag
* [Mix.Project] Support `def cli` to unify all CLI defaults in a single place
* [Mix.Project] Add `Mix.Project.deps_tree/1`
* [mix] Add `MIX_PROFILE` to profile a list of comma separated tasks
* [mix archive.install] Support `--sparse` option
* [mix compile.app] Warn if both `:applications` and `:extra_applications` are used
* [mix compile.elixir] Pass original exception down to diagnostic `:details` when possible
* [mix compile.elixir] Optimize scenario where there are thousands of files in `lib/` and one of them is changed
* [mix deps.clean] Emit a warning instead of crashing when a dependency cannot be removed
* [mix escript.build] Escripts now strip .beam files by default, which leads to smaller escripts. However, if you are using escripts to access Elixir docs or compile Elixir code, documentation and deprecation metadata is no longer available. Set `strip_beams: false` in your escript configuration in your `mix.exs` to keep all metadata
* [mix escript.install] Support `--sparse` option
* [mix release] Include `include/` directory in releases
* [mix test] Allow testing multiple file:line at once, such as `mix test test/foo_test.exs:13 test/bar_test.exs:27`
### 2. Bug fixes
#### Elixir
* [Code.Formatter] Fix a scenario where a keyword followed by parenthesis could go above the maximum line length
* [Code.Formatter] Remove unnecessary parens in nullary type funs
* [Exception] Fix operator precedence when printing guards in `Exception.blame/3`
* [File] Do not raise if there are file system race conditions in `File.cp/2`
* [File] Do not raise when deleting write-only empty directories on `File.rm_rf/1`
* [Kernel] Expand macros on the left side of -> in `try/rescue`
* [Kernel] Raise on misplaced `...` inside typespecs
* [Kernel] Do not import `behaviour_info` and `module_info` functions from Erlang modules
* [Kernel.ParallelCompiler] Make sure compiler doesn't crash when there are stray messages in the inbox
* [Kernel.ParallelCompiler] Track compile and runtime warnings separately
* [System] Fix race condition when a script would terminate before `System.stop/1` executes
* [URI] Make sure `URI.merge/2` works accordingly with relative paths
* [Code] Keep quotes for atom keys in formatter
* [Code.Fragment] Fix crash in `Code.Fragment.surround_context/2` when matching on `->`
* [IO] Raise when using `IO.binwrite/2` on terminated device (mirroring `IO.write/2`)
* [Kernel] Do not expand aliases recursively (the alias stored in Macro.Env is already expanded)
* [Kernel] Ensure `dbg` module is a compile-time dependency
* [Kernel] Warn when a private function or macro uses `unquote/1` and the function/macro itself is unused
* [Kernel] Re-enabled compiler optimizations for top level functions in scripts (disabled in v1.14.0 but shouldn't impact most programs)
* [Kernel] Do not define an alias for nested modules starting with `Elixir.` in their definition
* [Kernel.ParallelCompiler] Consider a module has been defined in `@after_compile` callbacks to avoid deadlocks
* [Macro] Address exception on `Macro.to_string/1` for certain ASTs
* [Path] Lazily evaluate `File.cwd!/0` in `Path.expand/1` and `Path.absname/1`
* [Path] Ensure `Path.relative_to/2` returns a relative path when the given argument does not share a common prefix with `cwd`
#### ExUnit
* [ExUnit] Fix crash when `@tag capture_log: true` was set to true and the Logger application was shut down in the middle of the test
* [ExUnit] Do not merge context as tags inside the runner to reduce memory usage when emitting events to formatters
* [ExUnit] Do not expand or collect vars from quote in ExUnit assertions
* [ExUnit] Raise on incorrectly dedented doctests
#### IEx
* [IEx] Do not spawn a process to read IO. This fixes a bug where multiline paste stopped working
whenever the input reader was killed
* [IEx] Do not perform completion for prompts triggered during code evaluation
* [IEx.Pry] Fix prying functions with only literals in their body
#### Mix
* [mix compile] Include `cwd` in compiler cache key
* [mix release] Fix Windows service when invoking `erlsrv.exe` in path with spaces
* [mix archive.install] Restore code paths after `mix archive.install`
* [mix compile] Ensure files with duplicate modules are recompiled whenever any of the files change
* [mix compile] Update Mix compiler diagnostics documentation and typespecs to match the Elixir compiler behaviour where both lines and columns start from one (before it inaccurately said that columns started from zero)
* [mix escript.install] Restore code paths after `mix escript.install`
### 3. Soft deprecations (no warnings emitted)
#### Elixir
* [File] `File.cp/3` and `File.cp_r/3` with a function as third argument
is deprecated in favor of a keyword list
* [Kernel.ParallelCompiler] Require the `:return_diagnostics` option to be
set to true when compiling or requiring code
#### Logger
* [Logger] `add_backend/2`, `remove_backend/2`, and `configure_backend/2` have been deprecated
in favor of the new `:logger_backends` dependency
* [Logger] The `:console` configuration has been deprecated in favor of `:default_formatter`
* [Logger] The `:backends` configuration has been deprecated in favor of `Logger.add_handlers/1`
* [File] Deprecate `File.stream!(file, options, line_or_bytes)` in favor of keeping the options as last argument, as in `File.stream!(file, line_or_bytes, options)`
* [Kernel.ParallelCompiler] Deprecate `Kernel.ParallelCompiler.async/1` in favor of `Kernel.ParallelCompiler.pmap/2`
* [Path] Deprecate `Path.safe_relative_to/2` in favor of `Path.safe_relative/2`
#### Mix
* [Mix.Project] `:preferred_cli_env` is deprecated in favor of `:preferred_envs` in `def cli`
* [Mix.Project] `:preferred_cli_target` is deprecated in favor of `:preferred_targets` in `def cli`
* [mix local] The environment variable `HEX_MIRROR` is deprecated in favor of `HEX_BUILDS_URL`
* [mix compile] Returning a four-element tuple as a position in `Mix.Task.Compiler.Diagnostic`
### 4. Hard deprecations
#### Elixir
* [Calendar] `Calendar.ISO.day_of_week/3` is deprecated in favor of `Calendar.ISO.day_of_week/4`
* [Exception] `Exception.exception?/1` is deprecated in favor of `Kernel.is_exception/1`
* [Kernel] Deprecate `...` as a valid function call identifier
* [Regex] `Regex.regex?/1` is deprecated in favor of `Kernel.is_struct/2`
* [Date] Deprecate inferring a range with negative step, call `Date.range/3` with a negative step instead
* [Enum] Deprecate passing a range with negative step on `Enum.slice/2`, give `first..last//1` instead
* [Kernel] `~R/.../` is deprecated in favor of `~r/.../`. This is because `~R/.../` still allowed escape codes, which did not fit the definition of uppercase sigils
* [String] Deprecate passing a range with negative step on `String.slice/2`, give `first..last//1` instead
#### Logger
#### ExUnit
* [Logger] `Logger.warn/2` is deprecated in favor of `Logger.warning/2`
* [ExUnit.Formatter] Deprecate `format_time/2`, use `format_times/1` instead
## v1.14
#### Mix
The CHANGELOG for v1.14 releases can be found [in the v1.14 branch](https://github.com/elixir-lang/elixir/blob/v1.14/CHANGELOG.md).
* [mix compile.leex] Require `:leex` to be added as a compiler to run the `leex` compiler
* [mix compile.yecc] Require `:yecc` to be added as a compiler to run the `yecc` compiler
## v1.15
The CHANGELOG for v1.15 releases can be found [in the v1.15 branch](https://github.com/elixir-lang/elixir/blob/v1.15/CHANGELOG.md).
+3 -5
View File
@@ -2,9 +2,7 @@ PREFIX ?= /usr/local
TEST_FILES ?= "*_test.exs"
SHARE_PREFIX ?= $(PREFIX)/share
MAN_PREFIX ?= $(SHARE_PREFIX)/man
#CANONICAL := MAJOR.MINOR/
CANONICAL ?= main/
DOCS_FORMAT ?= html
# CANONICAL := main/
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
ERLC := erlc -I lib/elixir/include
ERL_MAKE := if [ -n "$(ERLC_OPTS)" ]; then ERL_COMPILER_OPTIONS=$(ERLC_OPTS) erl -make; else erl -make; fi
@@ -47,7 +45,7 @@ lib/$(1)/ebin/Elixir.$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1
@ rm -rf lib/$(1)/ebin
$(Q) cd lib/$(1) && ../../$$(ELIXIRC) "lib/**/*.ex" -o ebin
test_$(1): compile $(1)
test_$(1): test_formatted $(1)
@ echo "==> $(1) (ex_unit)"
$(Q) cd lib/$(1) && ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/$(TEST_FILES)";
endef
@@ -181,7 +179,7 @@ clean_residual_files:
LOGO_PATH = $(shell test -f ../docs/logo.png && echo "--logo ../docs/logo.png")
SOURCE_REF = $(shell tag="$(call GIT_TAG)" revision="$(call GIT_REVISION)"; echo "$${tag:-$$revision}")
DOCS_COMPILE = CANONICAL=$(CANONICAL) bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" --main "$(3)" --source-url "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" $(call LOGO_PATH) --output doc/$(2) --canonical "https://hexdocs.pm/$(2)/$(CANONICAL)" --homepage-url "https://elixir-lang.org/docs.html" --formatter "$(DOCS_FORMAT)" $(4)
DOCS_COMPILE = CANONICAL=$(CANONICAL) bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" --main "$(3)" --source-url "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" $(call LOGO_PATH) --output doc/$(2) --canonical "https://hexdocs.pm/$(2)/$(CANONICAL)" --homepage-url "https://elixir-lang.org/docs.html" $(4)
DOCS_CONFIG = bin/elixir lib/elixir/scripts/docs_config.exs "$(1)"
docs: compile ../ex_doc/bin/ex_doc docs_elixir docs_eex docs_mix docs_iex docs_ex_unit docs_logger
+3 -5
View File
@@ -89,7 +89,7 @@ After that, clone this repository to your machine, compile and test it:
```sh
git clone https://github.com/elixir-lang/elixir.git
cd elixir
make clean test
make
```
> Note: if you are running on Windows,
@@ -99,9 +99,7 @@ on Windows](https://github.com/elixir-lang/elixir/wiki/Windows).
In case you want to use this Elixir version as your system version,
you need to add the `bin` directory to [your PATH environment variable](https://elixir-lang.org/install.html#setting-path-environment-variable).
If Elixir fails to build (specifically when pulling in a new version via
`git`), be sure to remove any previous build artifacts by running
`make clean`, then `make test`.
Additionally, you may choose to run the test suite with `make clean test`.
## Contributing
@@ -204,7 +202,7 @@ to be installed and built alongside Elixir:
```sh
# After cloning and compiling Elixir, in its parent directory:
git clone https://github.com/elixir-lang/ex_doc.git
cd ex_doc && ../elixir/bin/mix do deps.get + compile
cd ex_doc && ../elixir/bin/elixir ../elixir/bin/mix do deps.get + compile
```
Now go back to Elixir's root directory and run:
+2 -2
View File
@@ -14,13 +14,13 @@
6. Copy the relevant bits from /CHANGELOG.md to the GitHub release and publish it
7. Add the release to `elixir.csv` with the minimum supported OTP version (all releases), update `erlang.csv` to the latest supported OTP version, and `_data/elixir-versions.yml` (except for RCs) files in `elixir-lang/elixir-lang.github.com`
7. Update `_data/elixir-versions.yml` (except for RCs) in `elixir-lang/elixir-lang.github.com`
## Creating a new vMAJOR.MINOR branch (after first rc)
### In the new branch
1. Set `CANONICAL=` in /Makefile
1. Comment out `CANONICAL=` in /Makefile
2. Update tables in /SECURITY.md and "Compatibility and Deprecations"
+6 -7
View File
@@ -6,19 +6,18 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
Elixir version | Support
:------------- | :-----------------------------
1.15 | Development
1.14 | Bug fixes and security patches
1.16 | Bug fixes and security patches
1.15 | Security patches only
1.14 | Security patches only
1.13 | Security patches only
1.12 | Security patches only
1.11 | Security patches only
1.10 | Security patches only
## Announcements
New releases are announced in the read-only [announcements mailing list](https://groups.google.com/group/elixir-lang-ann). You can subscribe by sending an email to elixir-lang-ann+subscribe@googlegroups.com and replying to the confirmation email.
New releases are announced in the read-only [announcements mailing list](https://groups.google.com/group/elixir-lang-ann). You can subscribe by sending an email to elixir-lang-ann+subscribe@googlegroups.com and replying to the confirmation email. Security notifications [will be tagged with `[security]`](https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date).
Security notifications [will be tagged with `[security]`](https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date).
You may also see [all releases](https://github.com/elixir-lang/elixir/releases) and [consult all disclosed vulnerabilities](https://github.com/elixir-lang/elixir/security) on GitHub.
## Reporting a vulnerability
Please disclose security vulnerabilities privately at elixir-security@googlegroups.com
[Please disclose security vulnerabilities privately via GitHub](https://github.com/elixir-lang/elixir/security).
+1 -1
View File
@@ -1 +1 @@
1.15.0-rc.1
1.16.3
+11 -6
View File
@@ -1,7 +1,7 @@
#!/bin/sh
set -e
ELIXIR_VERSION=1.15.0-rc.1
ELIXIR_VERSION=1.16.3
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
cat <<USAGE >&2
@@ -94,6 +94,7 @@ starts_with () {
}
ERL_EXEC="erl"
MODE="cli"
I=1
E=0
LENGTH=$#
@@ -104,13 +105,17 @@ while [ $I -le $LENGTH ]; do
S=0
C=0
case "$1" in
+elixirc|+iex)
+elixirc)
C=1
;;
-v|--no-halt|--dbg)
+iex)
C=1
MODE="iex"
;;
-v|--no-halt)
C=1
;;
-e|-r|-pr|-pa|-pz|--eval|--remsh|--dot-iex)
-e|-r|-pr|-pa|-pz|--eval|--remsh|--dot-iex|--dbg)
C=2
;;
--rpc-eval)
@@ -223,12 +228,12 @@ fi
ERTS_BIN=
ERTS_BIN="$ERTS_BIN"
set -- "$ERTS_BIN$ERL_EXEC" -noshell -elixir_root "$SCRIPT_PATH"/../lib -pa "$SCRIPT_PATH"/../lib/elixir/ebin $ELIXIR_ERL_OPTIONS -s elixir start_cli $ERL "$@"
set -- "$ERTS_BIN$ERL_EXEC" -noshell -elixir_root "$SCRIPT_PATH"/../lib -pa "$SCRIPT_PATH"/../lib/elixir/ebin $ELIXIR_ERL_OPTIONS -s elixir start_$MODE $ERL "$@"
if [ -n "$RUN_ERL_PIPE" ]; then
ESCAPED=""
for PART in "$@"; do
ESCAPED="$ESCAPED $(echo "$PART" | sed 's@[^a-zA-Z0-9_/-]@\\&@g')"
ESCAPED="$ESCAPED $(printf '%s' "$PART" | sed 's@[^a-zA-Z0-9_/-]@\\&@g')"
done
mkdir -p "$RUN_ERL_PIPE"
mkdir -p "$RUN_ERL_LOG"
+15 -13
View File
@@ -1,6 +1,6 @@
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
set ELIXIR_VERSION=1.15.0-rc.1
set ELIXIR_VERSION=1.16.3
setlocal enabledelayedexpansion
if ""%1""=="""" if ""%2""=="""" goto documentation
@@ -81,9 +81,6 @@ set beforeExtra=
rem Option which determines whether the loop is over
set endLoop=0
rem Designates which mode / Elixir component to run as
set runMode="elixir"
rem Designates the path to the current script
set SCRIPT_PATH=%~dp0
@@ -106,7 +103,7 @@ if !endLoop! == 1 (
)
rem ******* EXECUTION OPTIONS **********************
if !par!=="--werl" (set useWerl=1 && goto startloop)
if !par!=="+iex" (set parsElixir=!parsElixir! +iex && goto startloop)
if !par!=="+iex" (set parsElixir=!parsElixir! +iex && set useIEx=1 && goto startloop)
if !par!=="+elixirc" (set parsElixir=!parsElixir! +elixirc && goto startloop)
rem ******* EVAL PARAMETERS ************************
if ""==!par:-e=! (
@@ -143,16 +140,16 @@ if ""==!par:--remsh=! (set "parsElixir=!parsElixir! --remsh %~1" && shift &&
if ""==!par:--dot-iex=! (set "parsElixir=!parsElixir! --dot-iex %~1" && shift && goto startloop)
if ""==!par:--dbg=! (set "parsElixir=!parsElixir! --dbg %~1" && shift && goto startloop)
rem ******* ERLANG PARAMETERS **********************
if ""==!par:--boot=! (set "parsErlang=!parsErlang! -boot %~1" && shift && goto startloop)
if ""==!par:--boot-var=! (set "parsErlang=!parsErlang! -boot_var %~1 %~2" && shift && shift && goto startloop)
if ""==!par:--cookie=! (set "parsErlang=!parsErlang! -setcookie %~1" && shift && goto startloop)
if ""==!par:--boot=! (set "parsErlang=!parsErlang! -boot "%~1"" && shift && goto startloop)
if ""==!par:--boot-var=! (set "parsErlang=!parsErlang! -boot_var "%~1" "%~2"" && shift && shift && goto startloop)
if ""==!par:--cookie=! (set "parsErlang=!parsErlang! -setcookie "%~1"" && shift && goto startloop)
if ""==!par:--hidden=! (set "parsErlang=!parsErlang! -hidden" && goto startloop)
if ""==!par:--erl-config=! (set "parsErlang=!parsErlang! -config %~1" && shift && goto startloop)
if ""==!par:--erl-config=! (set "parsErlang=!parsErlang! -config "%~1"" && shift && goto startloop)
if ""==!par:--logger-otp-reports=! (set "parsErlang=!parsErlang! -logger handle_otp_reports %1" && shift && goto startloop)
if ""==!par:--logger-sasl-reports=! (set "parsErlang=!parsErlang! -logger handle_sasl_reports %1" && shift && goto startloop)
if ""==!par:--name=! (set "parsErlang=!parsErlang! -name %~1" && shift && goto startloop)
if ""==!par:--sname=! (set "parsErlang=!parsErlang! -sname %~1" && shift && goto startloop)
if ""==!par:--vm-args=! (set "parsErlang=!parsErlang! -args_file %~1" && shift && goto startloop)
if ""==!par:--name=! (set "parsErlang=!parsErlang! -name "%~1"" && shift && goto startloop)
if ""==!par:--sname=! (set "parsErlang=!parsErlang! -sname "%~1"" && shift && goto startloop)
if ""==!par:--vm-args=! (set "parsErlang=!parsErlang! -args_file "%~1"" && shift && goto startloop)
if ""==!par:--erl=! (set "beforeExtra=!beforeExtra! %~1" && shift && goto startloop)
if ""==!par:--pipe-to=! (echo --pipe-to : Option is not supported on Windows && goto end)
set endLoop=1
@@ -164,8 +161,13 @@ reg query HKCU\Console /v VirtualTerminalLevel 2>nul | findstr /e "0x1" >nul 2>n
if %errorlevel% == 0 (
set beforeExtra=-elixir ansi_enabled true !beforeExtra!
)
if defined useIEx (
set beforeExtra=-s elixir start_iex !beforeExtra!
) else (
set beforeExtra=-s elixir start_cli !beforeExtra!
)
set beforeExtra=-noshell -elixir_root !SCRIPT_PATH!..\lib -pa !SCRIPT_PATH!..\lib\elixir\ebin -s elixir start_cli !beforeExtra!
set beforeExtra=-noshell -elixir_root "!SCRIPT_PATH!..\lib" -pa "!SCRIPT_PATH!..\lib\elixir\ebin" !beforeExtra!
if defined ELIXIR_CLI_DRY_RUN (
if defined useWerl (
+1 -1
View File
@@ -30,4 +30,4 @@ readlink_f () {
SELF=$(readlink_f "$0")
SCRIPT_PATH=$(dirname "$SELF")
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user elixir" -e ":elixir.start_iex()" +iex "$@"
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user elixir" +iex "$@"
+1 -1
View File
@@ -24,6 +24,6 @@ goto end
:run
if defined IEX_WITH_WERL (set __ELIXIR_IEX_FLAGS=--werl) else (set __ELIXIR_IEX_FLAGS=)
call "%~dp0\elixir.bat" --no-halt --erl "-user elixir" -e ":elixir.start_iex()" +iex %__ELIXIR_IEX_FLAGS% %*
call "%~dp0\elixir.bat" --no-halt --erl "-user elixir" +iex %__ELIXIR_IEX_FLAGS% %*
:end
endlocal
+5 -2
View File
@@ -1,9 +1,12 @@
defmodule EEx.SyntaxError do
defexception [:message, :file, :line, :column]
defexception [:file, :line, :column, :snippet, message: "syntax error"]
@impl true
def message(exception) do
"#{exception.file}:#{exception.line}:#{exception.column}: #{exception.message}"
%{file: file, line: line, column: column, message: message, snippet: snippet} = exception
Exception.format_file_line_column(file && Path.relative_to_cwd(file), line, column, " ") <>
message <> (snippet || "")
end
end
+5 -6
View File
@@ -71,11 +71,9 @@ defmodule EEx.Compiler do
{:ok, expr, new_line, new_column, rest} ->
{key, expr} =
case :elixir_tokenizer.tokenize(expr, 1, file: "eex", check_terminators: false) do
{:ok, _line, _column, warnings, tokens} ->
Enum.each(Enum.reverse(warnings), fn {location, file, msg} ->
:elixir_errors.erl_warn(location, file, msg)
end)
{:ok, _line, _column, _warnings, tokens} ->
# We ignore warnings because the code will be tokenized
# again later with the right line+column info
token_key(tokens, expr)
{:error, _, _, _, _} ->
@@ -491,7 +489,8 @@ defmodule EEx.Compiler do
defp syntax_error!(message, meta, state) do
raise EEx.SyntaxError,
message: message <> code_snippet(state.source, state.indentation, meta),
message: message,
snippet: code_snippet(state.source, state.indentation, meta),
file: state.file,
line: meta.line,
column: meta.column
+1 -1
View File
@@ -5,7 +5,7 @@ defmodule EEx.TokenizerTest do
@opts [indentation: 0, trim: false]
test "simple chars lists" do
test "simple charlists" do
assert EEx.tokenize(~c"foo", @opts) ==
{:ok, [{:text, ~c"foo", %{column: 1, line: 1}}, {:eof, %{column: 4, line: 1}}]}
end
+76 -60
View File
@@ -273,6 +273,32 @@ defmodule EExTest do
end
describe "raises syntax errors" do
test "with relative file information" do
message = """
foobar.eex:1:5: expected closing '%>' for EEx expression
|
1 | foo <%= bar
| ^\
"""
assert_raise EEx.SyntaxError, message, fn ->
EEx.compile_string("foo <%= bar", file: Path.join(File.cwd!(), "foobar.eex"))
end
end
test "when <%!-- is not closed" do
message = """
my_file.eex:1:5: expected closing '--%>' for EEx expression
|
1 | foo <%!-- bar
| ^\
"""
assert_raise EEx.SyntaxError, message, fn ->
EEx.compile_string("foo <%!-- bar", file: "my_file.eex")
end
end
test "when the token is invalid" do
message = """
nofile:1:5: expected closing '%>' for EEx expression
@@ -463,17 +489,13 @@ defmodule EExTest do
end
end
test "when middle expression has a modifier" do
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
EEx.compile_string("foo <%= if true do %>true<%= else %>false<% end %>")
end) =~ ~s[unexpected beginning of EEx tag \"<%=\" on \"<%= else %>\"]
end
test "when trying to use marker '|' without implementation" do
msg =
~r/unsupported EEx syntax <%| %> \(the syntax is valid but not supported by the current EEx engine\)/
test "when end expression has a modifier" do
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
EEx.compile_string("foo <%= if true do %>true<% else %>false<%= end %>")
end) =~
~s[unexpected beginning of EEx tag \"<%=\" on \"<%= end %>\"]
assert_raise EEx.SyntaxError, msg, fn ->
EEx.compile_string("<%| true %>")
end
end
test "when trying to use marker '/' without implementation" do
@@ -485,17 +507,6 @@ defmodule EExTest do
end
end
test "when trying to use marker '|' without implementation" do
msg =
~r/unsupported EEx syntax <%| %> \(the syntax is valid but not supported by the current EEx engine\)/
assert_raise EEx.SyntaxError, msg, fn ->
EEx.compile_string("<%| true %>")
end
end
end
describe "error messages" do
test "honor line numbers" do
assert_raise EEx.SyntaxError,
"nofile:100:6: expected closing '%>' for EEx expression",
@@ -516,18 +527,30 @@ defmodule EExTest do
EEx.compile_string("foo <%= bar", file: "my_file.eex")
end
end
end
test "when <%!-- is not closed" do
message = """
my_file.eex:1:5: expected closing '--%>' for EEx expression
|
1 | foo <%!-- bar
| ^\
"""
describe "warnings" do
test "when middle expression has a modifier" do
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
EEx.compile_string("foo <%= if true do %>true<%= else %>false<% end %>")
end) =~ ~s[unexpected beginning of EEx tag \"<%=\" on \"<%= else %>\"]
end
assert_raise EEx.SyntaxError, message, fn ->
EEx.compile_string("foo <%!-- bar", file: "my_file.eex")
end
test "when end expression has a modifier" do
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
EEx.compile_string("foo <%= if true do %>true<% else %>false<%= end %>")
end) =~
~s[unexpected beginning of EEx tag \"<%=\" on \"<%= end %>\"]
end
test "from tokenizer" do
warning =
ExUnit.CaptureIO.capture_io(:stderr, fn ->
EEx.compile_string(~s'<%= :"foo" %>', file: "tokenizer.ex")
end)
assert warning =~ "found quoted atom \"foo\" but the quotes are not required"
assert warning =~ "tokenizer.ex:1:5"
end
end
@@ -738,38 +761,31 @@ defmodule EExTest do
end
test "line and column meta" do
parser_options = Code.get_compiler_option(:parser_options)
Code.put_compiler_option(:parser_options, columns: true)
indentation = 12
try do
indentation = 12
ast =
EEx.compile_string(
"""
<%= f() %> <% f() %>
<%= f fn -> %>
<%= f() %>
<% end %>
""",
indentation: indentation
)
ast =
EEx.compile_string(
"""
<%= f() %> <% f() %>
<%= f fn -> %>
<%= f() %>
<% end %>
""",
indentation: indentation
)
{_, calls} =
Macro.prewalk(ast, [], fn
{:f, meta, _args} = expr, acc -> {expr, [meta | acc]}
other, acc -> {other, acc}
end)
{_, calls} =
Macro.prewalk(ast, [], fn
{:f, meta, _args} = expr, acc -> {expr, [meta | acc]}
other, acc -> {other, acc}
end)
assert Enum.reverse(calls) == [
[line: 1, column: indentation + 5],
[line: 1, column: indentation + 15],
[line: 2, column: indentation + 7],
[line: 3, column: indentation + 9]
]
after
Code.put_compiler_option(:parser_options, parser_options)
end
assert Enum.reverse(calls) == [
[line: 1, column: indentation + 5],
[line: 1, column: indentation + 15],
[line: 2, column: indentation + 7],
[line: 3, column: indentation + 9]
]
end
end
+55 -6
View File
@@ -191,9 +191,13 @@ defmodule Access do
case __STACKTRACE__ do
[unquote(top) | _] ->
reason =
"#{inspect(unquote(module))} does not implement the Access behaviour. " <>
"If you are using get_in/put_in/update_in, you can specify the field " <>
"to be accessed using Access.key!/1"
"""
#{inspect(unquote(module))} does not implement the Access behaviour
You can use the "struct.field" syntax to access struct fields. \
You can also use Access.key!/1 to access struct fields dynamically \
inside get_in/put_in/update_in\
"""
%{unquote(exception) | reason: reason}
@@ -326,9 +330,23 @@ defmodule Access do
end
end
def get(list, key, _default) when is_list(list) and is_integer(key) do
raise ArgumentError, """
the Access module does not support accessing lists by index, got: #{inspect(key)}
Accessing a list by index is typically discouraged in Elixir, \
instead we prefer to use the Enum module to manipulate lists \
as a whole. If you really must access a list element by index, \
you can Enum.at/1 or the functions in the List module\
"""
end
def get(list, key, _default) when is_list(list) do
raise ArgumentError,
"the Access calls for keywords expect the key to be an atom, got: " <> inspect(key)
raise ArgumentError, """
the Access module supports only keyword lists (with atom keys), got: #{inspect(key)}
If you want to search lists of tuples, use List.keyfind/3\
"""
end
def get(nil, _key, default) do
@@ -377,10 +395,26 @@ defmodule Access do
Map.get_and_update(map, key, fun)
end
def get_and_update(list, key, fun) when is_list(list) do
def get_and_update(list, key, fun) when is_list(list) and is_atom(key) do
Keyword.get_and_update(list, key, fun)
end
def get_and_update(list, key, _fun) when is_list(list) and is_integer(key) do
raise ArgumentError, """
the Access module does not support accessing lists by index, got: #{inspect(key)}
Accessing a list by index is typically discouraged in Elixir, \
instead we prefer to use the Enum module to manipulate lists \
as a whole. If you really must mostify a list element by index, \
you can Access.at/1 or the functions in the List module\
"""
end
def get_and_update(list, key, _fun) when is_list(list) do
raise ArgumentError,
"the Access module supports only keyword lists (with atom keys), got: " <> inspect(key)
end
def get_and_update(nil, key, _fun) do
raise ArgumentError, "could not put/update key #{inspect(key)} on a nil value"
end
@@ -507,6 +541,21 @@ defmodule Access do
iex> get_in(map, [Access.key!(:user), Access.key!(:unknown)])
** (KeyError) key :unknown not found in: %{name: \"john\"}
The examples above could be partially written as:
iex> map = %{user: %{name: "john"}}
iex> map.user.name
"john"
iex> get_and_update_in(map.user.name, fn prev ->
...> {prev, String.upcase(prev)}
...> end)
{"john", %{user: %{name: "JOHN"}}}
However, it is not possible to remove fields using the dot notation,
as it is implified those fields must also be present. In any case,
`Access.key!/1` is useful when the key is not known in advance
and must be accessed dynamically.
An error is raised if the accessed structure is not a map/struct:
iex> get_in([], [Access.key!(:foo)])
+4 -1
View File
@@ -200,7 +200,7 @@ defmodule Application do
In the sections above, we have configured an application in the
`application/0` section of the `mix.exs` file. Ultimately, Mix will use
this configuration to create an [*application resource
file*](https://www.erlang.org/doc/man/application.html), which is a file called
file*](https://www.erlang.org/doc/man/app), which is a file called
`APP_NAME.app`. For example, the application resource file of the OTP
application `ex_unit` is called `ex_unit.app`.
@@ -467,6 +467,9 @@ defmodule Application do
* #{Enum.map_join(@application_keys, "\n * ", &"`#{inspect(&1)}`")}
For a description of all fields, see [Erlang's application
specification](https://www.erlang.org/doc/man/app).
Note the environment is not returned as it can be accessed via
`fetch_env/2`. Returns `nil` if the application is not loaded.
"""
+17 -8
View File
@@ -71,8 +71,9 @@ defmodule Date do
A range of dates represents a discrete number of dates where
the first and last values are dates with matching calendars.
Ranges of dates can be either increasing (`first <= last`) or
decreasing (`first > last`). They are also always inclusive.
Ranges of dates can be increasing (`first <= last`) and are
always inclusive. For a decreasing range, use `range/3` with
a step of -1 as first argument.
## Examples
@@ -92,8 +93,6 @@ defmodule Date do
true
iex> Enum.take(range, 3)
[~D[2001-01-01], ~D[2001-01-02], ~D[2001-01-03]]
iex> for d <- Date.range(~D[2023-03-01], ~D[2023-04-01]), Date.day_of_week(d) == 7, do: d
[~D[2023-03-05], ~D[2023-03-12], ~D[2023-03-19], ~D[2023-03-26]]
"""
@doc since: "1.5.0"
@@ -101,8 +100,18 @@ defmodule Date do
def range(%{calendar: calendar} = first, %{calendar: calendar} = last) do
{first_days, _} = to_iso_days(first)
{last_days, _} = to_iso_days(last)
# TODO: Deprecate inferring a range with a step of -1 on Elixir v1.16
step = if first_days <= last_days, do: 1, else: -1
step =
if first_days <= last_days do
1
else
IO.warn(
"a negative range was inferred for Date.range/2, call Date.range/3 instead with -1 as third argument"
)
-1
end
range(first, first_days, last, last_days, calendar, step)
end
@@ -569,7 +578,7 @@ defmodule Date do
end
@doc """
Returns true if the first date is strictly earlier than the second.
Returns `true` if the first date is strictly earlier than the second.
## Examples
@@ -588,7 +597,7 @@ defmodule Date do
end
@doc """
Returns true if the first date is strictly later than the second.
Returns `true` if the first date is strictly later than the second.
## Examples
+38 -13
View File
@@ -72,7 +72,7 @@ defmodule DateTime do
rules, ultimately affecting the result. For example, a country may
choose to enter or abandon "Daylight Saving Time", which is a
process where we adjust the clock one hour forward or one hour
back once per year. Whenener the rules change, the exact instant
back once per year. Whenever the rules change, the exact instant
that 2:30 AM in Polish time will be in Brazil may change.
In other words, whenever working with future DateTimes, there is
@@ -88,12 +88,13 @@ defmodule DateTime do
time zone observes "Daylight Saving Time", they will move their
clock forward once a year. When this happens, there is a whole
hour that does not exist. Then, when they move the clock back,
there is a certain hour that will happen twice. So if you want
to schedule a meeting when this shift back happens, you would
need to explicitly say which of the 2:30 AM you precisely mean.
Applications that are date and time sensitive, need to take
these scenarios into account and correctly communicate them to
users.
there is a certain hour that will happen twice. So if you want to
schedule a meeting when this shift back happens, you would need to
explicitly say which occurence of 2:30 AM you mean: the one in
"Summer Time", which occurs before the shift, or the one
in "Standard Time", which occurs after it. Applications that are
date and time sensitive need to take these scenarios into account
and correctly communicate them to users.
The good news is: Elixir contains all of the building blocks
necessary to tackle those problems. The default timezone database
@@ -103,6 +104,24 @@ defmodule DateTime do
query the database and return the relevant information. For
example, look at how `DateTime.new/4` returns different results
based on the scenarios described in this section.
## Converting between timezones
Bearing in mind the cautions above, and assuming you've brought in a full
timezone database, here are some examples of common shifts between time
zones.
# Local time to UTC
new_york = DateTime.from_naive!(~N[2023-06-26T09:30:00], "America/New_York")
#=> #DateTime<2023-06-26 09:30:00-04:00 EDT America/New_York>
utc = DateTime.shift_zone!(new_york, "Etc/UTC")
#=> ~U[2023-06-26 13:30:00Z]
# UTC to local time
DateTime.shift_zone!(utc, "Europe/Paris")
#=> #DateTime<2023-06-26 15:30:00+02:00 CEST Europe/Paris>
"""
@enforce_keys [:year, :month, :day, :hour, :minute, :second] ++
@@ -174,7 +193,7 @@ defmodule DateTime do
end
@doc """
Returns the current datetime in UTC, supporting
Returns the current datetime in UTC, supporting
a specific calendar and precision.
If you want the current time in Unix seconds,
@@ -1189,7 +1208,7 @@ defmodule DateTime do
end
@doc """
Converts to ISO8601 specifying both a calendar and a mode.
Converts from ISO8601 specifying both a calendar and a mode.
See `from_iso8601/2` for more information.
@@ -1427,7 +1446,7 @@ defmodule DateTime do
end
@doc """
Returns true if the first datetime is strictly earlier than the second.
Returns `true` if the first datetime is strictly earlier than the second.
## Examples
@@ -1446,7 +1465,7 @@ defmodule DateTime do
end
@doc """
Returns true if the first datetime is strictly later than the second.
Returns `true` if the first datetime is strictly later than the second.
## Examples
@@ -1516,6 +1535,12 @@ defmodule DateTime do
%{utc_offset: utc_offset2, std_offset: std_offset2} = datetime2,
unit
) do
if not is_integer(unit) and
unit not in ~w(second millisecond microsecond nanosecond)a do
raise ArgumentError,
"unsupported time unit. Expected :day, :hour, :minute, :second, :millisecond, :microsecond, :nanosecond, or a positive integer, got #{inspect(unit)}"
end
naive_diff =
(datetime1 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(unit)) -
(datetime2 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(unit))
@@ -1532,10 +1557,10 @@ defmodule DateTime do
`t:System.time_unit/0`. It defaults to `:second`. Negative values
will move backwards in time.
This function always consider the unit to be computed according
This function always considers the unit to be computed according
to the `Calendar.ISO`.
This function uses relies on a contiguous representation of time,
This function relies on a contiguous representation of time,
ignoring the wall time and timezone changes. For example, if you add
one day when there are summer time/daylight saving time changes,
it will also change the time forward or backward by one hour,
+16 -4
View File
@@ -471,6 +471,12 @@ defmodule NaiveDateTime do
unit
)
when is_integer(amount_to_add) do
if not is_integer(unit) and
unit not in ~w(second millisecond microsecond nanosecond)a do
raise ArgumentError,
"unsupported time unit. Expected :day, :hour, :minute, :second, :millisecond, :microsecond, :nanosecond, or a positive integer, got #{inspect(unit)}"
end
ppd = System.convert_time_unit(86400, :second, unit)
precision = max(Calendar.ISO.time_unit_to_precision(unit), precision)
@@ -554,6 +560,12 @@ defmodule NaiveDateTime do
"and thus the result would be ambiguous"
end
if not is_integer(unit) and
unit not in ~w(second millisecond microsecond nanosecond)a do
raise ArgumentError,
"unsupported time unit. Expected :day, :hour, :minute, :second, :millisecond, :microsecond, :nanosecond, or a positive integer, got #{inspect(unit)}"
end
units1 = naive_datetime1 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(unit)
units2 = naive_datetime2 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(unit)
units1 - units2
@@ -1073,7 +1085,7 @@ defmodule NaiveDateTime do
end
@doc """
Returns true if the first `NaiveDateTime` is strictly earlier than the second.
Returns `true` if the first `NaiveDateTime` is strictly earlier than the second.
## Examples
@@ -1092,7 +1104,7 @@ defmodule NaiveDateTime do
end
@doc """
Returns true if the first `NaiveDateTime` is strictly later than the second.
Returns `true` if the first `NaiveDateTime` is strictly later than the second.
## Examples
@@ -1212,7 +1224,7 @@ defmodule NaiveDateTime do
datetime
|> NaiveDateTime.beginning_of_day()
|> DateTime.from_naive(datetime.timezone)
|> DateTime.from_naive(datetime.time_zone)
Note that the beginning of the day may not exist or be ambiguous
in a given timezone, so you must handle those cases accordingly.
@@ -1239,7 +1251,7 @@ defmodule NaiveDateTime do
datetime
|> NaiveDateTime.end_of_day()
|> DateTime.from_naive(datetime.timezone)
|> DateTime.from_naive(datetime.time_zone)
Note that the end of the day may not exist or be ambiguous
in a given timezone, so you must handle those cases accordingly.
+2 -2
View File
@@ -599,7 +599,7 @@ defmodule Time do
end
@doc """
Returns true if the first time is strictly earlier than the second.
Returns `true` if the first time is strictly earlier than the second.
## Examples
@@ -618,7 +618,7 @@ defmodule Time do
end
@doc """
Returns true if the first time is strictly later than the second.
Returns `true` if the first time is strictly later than the second.
## Examples
+75 -17
View File
@@ -158,6 +158,10 @@ defmodule Code do
of keys to traverse in the application environment and `return` is either
`{:ok, value}` or `:error`.
* `:defmodule` - (since v1.16.2) traced as soon as the definition of a module
starts. This is invoked early on in the module life-cycle, `Module.open?/1`
still returns `false` for such traces
* `{:on_module, bytecode, _ignore}` - (since v1.13.0) traced whenever a module
is defined. This is equivalent to the `@after_compile` callback and invoked
after any `@after_compile` in the given module. The third element is currently
@@ -196,19 +200,44 @@ defmodule Code do
@typedoc """
Diagnostics returned by the compiler and code evaluation.
The file and position relate to where the diagnostic should be shown.
If there is a file and position, then the diagnostic is precise
and you can use the given file and position for generating snippets,
IDEs annotations, and so on. An optional span is available with
the line and column the diagnostic ends.
Otherwise, a stacktrace may be given, which you can place your own
heuristics to provide better reporting.
The source field points to the source file the compiler tracked
the error to. For example, a file `lib/foo.ex` may embed `.eex`
templates from `lib/foo/bar.eex`. A syntax error on the EEx template
will point to file `lib/foo/bar.eex` but the source is `lib/foo.ex`.
"""
@type diagnostic(severity) :: %{
required(:file) => Path.t(),
required(:source) => Path.t() | nil,
required(:file) => Path.t() | nil,
required(:severity) => severity,
required(:message) => String.t(),
required(:position) => position,
required(:position) => position(),
required(:stacktrace) => Exception.stacktrace(),
required(:span) => {line :: pos_integer(), column :: pos_integer()} | nil,
optional(:details) => term(),
optional(any()) => any()
}
@typedoc "The line. 0 indicates no line."
@type line() :: non_neg_integer()
@type position() :: line() | {pos_integer(), column :: non_neg_integer}
@typedoc """
The position of the diagnostic.
Can be either a line number or a `{line, column}`.
Line and columns numbers are one-based.
A position of `0` represents unknown.
"""
@type position() :: line() | {line :: pos_integer(), column :: pos_integer()}
@boolean_compiler_options [
:docs,
@@ -553,14 +582,32 @@ defmodule Code do
@doc """
Executes the given `fun` and capture all diagnostics.
Diagnostics are warnings and errors emitted by the compiler
and by functions such as `IO.warn/2`.
Diagnostics are warnings and errors emitted during code
evaluation or single-file compilation and by functions
such as `IO.warn/2`.
If using `mix compile` or `Kernel.ParallelCompiler`,
note they already capture and return diagnostics.
## Options
* `:log` - if the diagnostics should be logged as they happen.
Defaults to `false`.
> #### Rescuing errors {: .info}
>
> `with_diagnostics/2` does not automatically handle exceptions.
> You may capture them by adding a `try/1` in `fun`:
>
> {result, all_errors_and_warnings} =
> Code.with_diagnostics(fn ->
> try do
> {:ok, Code.compile_quoted(quoted)}
> rescue
> err -> {:error, err}
> end
> end)
"""
@doc since: "1.15.0"
@spec with_diagnostics(keyword(), (-> result)) :: {result, [diagnostic(:warning | :error)]}
@@ -588,11 +635,18 @@ defmodule Code do
A diagnostic is either returned by `Kernel.ParallelCompiler`
or by `Code.with_diagnostics/2`.
## Options
* `:snippet` - whether to read the code snippet in the diagnostic location.
As it may impact performance, it is not recommended to be used in runtime.
Defaults to `true`.
"""
@doc since: "1.15.0"
@spec print_diagnostic(diagnostic(:warning | :error)) :: :ok
def print_diagnostic(diagnostic) do
:elixir_errors.print_diagnostic(diagnostic)
@spec print_diagnostic(diagnostic(:warning | :error), keyword()) :: :ok
def print_diagnostic(diagnostic, opts \\ []) do
read_snippet? = Keyword.get(opts, :snippet, true)
:elixir_errors.print_diagnostic(diagnostic, read_snippet?)
:ok
end
@@ -936,10 +990,9 @@ defmodule Code do
to_quoted_opts =
[
unescape: false,
warn_on_unnecessary_quotes: false,
literal_encoder: &{:ok, {:__block__, &2, [&1]}},
token_metadata: true,
warnings: false
emit_warnings: false
] ++ opts
{forms, comments} = string_to_quoted_with_comments!(string, to_quoted_opts)
@@ -1044,6 +1097,8 @@ defmodule Code do
"""
@doc since: "1.14.0"
@spec eval_quoted_with_env(Macro.t(), binding, Macro.Env.t(), keyword) ::
{term, binding, Macro.Env.t()}
def eval_quoted_with_env(quoted, binding, %Macro.Env{} = env, opts \\ [])
when is_list(binding) do
eval_verify(:eval_quoted, [quoted, binding, env, opts])
@@ -1098,9 +1153,8 @@ defmodule Code do
atoms but `:existing_atoms_only` is still used for dynamic atoms,
such as atoms with interpolations.
* `:warn_on_unnecessary_quotes` - when `false`, does not warn
when atoms, keywords or calls have unnecessary quotes on
them. Defaults to `true`.
* `:emit_warnings` (since v1.16.0) - when `false`, does not emit
tokenizing/parsing related warnings. Defaults to `true`.
## `Macro.to_string/2`
@@ -1136,7 +1190,10 @@ defmodule Code do
* syntax keywords (`fn`, `do`, `else`, and so on)
* atoms containing interpolation (`:"#{1 + 1} is two"`), as these
atoms are constructed at runtime.
atoms are constructed at runtime
* atoms used to represent single-letter sigils like `:sigil_X`
(but multi-letter sigils like `:sigil_XYZ` are encoded).
"""
@spec string_to_quoted(List.Chars.t(), keyword) ::
@@ -1564,7 +1621,7 @@ defmodule Code do
to the parser when compiling files. It accepts the same options as
`string_to_quoted/2` (except by the options that change the AST itself).
This can be used in combination with the tracer to retrieve localized
information about events happening during compilation. Defaults to `[]`.
information about events happening during compilation. Defaults to `[columns: true]`.
This option only affects code compilation functions, such as `compile_string/2`
and `compile_file/2` but not `string_to_quoted/2` and friends, as the
latter is used for other purposes beyond compilation.
@@ -1927,7 +1984,7 @@ defmodule Code do
end
@doc """
Returns true if the current process can await for module compilation.
Returns `true` if the current process can await for module compilation.
When compiling Elixir code via `Kernel.ParallelCompiler`, which is
used by Mix and `elixirc`, calling a module that has not yet been
@@ -2026,7 +2083,8 @@ defmodule Code do
defp get_beam_and_path(module) do
with {^module, beam, filename} <- :code.get_object_code(module),
{:ok, ^module} <- beam |> :beam_lib.info() |> Keyword.fetch(:module) do
info_pairs when is_list(info_pairs) <- :beam_lib.info(beam),
{:ok, ^module} <- Keyword.fetch(info_pairs, :module) do
{beam, filename}
else
_ -> :error
+61 -36
View File
@@ -6,7 +6,8 @@ defmodule Code.Formatter do
@double_heredoc "\"\"\""
@single_quote "'"
@single_heredoc "'''"
@sigil_c "~c\""
@sigil_c_double "~c\""
@sigil_c_single "~c'"
@sigil_c_heredoc "~c\"\"\""
@newlines 2
@min_line 0
@@ -299,7 +300,7 @@ defmodule Code.Formatter do
remote_to_algebra(quoted, context, state)
meta[:delimiter] == ~s['''] ->
{opener, quotes} = get_charlist_quotes(true, state)
{opener, quotes} = get_charlist_quotes(:heredoc, state)
{doc, state} =
entries
@@ -309,7 +310,7 @@ defmodule Code.Formatter do
{force_unfit(doc), state}
true ->
{opener, quotes} = get_charlist_quotes(false, state)
{opener, quotes} = get_charlist_quotes({:regular, entries}, state)
list_interpolation_to_algebra(entries, quotes, state, opener, quotes)
end
end
@@ -368,13 +369,14 @@ defmodule Code.Formatter do
defp quoted_to_algebra({:__block__, meta, [list]}, _context, state) when is_list(list) do
case meta[:delimiter] do
~s['''] ->
{opener, quotes} = get_charlist_quotes(true, state)
{opener, quotes} = get_charlist_quotes(:heredoc, state)
string = list |> List.to_string() |> escape_heredoc(quotes)
{opener |> concat(string) |> concat(quotes) |> force_unfit(), state}
~s['] ->
{opener, quotes} = get_charlist_quotes(false, state)
string = list |> List.to_string() |> escape_string(quotes)
string = list |> List.to_string()
{opener, quotes} = get_charlist_quotes({:regular, [string]}, state)
string = escape_string(string, quotes)
{opener |> concat(string) |> concat(quotes), state}
_other ->
@@ -431,7 +433,7 @@ defmodule Code.Formatter do
{color("nil", nil, state.inspect_opts), state}
end
defp quoted_to_algebra({:__block__, meta, _} = block, _context, state) do
defp quoted_to_algebra({:__block__, meta, args} = block, _context, state) when is_list(args) do
{block, state} = block_to_algebra(block, line(meta), closing_line(meta), state)
{surround("(", block, ")"), state}
end
@@ -515,19 +517,20 @@ defmodule Code.Formatter do
if keyword_key?(left_arg) do
{left, state} =
case left_arg do
# TODO: Remove this clause in v1.16 when we no longer quote operator :..//
# TODO: Remove this clause in v1.18 when we no longer quote operator :..//
{:__block__, _, [:"..//"]} ->
{string(~S{"..//":}), state}
{:__block__, _, [atom]} when is_atom(atom) ->
key =
iodata =
if Macro.classify_atom(atom) in [:identifier, :unquoted] do
IO.iodata_to_binary([Atom.to_string(atom), ?:])
[Atom.to_string(atom), ?:]
else
IO.iodata_to_binary([?", Atom.to_string(atom), ?", ?:])
[?", atom |> Atom.to_string() |> String.replace("\"", "\\\""), ?", ?:]
end
{string(key) |> color(:atom, state.inspect_opts), state}
{iodata |> IO.iodata_to_binary() |> string() |> color(:atom, state.inspect_opts),
state}
{{:., _, [:erlang, :binary_to_atom]}, _, [{:<<>>, _, entries}, :utf8]} ->
interpolation_to_algebra(entries, @double_quote, state, "\"", "\":")
@@ -1419,7 +1422,9 @@ defmodule Code.Formatter do
defp bitstring_segment_to_algebra({{:"::", _, [segment, spec]}, i}, state, last) do
{doc, state} = quoted_to_algebra(segment, :parens_arg, state)
{spec, state} = bitstring_spec_to_algebra(spec, state, state.normalize_bitstring_modifiers)
{spec, state} =
bitstring_spec_to_algebra(spec, state, state.normalize_bitstring_modifiers, :"::")
spec = wrap_in_parens_if_inspected_atom(spec)
spec = if i == last, do: bitstring_wrap_parens(spec, i, last), else: spec
@@ -1438,15 +1443,17 @@ defmodule Code.Formatter do
{bitstring_wrap_parens(doc, i, last), state}
end
defp bitstring_spec_to_algebra({op, _, [left, right]}, state, normalize_modifiers)
defp bitstring_spec_to_algebra({op, _, [left, right]}, state, normalize_modifiers, paren_op)
when op in [:-, :*] do
normalize_modifiers = normalize_modifiers && op != :*
{left, state} = bitstring_spec_to_algebra(left, state, normalize_modifiers)
{left, state} = bitstring_spec_to_algebra(left, state, normalize_modifiers, op)
{right, state} = bitstring_spec_element_to_algebra(right, state, normalize_modifiers)
{concat(concat(left, Atom.to_string(op)), right), state}
doc = concat(concat(left, Atom.to_string(op)), right)
doc = if paren_op == :*, do: wrap_in_parens(doc), else: doc
{doc, state}
end
defp bitstring_spec_to_algebra(spec, state, normalize_modifiers) do
defp bitstring_spec_to_algebra(spec, state, normalize_modifiers, _paren_op) do
bitstring_spec_element_to_algebra(spec, state, normalize_modifiers)
end
@@ -1557,7 +1564,7 @@ defmodule Code.Formatter do
Atom.to_string(nil) |> color(nil, inspect_opts)
end
# TODO: Remove this clause in v1.16 when we no longer quote operator :..//
# TODO: Remove this clause in v1.18 when we no longer quote operator :..//
defp atom_to_algebra(:"..//", _, inspect_opts) do
string(":\"..//\"") |> color(:atom, inspect_opts)
end
@@ -1618,25 +1625,30 @@ defmodule Code.Formatter do
end
defp insert_underscores(digits) do
byte_size = byte_size(digits)
cond do
digits =~ "_" ->
digits
byte_size(digits) >= 6 ->
digits
|> String.to_charlist()
|> Enum.reverse()
|> Enum.chunk_every(3)
|> Enum.intersperse(~c"_")
|> List.flatten()
|> Enum.reverse()
|> List.to_string()
byte_size >= 6 ->
offset = rem(byte_size, 3)
{prefix, rest} = String.split_at(digits, offset)
do_insert_underscores(prefix, rest)
true ->
digits
end
end
defp do_insert_underscores(acc, ""), do: acc
defp do_insert_underscores("", <<next::binary-3, rest::binary>>),
do: do_insert_underscores(next, rest)
defp do_insert_underscores(acc, <<next::binary-3, rest::binary>>),
do: do_insert_underscores(<<acc::binary, "_", next::binary>>, rest)
defp escape_heredoc(string, escape) do
string = String.replace(string, escape, "\\" <> escape)
heredoc_to_algebra(["" | String.split(string, "\n")])
@@ -1674,6 +1686,7 @@ defmodule Code.Formatter do
end
defp heredoc_line(["", _ | _]), do: nest(line(), :reset)
defp heredoc_line(["\r", _ | _]), do: nest(line(), :reset)
defp heredoc_line(_), do: line()
defp args_to_algebra_with_comments(args, meta, skip_parens?, last_arg_mode, join, state, fun) do
@@ -1946,6 +1959,14 @@ defmodule Code.Formatter do
# fn a, b, c when d -> e end
defp clause_args_to_algebra([{:when, meta, args}], state) do
{args, right} = split_last(args)
# If there are any keywords, wrap them in lists
args =
Enum.map(args, fn
[_ | _] = keyword -> {:__block__, [], [keyword]}
other -> other
end)
left = {{:special, :clause_args}, meta, [args]}
binary_op_to_algebra(:when, "when", meta, left, right, :no_parens_arg, state)
end
@@ -2399,19 +2420,23 @@ defmodule Code.Formatter do
{left, right}
end
defp get_charlist_quotes(_heredoc = false, state) do
if state.normalize_charlists_as_sigils do
{@sigil_c, @double_quote}
else
{@single_quote, @single_quote}
end
end
defp get_charlist_quotes(_heredoc = true, state) do
defp get_charlist_quotes(:heredoc, state) do
if state.normalize_charlists_as_sigils do
{@sigil_c_heredoc, @double_heredoc}
else
{@single_heredoc, @single_heredoc}
end
end
defp get_charlist_quotes({:regular, chunks}, state) do
cond do
!state.normalize_charlists_as_sigils -> {@single_quote, @single_quote}
Enum.any?(chunks, &has_double_quote?/1) -> {@sigil_c_single, @single_quote}
true -> {@sigil_c_double, @double_quote}
end
end
defp has_double_quote?(chunk) do
is_binary(chunk) and chunk =~ @double_quote
end
end
+30 -14
View File
@@ -3,10 +3,6 @@ defmodule Code.Fragment do
This module provides conveniences for analyzing fragments of
textual code and extract available information whenever possible.
Most of the functions in this module provide a best-effort
and may not be accurate under all circumstances. Read each
documentation for more information.
This module should be considered experimental.
"""
@@ -82,6 +78,9 @@ defmodule Code.Fragment do
* `{:local_call, charlist}` - the context is a local (import or local)
call, such as `hello_world(` and `hello_world `
* `{:anonymous_call, inside_caller}` - the context is an anonymous
call, such as `fun.(` and `@fun.(`.
* `{:module_attribute, charlist}` - the context is a module attribute,
such as `@hello_wor`
@@ -144,6 +143,7 @@ defmodule Code.Fragment do
| {:local_or_var, charlist}
| {:local_arity, charlist}
| {:local_call, charlist}
| {:anonymous_call, inside_caller}
| {:module_attribute, charlist}
| {:operator, charlist}
| {:operator_arity, charlist}
@@ -168,7 +168,8 @@ defmodule Code.Fragment do
| {:alias, inside_alias, charlist}
| {:local_or_var, charlist}
| {:module_attribute, charlist}
| {:dot, inside_dot, charlist}
| {:dot, inside_dot, charlist},
inside_caller: {:var, charlist} | {:module_attribute, charlist}
def cursor_context(fragment, opts \\ [])
def cursor_context(fragment, opts)
@@ -246,11 +247,22 @@ defmodule Code.Fragment do
end
defp call_to_cursor_context({reverse, spaces}) do
case identifier_to_cursor_context(reverse, spaces, true) do
{{:local_or_var, acc}, count} -> {{:local_call, acc}, count}
{{:dot, base, acc}, count} -> {{:dot_call, base, acc}, count}
{{:operator, acc}, count} -> {{:operator_call, acc}, count}
{_, _} -> {:none, 0}
with [?. | rest] <- reverse,
{rest, spaces} = strip_spaces(rest, spaces),
[h | _] when h not in @non_identifier <- rest do
case identifier_to_cursor_context(rest, spaces, true) do
{{:local_or_var, acc}, count} -> {{:anonymous_call, {:var, acc}}, count + 1}
{{:module_attribute, _} = attr, count} -> {{:anonymous_call, attr}, count + 1}
{_, _} -> {:none, 0}
end
else
_ ->
case identifier_to_cursor_context(reverse, spaces, true) do
{{:local_or_var, acc}, count} -> {{:local_call, acc}, count}
{{:dot, base, acc}, count} -> {{:dot_call, base, acc}, count}
{{:operator, acc}, count} -> {{:operator_call, acc}, count}
{_, _} -> {:none, 0}
end
end
end
@@ -477,7 +489,7 @@ defmodule Code.Fragment do
cond do
Code.Identifier.unary_op(op) == :error and Code.Identifier.binary_op(op) == :error ->
:none
{:none, 0}
match?([?. | rest] when rest == [] or hd(rest) != ?., rest) ->
dot(tl(rest), dot_count + 1, acc)
@@ -585,7 +597,8 @@ defmodule Code.Fragment do
| {:dot, inside_dot, charlist}
| {:module_attribute, charlist}
| {:unquoted_atom, charlist}
| {:var, charlist},
| {:var, charlist}
| :expr,
inside_alias:
{:local_or_var, charlist}
| {:module_attribute, charlist},
@@ -681,6 +694,9 @@ defmodule Code.Fragment do
{{:alias, acc}, offset} ->
build_surround({:alias, acc}, reversed, line, offset)
{{:alias, parent, acc}, offset} ->
build_surround({:alias, parent, acc}, reversed, line, offset)
{{:struct, acc}, offset} ->
build_surround({:struct, acc}, reversed, line, offset)
@@ -741,7 +757,7 @@ defmodule Code.Fragment do
end
end
defp take_alias([h | t], acc) when h not in @non_identifier,
defp take_alias([h | t], acc) when h in ?A..?Z or h in ?a..?z or h in ?0..?9 or h == ?_,
do: take_alias(t, [h | acc])
defp take_alias(rest, acc) do
@@ -1075,6 +1091,6 @@ defmodule Code.Fragment do
opts =
Keyword.take(opts, [:file, :line, :column, :columns, :token_metadata, :literal_encoder])
Code.string_to_quoted(fragment, [cursor_completion: true, warnings: false] ++ opts)
Code.string_to_quoted(fragment, [cursor_completion: true, emit_warnings: false] ++ opts)
end
end
+9 -7
View File
@@ -349,19 +349,25 @@ defmodule Code.Normalizer do
meta
end
last = List.last(args)
cond do
Keyword.has_key?(meta, :do) or match?([{{:__block__, _, [:do]}, _} | _], List.last(args)) ->
not allow_keyword?(form, arity) ->
args = normalize_args(args, %{state | parent_meta: meta})
{form, meta, args}
Keyword.has_key?(meta, :do) or match?([{{:__block__, _, [:do]}, _} | _], last) ->
# def foo do :ok end
# def foo, do: :ok
normalize_kw_blocks(form, meta, args, state)
match?([{:do, _} | _], List.last(args)) ->
match?([{:do, _} | _], last) and Keyword.keyword?(last) ->
# Non normalized kw blocks
line = state.parent_meta[:line]
meta = meta ++ [do: [line: line], end: [line: line]]
normalize_kw_blocks(form, meta, args, state)
allow_keyword?(form, arity) ->
true ->
args = normalize_args(args, %{state | parent_meta: meta})
{last_arg, leading_args} = List.pop_at(args, -1, [])
@@ -382,10 +388,6 @@ defmodule Code.Normalizer do
end
{form, meta, leading_args ++ last_args}
true ->
args = normalize_args(args, %{state | parent_meta: meta})
{form, meta, args}
end
end
+13 -4
View File
@@ -121,7 +121,7 @@ defmodule Code.Typespec do
located by the runtime system. The types will be in the Erlang
Abstract Format.
"""
@spec fetch_specs(module) :: {:ok, [tuple]} | :error
@spec fetch_specs(module | binary) :: {:ok, [tuple]} | :error
def fetch_specs(module) when is_atom(module) or is_binary(module) do
case typespecs_abstract_code(module) do
{:ok, abstract_code} ->
@@ -142,7 +142,7 @@ defmodule Code.Typespec do
which can be located by the runtime system. The types will be
in the Erlang Abstract Format.
"""
@spec fetch_callbacks(module) :: {:ok, [tuple]} | :error
@spec fetch_callbacks(module | binary) :: {:ok, [tuple]} | :error
def fetch_callbacks(module) when is_atom(module) or is_binary(module) do
case typespecs_abstract_code(module) do
{:ok, abstract_code} ->
@@ -175,7 +175,8 @@ defmodule Code.Typespec do
defp get_module_and_beam(module) when is_atom(module) do
with {^module, beam, _filename} <- :code.get_object_code(module),
{:ok, ^module} <- beam |> :beam_lib.info() |> Keyword.fetch(:module) do
info_pairs when is_list(info_pairs) <- :beam_lib.info(beam),
{:ok, ^module} <- Keyword.fetch(info_pairs, :module) do
{module, beam}
else
_ -> :error
@@ -419,5 +420,13 @@ defmodule Code.Typespec do
:error
end
defp meta(anno), do: [line: :erl_anno.line(anno)]
defp meta(anno) do
case :erl_anno.location(anno) do
{line, column} ->
[line: line, column: column]
line when is_integer(line) ->
[line: line]
end
end
end
+3 -3
View File
@@ -46,9 +46,9 @@ defmodule Config do
## Migrating from `use Mix.Config`
The `Config` module in Elixir was introduced in v1.9 as a replacement to
`Mix.Config`, which was specific to Mix and has been deprecated.
`use Mix.Config`, which was specific to Mix and has been deprecated.
You can leverage `Config` instead of `Mix.Config` in three steps. The first
You can leverage `Config` instead of `use Mix.Config` in three steps. The first
step is to replace `use Mix.Config` at the top of your config files by
`import Config`.
@@ -86,7 +86,7 @@ defmodule Config do
the `mix.exs` file and inside custom Mix tasks, which always within the
`Mix.Tasks` namespace.
## config/runtime.exs
## `config/runtime.exs`
For runtime configuration, you can use the `config/runtime.exs` file.
It is executed right before applications start in both Mix and releases
+1 -1
View File
@@ -342,7 +342,7 @@ defmodule Config.Provider do
* Make the runtime value match the compile time one
* Recompile your project. If the misconfigured application is a dependency, \
you may need to run "mix deps.compile #{app} --force"
you may need to run "mix deps.clean #{app} --build"
* Alternatively, you can disable this check. If you are using releases, you can \
set :validate_compile_env to false in your release configuration. If you are \
+47 -36
View File
@@ -256,10 +256,13 @@ defmodule Enum do
iex> Enum.map(map, fn {k, v} -> {k, v * 2} end)
[{"a", 2}, {"b", 4}]
However, many other enumerables exist in the language, such as `MapSet`s
Many other enumerables exist in the language, such as `MapSet`s
and the data type returned by `File.stream!/3` which allows a file to be
traversed as if it was an enumerable.
For a general overview of all functions in the `Enum` module, see
[the `Enum` cheatsheet](enum-cheat.cheatmd).
The functions in this module work in linear time. This means that, the
time it takes to perform an operation grows at the same rate as the length
of the enumerable. This is expected on operations such as `Enum.map/2`.
@@ -812,11 +815,11 @@ defmodule Enum do
@doc """
Enumerates the `enumerable`, returning a list where all consecutive
duplicated elements are collapsed to a single element.
duplicate elements are collapsed to a single element.
Elements are compared using `===/2`.
If you want to remove all duplicated elements, regardless of order,
If you want to remove all duplicate elements, regardless of order,
see `uniq/1`.
## Examples
@@ -845,7 +848,7 @@ defmodule Enum do
@doc """
Enumerates the `enumerable`, returning a list where all consecutive
duplicated elements are collapsed to a single element.
duplicate elements are collapsed to a single element.
The function `fun` maps every element to a term which is used to
determine if two elements are duplicates.
@@ -1216,7 +1219,8 @@ defmodule Enum do
"no bools!"
"""
@spec find_value(t, any, (element -> any)) :: any | nil
@spec find_value(t, default, (element -> found_value)) :: found_value | default
when found_value: term
def find_value(enumerable, default \\ nil, fun)
def find_value(enumerable, default, fun) when is_list(enumerable) do
@@ -1489,6 +1493,9 @@ defmodule Enum do
iex> Enum.into([a: 1, a: 2], %{})
%{a: 2}
iex> Enum.into([a: 2], %{a: 1, b: 3})
%{a: 2, b: 3}
"""
@spec into(Enumerable.t(), Collectable.t()) :: Collectable.t()
def into(enumerable, collectable)
@@ -2386,7 +2393,14 @@ defmodule Enum do
def random(enumerable) when is_list(enumerable) do
case length(enumerable) do
0 -> raise Enum.EmptyError
length -> enumerable |> drop_list(random_integer(0, length - 1)) |> hd()
length -> enumerable |> drop_list(random_count(length)) |> hd()
end
end
def random(first.._//step = range) do
case Range.size(range) do
0 -> raise Enum.EmptyError
size -> first + random_count(size) * step
end
end
@@ -2397,14 +2411,14 @@ defmodule Enum do
[]
{:ok, count, fun} when is_function(fun, 1) ->
slice_list(fun.(enumerable), random_integer(0, count - 1), 1, 1)
slice_list(fun.(enumerable), random_count(count), 1, 1)
# TODO: Deprecate me in Elixir v1.18.
{:ok, count, fun} when is_function(fun, 2) ->
fun.(random_integer(0, count - 1), 1)
fun.(random_count(count), 1)
{:ok, count, fun} when is_function(fun, 3) ->
fun.(random_integer(0, count - 1), 1, 1)
fun.(random_count(count), 1, 1)
{:error, _} ->
take_random(enumerable, 1)
@@ -2416,6 +2430,10 @@ defmodule Enum do
end
end
defp random_count(count) do
:rand.uniform(count) - 1
end
@doc """
Invokes `fun` for each element in the `enumerable` with the
accumulator.
@@ -2960,20 +2978,23 @@ defmodule Enum do
[]
# first is greater than last
iex> Enum.slice([1, 2, 3, 4, 5], 6..5)
iex> Enum.slice([1, 2, 3, 4, 5], 6..5//1)
[]
"""
@doc since: "1.6.0"
@spec slice(t, Range.t()) :: list
def slice(enumerable, first..last//step = index_range) do
# TODO: Deprecate negative steps on Elixir v1.16
# TODO: Support negative steps as a reverse on Elixir v2.0.
cond do
step > 0 ->
slice_range(enumerable, first, last, step)
step == -1 and first > last ->
IO.warn(
"negative steps are not supported in Enum.slice/2, pass #{first}..#{last}//1 instead"
)
slice_range(enumerable, first, last, 1)
true ->
@@ -3009,13 +3030,16 @@ defmodule Enum do
if first < count and amount > 0 do
amount = Kernel.min(amount, count - first)
amount = if step == 1, do: amount, else: div(amount - 1, step) + 1
amount = amount_with_step(amount, step)
fun.(first, amount, step)
else
[]
end
end
defp amount_with_step(amount, 1), do: amount
defp amount_with_step(amount, step), do: div(amount - 1, step) + 1
@doc """
Returns a subset list of the given `enumerable`, from `start_index` (zero-based)
with `amount` number of elements if available.
@@ -3599,7 +3623,7 @@ defmodule Enum do
sample = Tuple.duplicate(nil, count)
reducer = fn elem, {idx, sample} ->
jdx = random_integer(0, idx)
jdx = random_index(idx)
cond do
idx < count ->
@@ -3620,7 +3644,7 @@ defmodule Enum do
def take_random(enumerable, count) when is_integer(count) and count >= 0 do
reducer = fn elem, {idx, sample} ->
jdx = random_integer(0, idx)
jdx = random_index(idx)
cond do
idx < count ->
@@ -3656,6 +3680,9 @@ defmodule Enum do
defp take_random_list_one([], current, _), do: [current]
defp random_index(0), do: 0
defp random_index(idx), do: :rand.uniform(idx + 1) - 1
@doc """
Takes the elements from the beginning of the `enumerable` while `fun` returns
a truthy value.
@@ -3701,7 +3728,7 @@ defmodule Enum do
def to_list(enumerable), do: reverse(enumerable) |> :lists.reverse()
@doc """
Enumerates the `enumerable`, removing all duplicated elements.
Enumerates the `enumerable`, removing all duplicate elements.
## Examples
@@ -3766,9 +3793,6 @@ defmodule Enum do
iex> Enum.unzip([{:a, 1}, {:b, 2}, {:c, 3}])
{[:a, :b, :c], [1, 2, 3]}
iex> Enum.unzip(%{a: 1, b: 2})
{[:a, :b], [1, 2]}
"""
@spec unzip(t) :: {[element], [element]}
@@ -4024,7 +4048,7 @@ defmodule Enum do
...> end)
[{1, 2, 3}, {1, 2, 3}]
iex> enums = [[1, 2], %{a: 3, b: 4}, [5, 6]]
iex> enums = [[1, 2], [a: 3, b: 4], [5, 6]]
...> Enum.zip_reduce(enums, [], fn elements, acc ->
...> [List.to_tuple(elements) | acc]
...> end)
@@ -4139,18 +4163,6 @@ defmodule Enum do
end)
end
defp random_integer(limit, limit) when is_integer(limit) do
limit
end
defp random_integer(lower_limit, upper_limit) when upper_limit < lower_limit do
random_integer(upper_limit, lower_limit)
end
defp random_integer(lower_limit, upper_limit) do
lower_limit + :rand.uniform(upper_limit - lower_limit + 1) - 1
end
## Implementations
## all?/1
@@ -4440,7 +4452,7 @@ defmodule Enum do
if start >= 0 do
amount = Kernel.min(amount, count - start)
amount = if step == 1, do: amount, else: div(amount - 1, step) + 1
amount = amount_with_step(amount, step)
fun.(start, amount, step)
else
[]
@@ -4448,7 +4460,7 @@ defmodule Enum do
end
defp slice_forward(list, start, amount, step) when is_list(list) do
amount = if step == 1, do: amount, else: div(amount - 1, step) + 1
amount = amount_with_step(amount, step)
slice_list(list, start, amount, step)
end
@@ -4458,7 +4470,7 @@ defmodule Enum do
[]
{:ok, count, fun} when is_function(fun, 1) ->
amount = Kernel.min(amount, count - start)
amount = Kernel.min(amount, count - start) |> amount_with_step(step)
enumerable |> fun.() |> slice_exact(start, amount, step, count)
# TODO: Deprecate me in Elixir v1.18.
@@ -4473,8 +4485,7 @@ defmodule Enum do
end
{:ok, count, fun} when is_function(fun, 3) ->
amount = Kernel.min(amount, count - start)
amount = if step == 1, do: amount, else: div(amount - 1, step) + 1
amount = Kernel.min(amount, count - start) |> amount_with_step(step)
fun.(start, amount, step)
{:error, module} ->
File diff suppressed because it is too large Load Diff
+61 -8
View File
@@ -74,6 +74,29 @@ defmodule File do
Check `:file.open/2` for more information about such options and
other performance considerations.
## Seeking within a file
You may also use any of the functions from the [`:file`](`:file`)
module to interact with files returned by Elixir. For example,
to read from a specific position in a file, use `:file.pread/3`:
File.write!("example.txt", "Eats, Shoots & Leaves")
file = File.open!("example.txt")
:file.pread(file, 15, 6)
#=> {:ok, "Leaves"}
Alternatively, if you need to keep track of the current position,
use `:file.position/2` and `:file.read/2`:
:file.position(file, 6)
#=> {:ok, 6}
:file.read(file, 6)
#=> {:ok, "Shoots"}
:file.position(file, {:cur, -12})
#=> {:ok, 0}
:file.read(file, 4)
#=> {:ok, "Eats"}
"""
@type posix :: :file.posix()
@@ -110,6 +133,7 @@ defmodule File do
@type stream_mode ::
encoding_mode()
| read_offset_mode()
| :append
| :compressed
| :delayed_write
@@ -117,6 +141,8 @@ defmodule File do
| {:read_ahead, pos_integer | false}
| {:delayed_write, non_neg_integer, non_neg_integer}
@type read_offset_mode :: {:read_offset, non_neg_integer()}
@type erlang_time ::
{{year :: non_neg_integer(), month :: 1..12, day :: 1..31},
{hour :: 0..23, minute :: 0..59, second :: 0..59}}
@@ -1630,6 +1656,11 @@ defmodule File do
@doc """
Returns the list of files in the given directory.
Hidden files are not ignored and the results are *not* sorted.
Since directories are considered files by the file system,
they are also included in the returned value.
Returns `{:ok, files}` in case of success,
`{:error, reason}` otherwise.
"""
@@ -1671,6 +1702,18 @@ defmodule File do
:file.close(io_device)
end
@doc """
Shortcut for `File.stream!/3`.
"""
@spec stream!(Path.t(), :line | pos_integer | [stream_mode]) :: File.Stream.t()
def stream!(path, line_or_bytes_modes \\ [])
def stream!(path, modes) when is_list(modes),
do: stream!(path, :line, modes)
def stream!(path, line_or_bytes) when is_integer(line_or_bytes) or line_or_bytes == :line,
do: stream!(path, line_or_bytes, [])
@doc ~S"""
Returns a `File.Stream` for the given `path` with the given `modes`.
@@ -1708,27 +1751,37 @@ defmodule File do
One may also consider passing the `:delayed_write` option if the stream
is meant to be written to under a tight loop.
## Byte order marks
## Byte order marks and read offset
If you pass `:trim_bom` in the modes parameter, the stream will
trim UTF-8, UTF-16 and UTF-32 byte order marks when reading from file.
Note that this function does not try to discover the file encoding
based on BOM.
based on BOM. From Elixir v1.16.0, you may also pass a `:read_offset`
that is skipped whenever enumerating the stream (if both `:read_offset`
and `:trim_bom` are given, the offset is skipped after the BOM).
## Examples
# Read a utf8 text file which may include BOM
File.stream!("./test/test.txt", [:trim_bom, encoding: :utf8])
# Read in 2048 byte chunks rather than lines
File.stream!("./test/test.data", [], 2048)
#=> %File.Stream{line_or_bytes: 2048, modes: [:raw, :read_ahead, :binary],
#=> path: "./test/test.data", raw: true}
File.stream!("./test/test.data", 2048)
See `Stream.run/1` for an example of streaming into a file.
"""
@spec stream!(Path.t(), [stream_mode], :line | pos_integer) :: File.Stream.t()
def stream!(path, modes \\ [], line_or_bytes \\ :line) do
@spec stream!(Path.t(), :line | pos_integer, [stream_mode]) :: File.Stream.t()
def stream!(path, line_or_bytes, modes)
def stream!(path, modes, line_or_bytes) when is_list(modes) do
# TODO: Deprecate this on Elixir v1.20
stream!(path, line_or_bytes, modes)
end
def stream!(path, line_or_bytes, modes) do
modes = normalize_modes(modes, true)
File.Stream.__build__(IO.chardata_to_string(path), modes, line_or_bytes)
File.Stream.__build__(IO.chardata_to_string(path), line_or_bytes, modes)
end
@doc """
+43 -5
View File
@@ -17,7 +17,13 @@ defmodule File.Stream do
@type t :: %__MODULE__{}
@doc false
def __build__(path, modes, line_or_bytes) do
def __build__(path, line_or_bytes, modes) do
with {:read_offset, offset} <- :lists.keyfind(:read_offset, 1, modes),
false <- is_integer(offset) and offset >= 0 do
raise ArgumentError,
"expected :read_offset to be a non-negative integer, got: #{inspect(offset)}"
end
raw = :lists.keyfind(:encoding, 1, modes) == false
modes =
@@ -88,7 +94,7 @@ defmodule File.Stream do
start_fun = fn ->
case File.Stream.__open__(stream, read_modes(modes)) do
{:ok, device} ->
if :trim_bom in modes, do: trim_bom(device, raw) |> elem(0), else: device
skip_bom_and_offset(device, raw, modes)
{:error, reason} ->
raise File.Error, reason: reason, action: "stream", path: stream.path
@@ -104,9 +110,14 @@ defmodule File.Stream do
Stream.resource(start_fun, next_fun, &:file.close/1).(acc, fun)
end
def count(%{modes: modes, line_or_bytes: :line, path: path} = stream) do
def count(%{modes: modes, line_or_bytes: :line, path: path, raw: raw} = stream) do
pattern = :binary.compile_pattern("\n")
counter = &count_lines(&1, path, pattern, read_function(stream), 0)
counter = fn device ->
device = skip_bom_and_offset(device, raw, modes)
count_lines(device, path, pattern, read_function(stream), 0)
end
{:ok, open!(stream, modes, counter)}
end
@@ -116,8 +127,11 @@ defmodule File.Stream do
{:error, __MODULE__}
{:ok, %{size: size}} ->
bom_offset = count_raw_bom(stream, modes)
offset = get_read_offset(modes)
size = max(size - bom_offset - offset, 0)
remainder = if rem(size, bytes) == 0, do: 0, else: 1
{:ok, div(size, bytes) + remainder - count_raw_bom(stream, modes)}
{:ok, div(size, bytes) + remainder}
{:error, reason} ->
raise File.Error, reason: reason, action: "stream", path: path
@@ -158,6 +172,23 @@ defmodule File.Stream do
end
end
defp skip_bom_and_offset(device, raw, modes) do
device =
if :trim_bom in modes do
device |> trim_bom(raw) |> elem(0)
else
device
end
offset = get_read_offset(modes)
if offset > 0 do
{:ok, _} = :file.position(device, {:cur, offset})
end
device
end
defp trim_bom(device, true) do
bom_length = device |> IO.binread(4) |> bom_length()
{:ok, new_pos} = :file.position(device, bom_length)
@@ -183,6 +214,13 @@ defmodule File.Stream do
defp bom_length(<<254, 255, 0, 0, _rest::binary>>), do: 4
defp bom_length(_binary), do: 0
def get_read_offset(modes) do
case :lists.keyfind(:read_offset, 1, modes) do
{:read_offset, offset} -> offset
false -> 0
end
end
defp read_modes(modes) do
for mode <- modes, mode not in [:write, :append, :trim_bom], do: mode
end
+29 -5
View File
@@ -4,6 +4,9 @@ defmodule Float do
@moduledoc """
Functions for working with floating-point numbers.
For mathematical operations on top of floating-points,
see Erlang's [`:math`](`:math`) module.
## Kernel functions
There are functions related to floating-point numbers on the `Kernel` module
@@ -58,6 +61,7 @@ defmodule Float do
1.7976931348623157e308
"""
@spec max_finite() :: float
def max_finite, do: @max_finite
@doc """
@@ -69,6 +73,7 @@ defmodule Float do
-1.7976931348623157e308
"""
@spec min_finite() :: float
def min_finite, do: @min_finite
@doc """
@@ -193,7 +198,7 @@ defmodule Float do
defp add_dot(acc, false), do: acc <> ".0"
@doc """
Rounds a float to the largest number less than or equal to `num`.
Rounds a float to the largest float less than or equal to `number`.
`floor/2` also accepts a precision to round a floating-point value down
to an arbitrary number of fractional digits (between 0 and 15).
@@ -241,7 +246,7 @@ defmodule Float do
end
@doc """
Rounds a float to the smallest integer greater than or equal to `num`.
Rounds a float to the smallest float greater than or equal to `number`.
`ceil/2` also accepts a precision to round a floating-point value down
to an arbitrary number of fractional digits (between 0 and 15).
@@ -270,6 +275,8 @@ defmodule Float do
-56.0
iex> Float.ceil(34.251, 2)
34.26
iex> Float.ceil(-0.01)
-0.0
"""
@spec ceil(float, precision_range) :: float
@@ -327,6 +334,8 @@ defmodule Float do
-6.0
iex> Float.round(12.341444444444441, 15)
12.341444444444441
iex> Float.round(-0.01)
-0.0
"""
@spec round(float, precision_range) :: float
@@ -335,8 +344,13 @@ defmodule Float do
# and could be implemented in the future.
def round(float, precision \\ 0)
def round(float, 0) when float == 0.0, do: float
def round(float, 0) when is_float(float) do
float |> :erlang.round() |> :erlang.float()
case float |> :erlang.round() |> :erlang.float() do
zero when zero == 0.0 and float < 0.0 -> -0.0
rounded -> rounded
end
end
def round(float, precision) when is_float(float) and precision in @precision_range do
@@ -347,7 +361,7 @@ defmodule Float do
raise ArgumentError, invalid_precision_message(precision)
end
defp round(0.0 = num, _precision, _rounding), do: num
defp round(num, _precision, _rounding) when is_float(num) and num == 0.0, do: num
defp round(float, precision, rounding) do
<<sign::1, exp::11, significant::52-bitstring>> = <<float::float>>
@@ -360,6 +374,8 @@ defmodule Float do
case rounding do
:ceil when sign === 0 -> 1 / power_of_10(precision)
:floor when sign === 1 -> -1 / power_of_10(precision)
:ceil when sign === 1 -> minus_zero()
:half_up when sign === 1 -> minus_zero()
_ -> 0.0
end
@@ -389,6 +405,9 @@ defmodule Float do
boundary = den <<< 52
cond do
num == 0 and sign == 1 ->
minus_zero()
num == 0 ->
0.0
@@ -403,6 +422,11 @@ defmodule Float do
end
end
# TODO remove once we require Erlang/OTP 27+
# This function tricks the compiler to avoid this bug in previous versions:
# https://github.com/elixir-lang/elixir/blob/main/lib/elixir/lib/float.ex#L408-L412
defp minus_zero, do: -0.0
defp decompose(significant, initial) do
decompose(significant, 1, 0, initial)
end
@@ -495,7 +519,7 @@ defmodule Float do
"""
@doc since: "1.4.0"
@spec ratio(float) :: {integer, pos_integer}
def ratio(0.0), do: {0, 1}
def ratio(float) when is_float(float) and float == 0.0, do: {0, 1}
def ratio(float) when is_float(float) do
<<sign::1, exp::11, mantissa::52>> = <<float::float>>
+2 -2
View File
@@ -19,7 +19,7 @@ defmodule GenEvent do
One alternative to GenEvent is a very minimal solution consisting of using a
supervisor and multiple GenServers started under it. The supervisor acts as
the "event manager" and the children GenServers act as the "event handlers".
This approach has some shortcomings (it provides no backpressure for example)
This approach has some shortcomings (it provides no back-pressure for example)
but can still replace GenEvent for low-profile usages of it. [This blog post
by José
Valim](http://blog.plataformatec.com.br/2016/11/replacing-genevent-by-a-supervisor-genserver/)
@@ -31,7 +31,7 @@ defmodule GenEvent do
[GenStage](https://github.com/elixir-lang/gen_stage) provides a great
alternative. GenStage is an external Elixir library maintained by the Elixir
team; it provides a tool to implement systems that exchange events in a
demand-driven way with built-in support for backpressure. See the [GenStage
demand-driven way with built-in support for back-pressure. See the [GenStage
documentation](https://hexdocs.pm/gen_stage) for more information.
### `:gen_event`
+42 -2
View File
@@ -8,6 +8,13 @@ defmodule GenServer do
will have a standard set of interface functions and include functionality for
tracing and error reporting. It will also fit into a supervision tree.
```mermaid
graph BT
C(Client #3) ~~~ B(Client #2) ~~~ A(Client #1)
A & B & C -->|request| GenServer
GenServer -.->|reply| A & B & C
```
## Example
The GenServer behaviour abstracts the common client-server interaction.
@@ -136,11 +143,44 @@ defmodule GenServer do
end
end
In practice, it is common to have both server and client functions in
the same module. If the server and/or client implementations are growing
complex, you may want to have them in different modules.
The following diagram summarizes the interactions between client and server.
Both Client and Server are processes and communication happens via messages
(continuous line). The Server <-> Module interaction happens when the
GenServer process calls your code (dotted lines):
```mermaid
sequenceDiagram
participant C as Client (Process)
participant S as Server (Process)
participant M as Module (Code)
note right of C: Typically started by a supervisor
C->>+S: GenServer.start_link(module, arg, options)
S-->>+M: init(arg)
M-->>-S: {:ok, state} | :ignore | {:error, reason}
S->>-C: {:ok, pid} | :ignore | {:error, reason}
note right of C: call is synchronous
C->>+S: GenServer.call(pid, message)
S-->>+M: handle_call(message, from, state)
M-->>-S: {:reply, reply, state} | {:stop, reason, reply, state}
S->>-C: reply
note right of C: cast is asynchronous
C-)S: GenServer.cast(pid, message)
S-->>+M: handle_cast(message, state)
M-->>-S: {:noreply, state} | {:stop, reason, state}
note right of C: send is asynchronous
C-)S: Kernel.send(pid, message)
S-->>+M: handle_info(message, state)
M-->>-S: {:noreply, state} | {:stop, reason, state}
```
## How to supervise
A `GenServer` is most commonly started under a supervision tree.
@@ -431,7 +471,7 @@ defmodule GenServer do
guide provides a tutorial-like introduction. The documentation and links
in Erlang can also provide extra insight.
* [GenServer - Elixir's Getting Started Guide](https://elixir-lang.org/getting-started/mix-otp/genserver.html)
* [GenServer - Elixir's Getting Started Guide](genservers.md)
* [`:gen_server` module documentation](`:gen_server`)
* [gen_server Behaviour - OTP Design Principles](https://www.erlang.org/doc/design_principles/gen_server_concepts.html)
* [Clients and Servers - Learn You Some Erlang for Great Good!](http://learnyousomeerlang.com/clients-and-servers)
+18 -7
View File
@@ -117,13 +117,22 @@ defprotocol Inspect do
In case there is an error while your structure is being inspected,
Elixir will raise an `ArgumentError` error and will automatically fall back
to a raw representation for printing the structure.
to a raw representation for printing the structure. Furthermore, you
must be careful when debugging your own Inspect implementation, as calls
to `IO.inspect/2` or `dbg/1` may trigger an infinite loop (as in order to
inspect/debug the data structure, you must call `inspect` itself).
You can, however, access the underlying error by invoking the `Inspect`
implementation directly. For example, to test `Inspect.MapSet` above,
you can invoke it as:
Here are some tips:
Inspect.MapSet.inspect(MapSet.new(), %Inspect.Opts{})
* For debugging, use `IO.inspect/2` with the `structs: false` option,
which disables custom printing and avoids calling the Inspect
implementation recursively
* To access the underlying error on your custom `Inspect` implementation,
you may invoke the protocol directly. For example, we could invoke the
`Inspect.MapSet` implementation above as:
Inspect.MapSet.inspect(MapSet.new(), %Inspect.Opts{})
"""
@@ -408,6 +417,7 @@ defimpl Inspect, for: Regex do
defp normalize(<<?\\, ?\\, rest::binary>>, acc), do: normalize(rest, <<acc::binary, ?\\, ?\\>>)
defp normalize(<<?\\, ?/, rest::binary>>, acc), do: normalize(rest, <<acc::binary, ?/>>)
defp normalize(<<?\\, ?#, ?{, rest::binary>>, acc), do: normalize(rest, <<acc::binary, ?#, ?{>>)
defp normalize(<<char, rest::binary>>, acc), do: normalize(rest, <<acc::binary, char>>)
defp normalize(<<>>, acc), do: acc
@@ -525,7 +535,8 @@ end
defimpl Inspect, for: Any do
defmacro __deriving__(module, struct, options) do
fields = Map.keys(struct) -- [:__exception__, :__struct__]
fields = Enum.sort(Map.keys(struct) -- [:__exception__, :__struct__])
only = Keyword.get(options, :only, fields)
except = Keyword.get(options, :except, [])
optional = Keyword.get(options, :optional, [])
@@ -535,7 +546,7 @@ defimpl Inspect, for: Any do
:ok = validate_option(:optional, optional, fields, module)
inspect_module =
if fields == only and except == [] do
if fields == Enum.sort(only) and except == [] do
Inspect.Map
else
Inspect.Any
+2 -3
View File
@@ -143,9 +143,8 @@ defmodule Inspect.Opts do
function as this must be controlled by applications. Libraries
should instead define their own structs with custom inspect
implementations. If a library must change the default inspect
function, then it is best to define to ask users of your library
to explicitly call `default_inspect_fun/1` with your function of
choice.
function, then it is best to ask users of your library to explicitly
call `default_inspect_fun/1` with your function of choice.
The default is `Inspect.inspect/2`.
+43 -27
View File
@@ -268,9 +268,11 @@ defmodule IO do
to stdio with this function will likely result in the wrong data
being sent down the wire.
"""
@spec binwrite(device, iodata) :: :ok | {:error, term}
@spec binwrite(device, iodata) :: :ok
def binwrite(device \\ :stdio, iodata) when is_iodata(iodata) do
:file.write(map_dev(device), iodata)
with {:error, reason} <- :file.write(map_dev(device), iodata) do
:erlang.error(reason)
end
end
@doc """
@@ -306,17 +308,20 @@ defmodule IO do
entry from the compilation environment will be used
* a keyword list with at least the `:file` option representing
a single stacktrace entry (since v1.14.0). The `:line`, `:module`,
`:function` options are also supported
a single stacktrace entry (since v1.14.0). The `:line`, `:column`,
`:module`, and `:function` options are also supported
This function also notifies the compiler a warning was printed
(in case --warnings-as-errors was enabled). It returns `:ok`
if it succeeds.
This function notifies the compiler a warning was printed
and emits a compiler diagnostic (`t:Code.diagnostic/1`).
The diagnostic will include precise file and location information
if a `Macro.Env` is given or those values have been passed as
keyword list, but not for stacktraces, as they are often imprecise.
It returns `:ok` if it succeeds.
## Examples
stacktrace = [{MyApp, :main, 1, [file: 'my_app.ex', line: 4]}]
IO.warn("variable bar is unused", stacktrace)
IO.warn("variable bar is unused", module: MyApp, function: {:main, 1}, line: 4, file: "my_app.ex")
#=> warning: variable bar is unused
#=> my_app.ex:4: MyApp.main/1
@@ -325,37 +330,46 @@ defmodule IO do
:ok
def warn(message, stacktrace_info)
def warn(message, %Macro.Env{} = env) do
warn(message, Macro.Env.stacktrace(env))
end
def warn(message, %Macro.Env{line: line, file: file} = env) do
message = to_chardata(message)
def warn(message, []) do
:elixir_errors.emit_diagnostic(:warning, 0, nil, to_chardata(message), [])
:elixir_errors.emit_diagnostic(:warning, line, file, message, Macro.Env.stacktrace(env),
read_snippet: true
)
end
def warn(message, [{_, _} | _] = keyword) do
if file = keyword[:file] do
warn(
message,
%{
line = keyword[:line]
column = keyword[:column]
position = if line && column, do: {line, column}, else: line
message = to_chardata(message)
stacktrace =
Macro.Env.stacktrace(%{
__ENV__
| module: keyword[:module],
function: keyword[:function],
line: keyword[:line],
line: line,
file: file
}
})
:elixir_errors.emit_diagnostic(:warning, position, file, message, stacktrace,
read_snippet: true
)
else
warn(message, [])
end
end
def warn(message, [{_, _, _, opts} | _] = stacktrace) do
def warn(message, []) do
message = to_chardata(message)
line = opts[:line]
file = opts[:file]
file = file && List.to_string(file)
:elixir_errors.emit_diagnostic(:warning, line || 0, file, message, stacktrace)
:elixir_errors.emit_diagnostic(:warning, 0, nil, message, [], read_snippet: false)
end
def warn(message, [{_, _, _, _} | _] = stacktrace) do
message = to_chardata(message)
:elixir_errors.emit_diagnostic(:warning, 0, nil, message, stacktrace, read_snippet: false)
end
@doc false
@@ -563,6 +577,7 @@ defmodule IO do
"""
@doc since: "1.12.0"
@spec stream() :: Enumerable.t(String.t())
def stream, do: stream(:stdio, :line)
@doc """
@@ -593,11 +608,11 @@ defmodule IO do
Another example where you might want to collect a user input
every new line and break on an empty line, followed by removing
redundant new line characters (`"\n"`):
redundant new line characters (`"\\n"`):
IO.stream(:stdio, :line)
|> Enum.take_while(&(&1 != "\n"))
|> Enum.map(&String.replace(&1, "\n", ""))
|> Enum.take_while(&(&1 != "\\n"))
|> Enum.map(&String.replace(&1, "\\n", ""))
"""
@spec stream(device, :line | pos_integer) :: Enumerable.t()
@@ -616,6 +631,7 @@ defmodule IO do
"""
@doc since: "1.12.0"
@spec binstream() :: Enumerable.t(binary)
def binstream, do: binstream(:stdio, :line)
@doc """
+3
View File
@@ -274,6 +274,8 @@ defmodule IO.ANSI do
emitting actual ANSI codes. When `false`, no ANSI codes will be emitted.
By default checks if ANSI is enabled using the `enabled?/0` function.
An `ArgumentError` will be raised if an invalid ANSI code is provided.
## Examples
iex> IO.ANSI.format(["Hello, ", :red, :bright, "world!"], true)
@@ -315,6 +317,7 @@ defmodule IO.ANSI do
end
defp do_format(term, rem, acc, false, append_reset) when is_atom(term) do
format_sequence(term)
do_format([], rem, acc, false, append_reset)
end
+14 -195
View File
@@ -50,6 +50,10 @@ defmodule IO.ANSI.Docs do
"""
@spec print_headings([String.t()], keyword) :: :ok
def print_headings(headings, options \\ []) do
# It's possible for some of the headings to contain newline characters (`\n`), so in order to prevent it from
# breaking the output from `print_headings/2`, as `print_headings/2` tries to pad the whole heading, we first split
# any heading containgin newline characters into multiple headings, that way each one is padded on its own.
headings = Enum.flat_map(headings, fn heading -> String.split(heading, "\n") end)
options = Keyword.merge(default_options(), options)
newline_after_block(options)
width = options[:width]
@@ -114,196 +118,10 @@ defmodule IO.ANSI.Docs do
print_markdown(doc, options)
end
def print(doc, "application/erlang+html", options) when is_list(options) do
print_erlang_html(doc, options)
end
def print(_doc, format, options) when is_binary(format) and is_list(options) do
IO.puts("\nUnknown documentation format #{inspect(format)}\n")
end
## Erlang+html
def print_erlang_html(doc, options) do
options = Keyword.merge(default_options(), options)
IO.write(traverse_erlang_html(doc, "", options))
end
defp traverse_erlang_html(text, _indent, _options) when is_binary(text) do
text
end
defp traverse_erlang_html(nodes, indent, options) when is_list(nodes) do
for node <- nodes do
traverse_erlang_html(node, indent, options)
end
end
defp traverse_erlang_html({:div, [class: class] ++ _, entries}, indent, options) do
prefix = indent <> quote_prefix(options)
content =
entries
|> traverse_erlang_html(indent, options)
|> IO.iodata_to_binary()
|> String.trim_trailing()
[
prefix,
class |> to_string() |> String.upcase(),
"\n#{prefix}\n#{prefix}" | String.replace(content, "\n", "\n#{prefix}")
]
|> newline_cons()
end
defp traverse_erlang_html({:p, _, entries}, indent, options) do
[indent | handle_erlang_html_text(entries, indent, options)]
end
defp traverse_erlang_html({:h1, _, entries}, indent, options) do
entries |> traverse_erlang_html(indent, options) |> heading(1, options) |> newline_cons()
end
defp traverse_erlang_html({:h2, _, entries}, indent, options) do
entries |> traverse_erlang_html(indent, options) |> heading(2, options) |> newline_cons()
end
defp traverse_erlang_html({:h3, _, entries}, indent, options) do
entries |> traverse_erlang_html(indent, options) |> heading(3, options) |> newline_cons()
end
defp traverse_erlang_html({:h4, _, entries}, indent, options) do
entries |> traverse_erlang_html(indent, options) |> heading(4, options) |> newline_cons()
end
defp traverse_erlang_html({:h5, _, entries}, indent, options) do
entries |> traverse_erlang_html(indent, options) |> heading(5, options) |> newline_cons()
end
defp traverse_erlang_html({:h6, _, entries}, indent, options) do
entries |> traverse_erlang_html(indent, options) |> heading(6, options) |> newline_cons()
end
defp traverse_erlang_html({:br, _, []}, _indent, _options) do
[]
end
defp traverse_erlang_html({:i, _, entries}, indent, options) do
inline_text("_", traverse_erlang_html(entries, indent, options), options)
end
defp traverse_erlang_html({:em, _, entries}, indent, options) do
inline_text("*", traverse_erlang_html(entries, indent, options), options)
end
defp traverse_erlang_html({tag, _, entries}, indent, options) when tag in [:strong, :b] do
inline_text("**", traverse_erlang_html(entries, indent, options), options)
end
defp traverse_erlang_html({:code, _, entries}, indent, options) do
inline_text("`", traverse_erlang_html(entries, indent, options), options)
end
defp traverse_erlang_html({:pre, _, [{:code, _, entries}]}, indent, options) do
string =
entries
|> traverse_erlang_html(indent, options)
|> IO.iodata_to_binary()
["#{indent} ", String.replace(string, "\n", "\n#{indent} ")] |> newline_cons()
end
defp traverse_erlang_html({:a, attributes, entries}, indent, options) do
if href = attributes[:href] do
[traverse_erlang_html(entries, indent, options), ?\s, ?(, href, ?)]
else
traverse_erlang_html(entries, indent, options)
end
end
defp traverse_erlang_html({:dl, _, entries}, indent, options) do
traverse_erlang_html(entries, indent, options)
end
defp traverse_erlang_html({:dt, _, entries}, indent, options) do
[
"#{indent} ",
bullet_text(options) | handle_erlang_html_text(entries, indent <> " ", options)
]
end
defp traverse_erlang_html({:dd, _, entries}, indent, options) do
["#{indent} " | handle_erlang_html_text(entries, indent <> " ", options)]
end
defp traverse_erlang_html({:ul, attributes, entries}, indent, options) do
if attributes[:class] == "types" do
types =
for {:li, _, lines} <- entries,
line <- lines,
do: ["#{indent} ", traverse_erlang_html(line, indent <> " ", options), ?\n]
if types != [] do
["#{indent}Typespecs:\n\n", types, ?\n]
else
[]
end
else
for {:li, _, lines} <- entries do
[
"#{indent} ",
bullet_text(options) | handle_erlang_html_text(lines, indent <> " ", options)
]
end
end
end
defp traverse_erlang_html({:ol, _, entries}, indent, options) do
for {{:li, _, lines}, i} <- Enum.with_index(entries, 1) do
[
"#{indent} ",
Integer.to_string(i),
". " | handle_erlang_html_text(lines, indent <> " ", options)
]
end
end
defp traverse_erlang_html({tag, _, entries}, indent, options) do
[
indent <> "<#{tag}>\n",
traverse_erlang_html(entries, indent <> " ", options)
|> IO.iodata_to_binary()
|> String.trim_trailing(),
"\n" <> indent <> "</#{tag}>"
]
|> newline_cons()
end
defp newline_cons(text) do
[text | "\n\n"]
end
defp handle_erlang_html_text(entries, indent, options) do
if Enum.all?(entries, &inline_html?/1) do
entries
|> traverse_erlang_html(indent, options)
|> IO.iodata_to_binary()
|> String.split(@spaces)
|> wrap_text(options[:width], indent, true, "", [])
|> tl()
|> newline_cons()
else
entries
|> traverse_erlang_html(indent, options)
|> IO.iodata_to_binary()
|> String.trim_leading()
end
end
defp inline_html?(binary) when is_binary(binary), do: true
defp inline_html?({tag, _, _}) when tag in [:a, :code, :em, :i, :strong, :b, :br], do: true
defp inline_html?(_), do: false
## Markdown
def print_markdown(doc, options) do
@@ -358,12 +176,17 @@ defmodule IO.ANSI.Docs do
process_code(rest, [line], indent, options)
end
defp process(["```" <> _line | rest], text, indent, options) do
process_fenced_code_block(rest, text, indent, options, _delimiter = "```")
defp process(["```mermaid" <> _line | rest], text, indent, options) do
write_text(text, indent, options)
rest
|> Enum.drop_while(&(&1 != "```"))
|> Enum.drop(1)
|> process([], indent, options)
end
defp process(["~~~" <> _line | rest], text, indent, options) do
process_fenced_code_block(rest, text, indent, options, _delimiter = "~~~")
defp process(["```" <> _line | rest], text, indent, options) do
process_fenced_code_block(rest, text, indent, options, _delimiter = "```")
end
defp process(["<!--" <> line | rest], text, indent, options) do
@@ -570,7 +393,7 @@ defmodule IO.ANSI.Docs do
end
defp process_fenced_code([line | rest], code, indent, options, delimiter) do
if line === delimiter do
if line == delimiter do
process_code(rest, code, indent, options)
else
process_fenced_code(rest, [line | code], indent, options, delimiter)
@@ -949,10 +772,6 @@ defmodule IO.ANSI.Docs do
defp quote_prefix(options), do: "#{color(:doc_quote, options)}> #{maybe_reset(options)}"
defp heading(text, n, options) do
[color(:doc_headings, options), String.duplicate("#", n), " ", text, maybe_reset(options)]
end
defp inline_text(mark, text, options) do
if options[:enabled] do
[[color_for(mark, options) | text] | IO.ANSI.reset()]
+76 -114
View File
@@ -29,7 +29,7 @@ defmodule Kernel do
import Kernel, except: [if: 2, unless: 2]
See `Kernel.SpecialForms.import/2` for more information on importing.
See `import/2` for more information on importing.
Elixir also has special forms that are always imported and
cannot be skipped. These are described in `Kernel.SpecialForms`.
@@ -58,7 +58,7 @@ defmodule Kernel do
There are two data types without an accompanying module:
* Bitstring - a sequence of bits, created with `Kernel.SpecialForms.<<>>/1`.
* Bitstring - a sequence of bits, created with `<<>>/1`.
When the number of bits is divisible by 8, they are called binaries and can
be manipulated with Erlang's `:binary` module
* Reference - a unique value in the runtime system, created with `make_ref/0`
@@ -124,8 +124,9 @@ defmodule Kernel do
### Supporting documents
Elixir documentation also includes supporting documents under the
"Pages" section. Those are:
Under the "Pages" section in sidebar you will find tutorials, guides,
and reference documents that outline Elixir semantics and behaviours
in more detail. Those are:
* [Compatibility and deprecations](compatibility-and-deprecations.md) - lists
compatibility between every Elixir version and Erlang/OTP, release schema;
@@ -133,14 +134,12 @@ defmodule Kernel do
* [Library guidelines](library-guidelines.md) - general guidelines, anti-patterns,
and rules for those writing libraries
* [Naming conventions](naming-conventions.md) - naming conventions for Elixir code
* [Operators](operators.md) - lists all Elixir operators and their precedences
* [Operators reference](operators.md) - lists all Elixir operators and their precedences
* [Patterns and guards](patterns-and-guards.md) - an introduction to patterns,
guards, and extensions
* [Syntax reference](syntax-reference.md) - the language syntax reference
* [Typespecs](typespecs.md)- types and function specifications, including list of types
* [Typespecs reference](typespecs.md)- types and function specifications, including list of types
* [Unicode syntax](unicode-syntax.md) - outlines Elixir support for Unicode
* [Writing documentation](writing-documentation.md) - guidelines for writing
documentation in Elixir
## Guards
@@ -199,7 +198,7 @@ defmodule Kernel do
This means **comparisons in Elixir are structural**, as it has the goal
of comparing data types as efficiently as possible to create flexible
and perform data structures. This distinction is specially important
and performant data structures. This distinction is specially important
for functions that provide ordering, such as `>/2`, `</2`, `>=/2`,
`<=/2`, `min/2`, and `max/2`. For example:
@@ -380,11 +379,10 @@ defmodule Kernel do
end
@doc """
Extracts the part of the binary starting at `start` with length `length`.
Binaries are zero-indexed.
Extracts the part of the binary at `start` with `size`.
If `start` or `length` reference in any way outside the binary, an
`ArgumentError` exception is raised.
If `start` or `size` reference in any way outside the binary,
an `ArgumentError` exception is raised.
Allowed in guard tests. Inlined by the compiler.
@@ -393,13 +391,13 @@ defmodule Kernel do
iex> binary_part("foo", 1, 2)
"oo"
A negative `length` can be used to extract bytes that come *before* the byte
A negative `size` can be used to extract bytes that come *before* the byte
at `start`:
iex> binary_part("Hello", 5, -3)
"llo"
An `ArgumentError` is raised when the length is outside of the binary:
An `ArgumentError` is raised when the size is outside of the binary:
binary_part("Hello", 0, 10)
** (ArgumentError) argument error
@@ -2066,9 +2064,6 @@ defmodule Kernel do
{var, _, nil} when is_atom(var) ->
invalid_concat_left_argument_error(Atom.to_string(var))
{:^, _, [{var, _, nil}]} when is_atom(var) ->
invalid_concat_left_argument_error("^#{Atom.to_string(var)}")
_ ->
expanded_arg
end
@@ -2081,8 +2076,9 @@ defmodule Kernel do
defp invalid_concat_left_argument_error(arg) do
:erlang.error(
ArgumentError.exception(
"the left argument of <> operator inside a match should always be a literal " <>
"binary because its size can't be verified. Got: #{arg}"
"cannot perform prefix match because the left operand of <> has unknown size. " <>
"The left operand of <> inside a match should either be a literal binary or " <>
"an existing variable with the pin operator (such as ^some_var). Got: #{arg}"
)
)
end
@@ -2124,20 +2120,10 @@ defmodule Kernel do
end
erlang_error =
case :erlang.system_info(:otp_release) >= [?2, ?4] do
true ->
fn x ->
quote do
:erlang.error(unquote(x), :none, error_info: %{module: Exception})
end
end
false ->
fn x ->
quote do
:erlang.error(unquote(x))
end
end
fn x ->
quote do
:erlang.error(unquote(x), :none, error_info: %{module: Exception})
end
end
case message do
@@ -2376,7 +2362,7 @@ defmodule Kernel do
Keys in the `Enumerable` that don't exist in the struct are automatically
discarded. Note that keys must be atoms, as only atoms are allowed when
defining a struct. If keys in the `Enumerable` are duplicated, the last
defining a struct. If there are duplicate keys in the `Enumerable`, the last
entry will be taken (same behaviour as `Map.new/1`).
This function is useful for dynamically creating and updating structs, as
@@ -2495,7 +2481,7 @@ defmodule Kernel do
end
@doc """
Returns true if `term` is a struct; otherwise returns `false`.
Returns `true` if `term` is a struct; otherwise returns `false`.
Allowed in guard tests.
@@ -2531,7 +2517,7 @@ defmodule Kernel do
end
@doc """
Returns true if `term` is a struct of `name`; otherwise returns `false`.
Returns `true` if `term` is a struct of `name`; otherwise returns `false`.
`is_struct/2` does not check that `name` exists and is a valid struct.
If you want such validations, you must pattern match on the struct
@@ -2579,7 +2565,7 @@ defmodule Kernel do
end
@doc """
Returns true if `term` is an exception; otherwise returns `false`.
Returns `true` if `term` is an exception; otherwise returns `false`.
Allowed in guard tests.
@@ -2617,7 +2603,7 @@ defmodule Kernel do
end
@doc """
Returns true if `term` is an exception of `name`; otherwise returns `false`.
Returns `true` if `term` is an exception of `name`; otherwise returns `false`.
Allowed in guard tests.
@@ -3555,10 +3541,12 @@ defmodule Kernel do
not bootstrapped?(Macro) ->
nil
not function? and __CALLER__.context == :match ->
not function? and (__CALLER__.context == :match or __CALLER__.context == :guard) ->
raise ArgumentError,
"""
invalid write attribute syntax. If you want to define an attribute, don't do this:
invalid usage of module attributes. Module attributes cannot be used inside \
pattern matching (and guards) outside of a function. If you are trying to \
define an attribute, do not do this:
@foo = :value
@@ -3635,7 +3623,7 @@ defmodule Kernel do
defp do_at([], meta, name, function?, env) do
IO.warn(
"the @#{name}() notation (with parentheses) is deprecated, please use @#{name} (without parentheses) instead",
Macro.Env.stacktrace(env)
env
)
do_at(nil, meta, name, function?, env)
@@ -4601,9 +4589,8 @@ defmodule Kernel do
Marks that the given variable should not be hygienized.
This macro expects a variable and it is typically invoked
inside `Kernel.SpecialForms.quote/2` to mark that a variable
should not be hygienized. See `Kernel.SpecialForms.quote/2`
for more information.
inside `quote/2` to mark that a variable
should not be hygienized. See `quote/2` for more information.
## Examples
@@ -4639,7 +4626,7 @@ defmodule Kernel do
be hygienized. This means the alias will be expanded when
the macro is expanded.
Check `Kernel.SpecialForms.quote/2` for more information.
Check `quote/2` for more information.
"""
defmacro alias!(alias) when is_atom(alias) do
alias
@@ -4822,6 +4809,16 @@ defmodule Kernel do
Number.two()
#=> 2
## Module names and aliases
Module names (and aliases) must start with an ASCII uppercase character which
may be followed by any ASCII letter, number, or underscore. Elixir's
[Naming Conventions](naming-conventions.md) suggest for module names and aliases
to be written in the `CamelCase` format.
You can also use atoms as the module name, although they must only contain ASCII
characters.
## Nesting
Nesting a module inside another module affects the name of the nested module:
@@ -4840,7 +4837,7 @@ defmodule Kernel do
If the `Foo.Bar` module is moved somewhere else, the references to `Bar` in
the `Foo` module need to be updated to the fully-qualified name (`Foo.Bar`) or
an alias has to be explicitly set in the `Foo` module with the help of
`Kernel.SpecialForms.alias/2`.
`alias/2`.
defmodule Foo.Bar do
# code
@@ -4856,7 +4853,7 @@ defmodule Kernel do
Elixir module names can be dynamically generated. This is very
useful when working with macros. For instance, one could write:
defmodule String.to_atom("Foo#{1}") do
defmodule Module.concat(["Foo", "Bar"]) do
# contents ...
end
@@ -4892,26 +4889,15 @@ defmodule Kernel do
{expanded, with_alias} =
case is_atom(expanded) do
true ->
{full, old, opts} = alias_defmodule(alias, expanded, env)
# Expand the module considering the current environment/nesting
{full, old, new} = alias_defmodule(alias, expanded, env)
meta = [defined: full, context: env.module] ++ alias_meta(alias)
{full, {:alias, meta, [old, [as: new, warn: false]]}}
meta = [defined: full] ++ alias_meta(alias)
{full, {:require, meta, [old, opts]}}
false ->
{expanded, nil}
end
# We do this so that the block is not tail-call optimized and stacktraces
# are not messed up. Basically, we just insert something between the return
# value of the block and what is returned by defmodule. Using just ":ok" or
# similar doesn't work because it's likely optimized away by the compiler.
block =
quote do
result = unquote(block)
:elixir_utils.noop()
result
end
escaped =
case env do
%{function: nil, lexical_tracker: pid} when is_pid(pid) ->
@@ -4969,26 +4955,26 @@ defmodule Kernel do
# defmodule Elixir.Alias
defp alias_defmodule({:__aliases__, _, [:"Elixir", _ | _]}, module, _env),
do: {module, module, nil}
do: {module, module, []}
# defmodule Alias in root
defp alias_defmodule({:__aliases__, _, _}, module, %{module: nil}),
do: {module, module, nil}
defp alias_defmodule({:__aliases__, _, _}, module, %{module: nil}), do: {module, module, []}
# defmodule Alias nested
defp alias_defmodule({:__aliases__, _, [h | t]}, _module, env) when is_atom(h) do
module = :elixir_aliases.concat([env.module, h])
alias = String.to_atom("Elixir." <> Atom.to_string(h))
opts = [as: alias, warn: false]
case t do
[] -> {module, module, alias}
_ -> {String.to_atom(Enum.join([module | t], ".")), module, alias}
[] -> {module, module, opts}
_ -> {String.to_atom(Enum.join([module | t], ".")), module, opts}
end
end
# defmodule _
defp alias_defmodule(_raw, module, _env) do
{module, module, nil}
{module, module, []}
end
defp module_var({name, kind}, meta) when is_atom(kind), do: {name, meta, kind}
@@ -5079,34 +5065,18 @@ defmodule Kernel do
* can be given more than once
* ordered, as specified by the developer
## Function and variable names
## Function names
Function and variable names have the following syntax:
A _lowercase ASCII letter_ or an _underscore_, followed by any number of
_lowercase or uppercase ASCII letters_, _numbers_, or _underscores_.
Optionally they can end in either an _exclamation mark_ or a _question mark_.
For variables, any identifier starting with an underscore should indicate an
unused variable. For example:
def foo(bar) do
[]
end
#=> warning: variable bar is unused
def foo(_bar) do
[]
end
#=> no warning
def foo(_bar) do
_bar
end
#=> warning: the underscored variable "_bar" is used after being set
Function and variable names in Elixir must start with an underscore or a
Unicode letter that is not in uppercase or titlecase. They may continue
using a sequence of Unicode letters, numbers, and underscores. They may
end in `?` or `!`. Elixir's [Naming Conventions](naming-conventions.md)
suggest for function and variable names to be written in the `snake_case`
format.
## `rescue`/`catch`/`after`/`else`
Function bodies support `rescue`, `catch`, `after`, and `else` as `Kernel.SpecialForms.try/1`
Function bodies support `rescue`, `catch`, `after`, and `else` as `try/1`
does (known as "implicit try"). For example, the following two functions are equivalent:
def convert(number) do
@@ -5237,7 +5207,7 @@ defmodule Kernel do
A struct is a tagged map that allows developers to provide
default values for keys, tags to be used in polymorphic
dispatches and compile time assertions. For more information
about structs, please check `Kernel.SpecialForms.%/2`.
about structs, please check `%/2`.
It is only possible to define a struct per module, as the
struct is tied to the module itself. Calling `defstruct/1`
@@ -5362,7 +5332,8 @@ defmodule Kernel do
"""
defmacro defstruct(fields) do
quote bind_quoted: [fields: fields, bootstrapped?: bootstrapped?(Enum)] do
{struct, derive, kv, body} = Kernel.Utils.defstruct(__MODULE__, fields, bootstrapped?)
{struct, derive, kv, body} =
Kernel.Utils.defstruct(__MODULE__, fields, bootstrapped?, __ENV__)
case derive do
[] -> :ok
@@ -6039,7 +6010,7 @@ defmodule Kernel do
@doc since: "1.14.0"
defmacro dbg(code \\ quote(do: binding()), options \\ []) do
{mod, fun, args} = Application.compile_env!(__CALLER__, :elixir, :dbg_callback)
apply(mod, fun, [code, options, __CALLER__ | args])
Macro.compile_apply(mod, fun, [code, options, __CALLER__ | args], __CALLER__)
end
## Sigils
@@ -6205,35 +6176,26 @@ defmodule Kernel do
defmacro sigil_r(term, modifiers)
defmacro sigil_r({:<<>>, _meta, [string]}, options) when is_binary(string) do
binary = :elixir_interpolation.unescape_string(string, &Regex.unescape_map/1)
binary = :elixir_interpolation.unescape_string(string, &regex_unescape_map/1)
regex = Regex.compile!(binary, :binary.list_to_bin(options))
Macro.escape(regex)
end
defmacro sigil_r({:<<>>, meta, pieces}, options) do
binary = {:<<>>, meta, unescape_tokens(pieces, &Regex.unescape_map/1)}
binary = {:<<>>, meta, unescape_tokens(pieces, &regex_unescape_map/1)}
quote(do: Regex.compile!(unquote(binary), unquote(:binary.list_to_bin(options))))
end
@doc ~S"""
Handles the sigil `~R` for regular expressions.
It returns a regular expression pattern without interpolations and
without escape characters. Note it still supports escape of Regex
tokens (such as escaping `+` or `?`) and it also requires you to
escape the closing sigil character itself if it appears on the Regex.
More information on regexes can be found in the `Regex` module.
## Examples
iex> Regex.match?(~R(f#{1,3}o), "f#o")
true
"""
defmacro sigil_R(term, modifiers)
defp regex_unescape_map(:newline), do: true
defp regex_unescape_map(_), do: false
@doc false
defmacro sigil_R({:<<>>, _meta, [string]}, options) when is_binary(string) do
IO.warn(
"~R/.../ is deprecated, use ~r/.../ instead",
Macro.Env.stacktrace(__CALLER__)
)
regex = Regex.compile!(string, :binary.list_to_bin(options))
Macro.escape(regex)
end
+151 -57
View File
@@ -14,19 +14,12 @@ defmodule Kernel.ParallelCompiler do
@doc """
Starts a task for parallel compilation.
If you have a file that needs to compile other modules in parallel,
the spawned processes need to be aware of the compiler environment.
This function allows a developer to create a task that is aware of
those environments.
See `Task.async/1` for more information. The task spawned must be
always awaited on by calling `Task.await/1`
"""
@doc since: "1.6.0"
# TODO: Deprecate this on Elixir v1.20.
@doc deprecated: "Use `pmap/2` instead"
def async(fun) when is_function(fun, 0) do
case :erlang.get(:elixir_compiler_info) do
{compiler, _} ->
{compiler_pid, file_pid} ->
file = :erlang.get(:elixir_compiler_file)
dest = :erlang.get(:elixir_compiler_dest)
@@ -34,9 +27,9 @@ defmodule Kernel.ParallelCompiler do
{_parent, checker} = Module.ParallelChecker.get()
Task.async(fn ->
send(compiler, {:async, self()})
Module.ParallelChecker.put(compiler, checker)
:erlang.put(:elixir_compiler_info, {compiler, self()})
send(compiler_pid, {:async, self()})
Module.ParallelChecker.put(compiler_pid, checker)
:erlang.put(:elixir_compiler_info, {compiler_pid, file_pid})
:erlang.put(:elixir_compiler_file, file)
dest != :undefined and :erlang.put(:elixir_compiler_dest, dest)
:erlang.process_flag(:error_handler, error_handler)
@@ -50,6 +43,64 @@ defmodule Kernel.ParallelCompiler do
end
end
@doc """
Perform parallel compilation of `collection` with `fun`.
If you have a file that needs to compile other modules in parallel,
the spawned processes need to be aware of the compiler environment.
This function allows a developer to perform such tasks.
"""
@doc since: "1.16.0"
def pmap(collection, fun) when is_function(fun, 1) do
parent = self()
ref = make_ref()
# We spawn a series of tasks for parallel processing.
# The tasks notify themselves to the compiler.
tasks =
Enum.map(collection, fn item ->
async(fn ->
send(parent, {ref, self()})
receive do
^ref -> fun.(item)
end
end)
end)
# Then the tasks notify us. This is important because if
# we wait before the tasks notify the compiler, we may be
# released as there is nothing else running.
on =
for %{pid: pid} <- tasks do
receive do
{^ref, ^pid} -> pid
end
end
# Notify the compiler we are waiting on the tasks.
{compiler_pid, file_pid} = :erlang.get(:elixir_compiler_info)
defining = :elixir_module.compiler_modules()
send(compiler_pid, {:waiting, :pmap, self(), ref, file_pid, on, defining, :raise})
# Now we allow the tasks to run. This step is not strictly
# necessary but it makes compilation more deterministic by
# only allowing tasks to run once we are waiting.
Enum.each(on, &send(&1, ref))
# Await tasks and notify the compiler they are done. We could
# have the tasks report directly to the compiler, which in turn
# would notify us, but that would require reimplementing await_many,
# and copying the results across boundaries, so we don't.
res = Task.await_many(tasks, :infinity)
send(compiler_pid, {:available, :pmap, on})
# Only run once the compiler lets us, to avoid unbounded parallelism.
receive do
{^ref, _result} -> res
end
end
@doc """
Compiles the given files.
@@ -404,7 +455,11 @@ defmodule Kernel.ParallelCompiler do
# No more queue, nothing waiting, this cycle is done
defp spawn_workers([], spawned, waiting, files, result, warnings, errors, state)
when map_size(spawned) == 0 and map_size(waiting) == 0 do
[] = errors
# Print any spurious error that we may have found
Enum.map(errors, fn {diagnostic, read_snippet} ->
:elixir_errors.print_diagnostic(diagnostic, read_snippet)
end)
[] = files
cycle_return = each_cycle_return(state.each_cycle.())
state = cycle_timing(result, state)
@@ -459,8 +514,9 @@ defmodule Kernel.ParallelCompiler do
if deadlocked do
spawn_workers(deadlocked, spawned, waiting, files, result, warnings, errors, state)
else
deadlock_errors = handle_deadlock(waiting, files)
{return_error(deadlock_errors ++ errors, warnings), state}
return_error(warnings, errors, state, fn ->
handle_deadlock(waiting, files)
end)
end
end
@@ -528,7 +584,7 @@ defmodule Kernel.ParallelCompiler do
nilify_empty_or_sort(
for %{pid: file_pid} <- files,
{pid, {_, _, ^file_pid, on, _, _}} <- waiting_list,
not defining?(on, waiting_list),
is_atom(on) and not defining?(on, waiting_list),
do: {pid, :not_found}
)
end
@@ -536,7 +592,7 @@ defmodule Kernel.ParallelCompiler do
defp deadlocked(waiting_list, type, defining?) do
nilify_empty_or_sort(
for {pid, {_, _, _, on, _, ^type}} <- waiting_list,
defining?(on, waiting_list) == defining?,
is_atom(on) and defining?(on, waiting_list) == defining?,
do: {pid, :deadlock}
)
end
@@ -558,8 +614,8 @@ defmodule Kernel.ParallelCompiler do
new_spawned = Map.put(spawned, ref, pid)
wait_for_messages(queue, new_spawned, waiting, files, result, warnings, errors, state)
{:available, kind, module} ->
{available, result} = update_result(result, kind, module, true)
{:available, kind, on} ->
{available, result} = update_result(result, kind, on, :done)
spawn_workers(
available ++ queue,
@@ -577,7 +633,6 @@ defmodule Kernel.ParallelCompiler do
# Release the module loader which is waiting for an ack
send(child, {ref, :ack})
{available, result} = update_result(result, :module, module, binary)
spawn_workers(
@@ -596,7 +651,7 @@ defmodule Kernel.ParallelCompiler do
send(child, {ref, :not_found})
spawn_workers(queue, spawned, waiting, files, result, warnings, errors, state)
{:waiting, kind, child_pid, ref, file_pid, on, defining, deadlock?} ->
{:waiting, kind, child_pid, ref, file_pid, on, defining, deadlock} ->
# If we already got what we were waiting for, do not put it on waiting.
# If we're waiting on ourselves, send :found so that we can crash with
# a better error.
@@ -607,7 +662,7 @@ defmodule Kernel.ParallelCompiler do
send(child_pid, {ref, :found})
{waiting, files, result}
else
waiting = Map.put(waiting, child_pid, {kind, ref, file_pid, on, defining, deadlock?})
waiting = Map.put(waiting, child_pid, {kind, ref, file_pid, on, defining, deadlock})
files = update_timing(files, file_pid, :compiling)
result = Map.put(result, {kind, on}, [child_pid | available_or_pending])
{waiting, files, result}
@@ -631,12 +686,13 @@ defmodule Kernel.ParallelCompiler do
state = %{state | timer_ref: timer_ref}
spawn_workers(queue, spawned, waiting, files, result, warnings, errors, state)
{:diagnostic, %{severity: :warning} = diagnostic} ->
warnings = [Module.ParallelChecker.format_diagnostic_file(diagnostic) | warnings]
{:diagnostic, %{severity: :warning, file: file} = diagnostic, read_snippet} ->
:elixir_errors.print_diagnostic(diagnostic, read_snippet)
warnings = [%{diagnostic | file: file && Path.absname(file)} | warnings]
wait_for_messages(queue, spawned, waiting, files, result, warnings, errors, state)
{:diagnostic, %{severity: :error} = diagnostic} ->
errors = [Module.ParallelChecker.format_diagnostic_file(diagnostic) | errors]
{:diagnostic, %{severity: :error} = diagnostic, read_snippet} ->
errors = [{diagnostic, read_snippet} | errors]
wait_for_messages(queue, spawned, waiting, files, result, warnings, errors, state)
{:file_ok, child_pid, ref, file, lexical} ->
@@ -656,10 +712,13 @@ defmodule Kernel.ParallelCompiler do
spawn_workers(queue, new_spawned, waiting, new_files, result, warnings, errors, state)
{:file_error, child_pid, file, {kind, reason, stack}} ->
print_error(file, kind, reason, stack)
{_file, _new_spawned, new_files} = discard_file_pid(spawned, files, child_pid)
terminate(new_files)
{return_error([to_error(file, kind, reason, stack) | errors], warnings), state}
return_error(warnings, errors, state, fn ->
print_error(file, kind, reason, stack)
[to_error(file, kind, reason, stack)]
end)
{:DOWN, ref, :process, pid, reason} when is_map_key(spawned, ref) ->
# async spawned processes have no file, so we always have to delete the ref directly
@@ -668,18 +727,27 @@ defmodule Kernel.ParallelCompiler do
{file, spawned, files} = discard_file_pid(spawned, files, pid)
if file do
print_error(file.file, :exit, reason, [])
terminate(files)
{return_error([to_error(file.file, :exit, reason, []) | errors], warnings), state}
return_error(warnings, errors, state, fn ->
print_error(file.file, :exit, reason, [])
[to_error(file.file, :exit, reason, [])]
end)
else
wait_for_messages(queue, spawned, waiting, files, result, warnings, errors, state)
end
end
end
defp return_error(errors, warnings) do
defp return_error(warnings, errors, state, fun) do
errors =
Enum.map(errors, fn {%{file: file} = diagnostic, read_snippet} ->
:elixir_errors.print_diagnostic(diagnostic, read_snippet)
%{diagnostic | file: file && Path.absname(file)}
end)
info = %{compile_warnings: Enum.reverse(warnings), runtime_warnings: []}
{:error, Enum.reverse(errors), info}
{{:error, Enum.reverse(errors, fun.()), info}, state}
end
defp update_result(result, kind, module, value) do
@@ -809,12 +877,16 @@ defmodule Kernel.ParallelCompiler do
)
for {file, _, description, stacktrace} <- deadlock do
file = Path.absname(file)
%{
severity: :error,
file: Path.absname(file),
file: file,
source: file,
position: nil,
message: description,
stacktrace: stacktrace
stacktrace: stacktrace,
span: nil
}
end
end
@@ -838,42 +910,64 @@ defmodule Kernel.ParallelCompiler do
])
end
defp to_error(file, kind, reason, stack) do
line = get_line(file, reason, stack)
file = Path.absname(file)
defp to_error(source, kind, reason, stack) do
{file, line, span} = get_snippet_info(source, reason, stack)
source = Path.absname(source)
message = :unicode.characters_to_binary(Kernel.CLI.format_error(kind, reason, stack))
%{file: file, position: line || 0, message: message, severity: :error, stacktrace: stack}
%{
file: file || source,
source: source,
position: line || 0,
message: message,
severity: :error,
stacktrace: stack,
span: span,
details: {kind, reason}
}
end
defp get_line(_file, %{line: line, column: column}, _stack)
defp get_snippet_info(
_file,
%{file: file, line: line, column: column, end_line: end_line, end_column: end_column},
_stack
)
when is_integer(line) and line > 0 and is_integer(column) and column >= 0 and
is_integer(end_line) and end_line > 0 and is_integer(end_column) and end_column >= 0 do
{Path.absname(file), {line, column}, {end_line, end_column}}
end
defp get_snippet_info(_file, %{file: file, line: line, column: column}, _stack)
when is_integer(line) and line > 0 and is_integer(column) and column >= 0 do
{line, column}
{Path.absname(file), {line, column}, nil}
end
defp get_line(_file, %{line: line}, _stack) when is_integer(line) and line > 0 do
line
defp get_snippet_info(_file, %{line: line}, _stack) when is_integer(line) and line > 0 do
{nil, line, nil}
end
defp get_line(file, :undef, [{_, _, _, []}, {_, _, _, info} | _]) do
if Keyword.get(info, :file) == to_charlist(Path.relative_to_cwd(file)) do
Keyword.get(info, :line)
end
defp get_snippet_info(file, :undef, [{_, _, _, []}, {_, _, _, info} | _]) do
get_snippet_info_from_stacktrace_info(info, file)
end
defp get_line(file, _reason, [{_, _, _, [file: expanding]}, {_, _, _, info} | _])
defp get_snippet_info(file, _reason, [{_, _, _, [file: expanding]}, {_, _, _, info} | _])
when expanding in [~c"expanding macro", ~c"expanding struct"] do
if Keyword.get(info, :file) == to_charlist(Path.relative_to_cwd(file)) do
Keyword.get(info, :line)
end
get_snippet_info_from_stacktrace_info(info, file)
end
defp get_line(file, _reason, [{_, _, _, info} | _]) do
if Keyword.get(info, :file) == to_charlist(Path.relative_to_cwd(file)) do
Keyword.get(info, :line)
end
defp get_snippet_info(file, _reason, [{_, _, _, info} | _]) do
get_snippet_info_from_stacktrace_info(info, file)
end
defp get_line(_, _, _) do
nil
defp get_snippet_info(_, _, _) do
{nil, nil, nil}
end
defp get_snippet_info_from_stacktrace_info(info, file) do
if Keyword.get(info, :file) == to_charlist(Path.relative_to_cwd(file)) do
{nil, Keyword.get(info, :line), nil}
else
{nil, nil, nil}
end
end
end
+19 -14
View File
@@ -187,20 +187,23 @@ defmodule Kernel.SpecialForms do
iex> <<0, "foo">>
<<0, 102, 111, 111>>
Binaries need to be explicitly tagged as `binary`:
You can use one of `utf8` (the default), `utf16`, and `utf32` to
control how the string is encoded:
iex> <<"foo"::utf16>>
<<0, 102, 0, 111, 0, 111>>
Which is equivalent to writing:
iex> <<?f::utf16, ?o::utf16, ?o::utf16>>
<<0, 102, 0, 111, 0, 111>>
At runtime, binaries need to be explicitly tagged as `binary`:
iex> rest = "oo"
iex> <<102, rest::binary>>
"foo"
The `utf8`, `utf16`, and `utf32` types are for Unicode code points. They
can also be applied to literal strings and charlists:
iex> <<"foo"::utf16>>
<<0, 102, 0, 111, 0, 111>>
iex> <<"foo"::utf32>>
<<0, 0, 0, 102, 0, 0, 0, 111, 0, 0, 0, 111>>
Otherwise we get an `ArgumentError` when constructing the binary:
rest = "oo"
@@ -294,7 +297,7 @@ defmodule Kernel.SpecialForms do
`unsigned` (default) | `integer`
`little` | `integer`, `float`, `utf16`, `utf32`
`big` (default) | `integer`, `float`, `utf16`, `utf32`
`native` | `integer`, `utf16`, `utf32`
`native` | `integer`, `float`, `utf16`, `utf32`
### Sign
@@ -346,7 +349,7 @@ defmodule Kernel.SpecialForms do
Or as a part of function definitions to pattern match:
defmodule ImageTyper do
defmodule ImageType do
@png_signature <<137::size(8), 80::size(8), 78::size(8), 71::size(8),
13::size(8), 10::size(8), 26::size(8), 10::size(8)>>
@jpg_signature <<255::size(8), 216::size(8)>>
@@ -1023,9 +1026,10 @@ defmodule Kernel.SpecialForms do
end
end
require Hygiene
Hygiene.write()
Hygiene.read()
** (RuntimeError) undefined variable a or undefined function a/0
** (CompileError) undefined variable "a" (context Hygiene)
For such, you can explicitly pass the current module scope as
argument:
@@ -1044,6 +1048,7 @@ defmodule Kernel.SpecialForms do
end
end
require Hygiene
ContextHygiene.write()
ContextHygiene.read()
#=> 1
@@ -1540,7 +1545,7 @@ defmodule Kernel.SpecialForms do
area, as `{:ok, area}` or return `:error`. We could implement
this function as:
def area(map) do
def area(opts) do
case Map.fetch(opts, :width) do
{:ok, width} ->
case Map.fetch(opts, :height) do
@@ -1559,7 +1564,7 @@ defmodule Kernel.SpecialForms do
While the code above works, it is quite verbose. Using `with`,
we could rewrite it as:
def area(map) do
def area(opts) do
with {:ok, width} <- Map.fetch(opts, :width),
{:ok, height} <- Map.fetch(opts, :height) do
{:ok, width * height}
+51 -41
View File
@@ -385,17 +385,17 @@ defmodule Kernel.Typespec do
compile_error(caller, error)
end
line = line(meta)
location = location(meta)
vars = Keyword.keys(guard)
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
{return, state} = typespec(return, vars, caller, state)
spec = {:type, line, :fun, [{:type, line, :product, args}, return]}
spec = {:type, location, :fun, [{:type, location, :product, args}, return]}
{spec, state} =
case guard_to_constraints(guard, vars, meta, caller, state) do
{[], state} -> {spec, state}
{constraints, state} -> {{:type, line, :bounded_fun, [spec, constraints]}, state}
{constraints, state} -> {{:type, location, :bounded_fun, [spec, constraints]}, state}
end
ensure_no_unused_local_vars!(caller, state.local_vars)
@@ -437,7 +437,7 @@ defmodule Kernel.Typespec do
defp ensure_not_default(_), do: :ok
defp guard_to_constraints(guard, vars, meta, caller, state) do
line = line(meta)
location = location(meta)
fun = fn
{_name, {:var, _, context}}, {constraints, state} when is_atom(context) ->
@@ -445,9 +445,9 @@ defmodule Kernel.Typespec do
{name, type}, {constraints, state} ->
{spec, state} = typespec(type, vars, caller, state)
constraint = [{:atom, line, :is_subtype}, [{:var, line, name}, spec]]
constraint = [{:atom, location, :is_subtype}, [{:var, location, name}, spec]]
state = update_local_vars(state, name)
{[{:type, line, :constraint, constraint} | constraints], state}
{[{:type, location, :constraint, constraint} | constraints], state}
end
{constraints, state} = :lists.foldl(fun, {[], state}, guard)
@@ -456,21 +456,27 @@ defmodule Kernel.Typespec do
## To typespec conversion
defp line(meta) do
Keyword.get(meta, :line, 0)
defp location(meta) do
line = Keyword.get(meta, :line, 0)
if column = Keyword.get(meta, :column) do
{line, column}
else
line
end
end
# Handle unions
defp typespec({:|, meta, [_, _]} = exprs, vars, caller, state) do
exprs = collect_union(exprs)
{union, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, exprs)
{{:type, line(meta), :union, union}, state}
{{:type, location(meta), :union, union}, state}
end
# Handle binaries
defp typespec({:<<>>, meta, []}, _, _, state) do
line = line(meta)
{{:type, line, :binary, [{:integer, line, 0}, {:integer, line, 0}]}, state}
location = location(meta)
{{:type, location, :binary, [{:integer, location, 0}, {:integer, location, 0}]}, state}
end
defp typespec(
@@ -480,14 +486,18 @@ defmodule Kernel.Typespec do
state
)
when is_atom(ctx1) and is_atom(ctx2) and unit in 1..256 do
line = line(meta)
{{:type, line, :binary, [{:integer, line, 0}, {:integer, line(unit_meta), unit}]}, state}
location = location(meta)
{{:type, location, :binary, [{:integer, location, 0}, {:integer, location(unit_meta), unit}]},
state}
end
defp typespec({:<<>>, meta, [{:"::", size_meta, [{:_, _, ctx}, size]}]}, _, _, state)
when is_atom(ctx) and is_integer(size) and size >= 0 do
line = line(meta)
{{:type, line, :binary, [{:integer, line(size_meta), size}, {:integer, line, 0}]}, state}
location = location(meta)
{{:type, location, :binary, [{:integer, location(size_meta), size}, {:integer, location, 0}]},
state}
end
defp typespec(
@@ -505,8 +515,8 @@ defmodule Kernel.Typespec do
)
when is_atom(ctx1) and is_atom(ctx2) and is_atom(ctx3) and is_integer(size) and
size >= 0 and unit in 1..256 do
args = [{:integer, line(size_meta), size}, {:integer, line(unit_meta), unit}]
{{:type, line(meta), :binary, args}, state}
args = [{:integer, location(size_meta), size}, {:integer, location(unit_meta), unit}]
{{:type, location(meta), :binary, args}, state}
end
defp typespec({:<<>>, _meta, _args}, _vars, caller, _state) do
@@ -519,7 +529,7 @@ defmodule Kernel.Typespec do
## Handle maps and structs
defp typespec({:map, meta, args}, _vars, _caller, state) when args == [] or is_atom(args) do
{{:type, line(meta), :map, :any}, state}
{{:type, location(meta), :map, :any}, state}
end
defp typespec({:%{}, meta, fields} = map, vars, caller, state) do
@@ -527,17 +537,17 @@ defmodule Kernel.Typespec do
{{:required, meta2, [k]}, v}, state ->
{arg1, state} = typespec(k, vars, caller, state)
{arg2, state} = typespec(v, vars, caller, state)
{{:type, line(meta2), :map_field_exact, [arg1, arg2]}, state}
{{:type, location(meta2), :map_field_exact, [arg1, arg2]}, state}
{{:optional, meta2, [k]}, v}, state ->
{arg1, state} = typespec(k, vars, caller, state)
{arg2, state} = typespec(v, vars, caller, state)
{{:type, line(meta2), :map_field_assoc, [arg1, arg2]}, state}
{{:type, location(meta2), :map_field_assoc, [arg1, arg2]}, state}
{k, v}, state ->
{arg1, state} = typespec(k, vars, caller, state)
{arg2, state} = typespec(v, vars, caller, state)
{{:type, line(meta), :map_field_exact, [arg1, arg2]}, state}
{{:type, location(meta), :map_field_exact, [arg1, arg2]}, state}
{:|, _, [_, _]}, _state ->
error =
@@ -551,7 +561,7 @@ defmodule Kernel.Typespec do
end
{fields, state} = :lists.mapfoldl(fun, state, fields)
{{:type, line(meta), :map, fields}, state}
{{:type, location(meta), :map, fields}, state}
end
defp typespec({:%, _, [name, {:%{}, meta, fields}]} = node, vars, caller, state) do
@@ -644,7 +654,7 @@ defmodule Kernel.Typespec do
{right, state} = typespec(right, vars, caller, state)
:ok = validate_range(left, right, caller)
{{:type, line(meta), :range, [left, right]}, state}
{{:type, location(meta), :range, [left, right]}, state}
end
# Handle special forms
@@ -668,7 +678,7 @@ defmodule Kernel.Typespec do
pair -> pair
end
{{:type, line(meta), :fun, fun_args}, state}
{{:type, location(meta), :fun, fun_args}, state}
end
# Handle type operator
@@ -691,10 +701,10 @@ defmodule Kernel.Typespec do
# This may be generating an invalid typespec but we need to generate it
# to avoid breaking existing code that was valid but only broke Dialyzer
{right, state} = typespec(expr, vars, caller, state)
{{:ann_type, line(meta), [{:var, line(var_meta), var_name}, right]}, state}
{{:ann_type, location(meta), [{:var, location(var_meta), var_name}, right]}, state}
{right, state} ->
{{:ann_type, line(meta), [{:var, line(var_meta), var_name}, right]}, state}
{{:ann_type, location(meta), [{:var, location(var_meta), var_name}, right]}, state}
end
end
@@ -723,13 +733,13 @@ defmodule Kernel.Typespec do
{left, state} = typespec(left, vars, caller, state)
state = %{state | undefined_type_error_enabled?: true}
{right, state} = typespec(right, vars, caller, state)
{{:ann_type, line(meta), [left, right]}, state}
{{:ann_type, location(meta), [left, right]}, state}
end
# Handle unary ops
defp typespec({op, meta, [integer]}, _, _, state) when op in [:+, :-] and is_integer(integer) do
line = line(meta)
{{:op, line, op, {:integer, line, integer}}, state}
location = location(meta)
{{:op, location, op, {:integer, location, integer}}, state}
end
# Handle remote calls in the form of @module_attribute.type.
@@ -778,12 +788,12 @@ defmodule Kernel.Typespec do
# Handle tuples
defp typespec({:tuple, meta, []}, _vars, _caller, state) do
{{:type, line(meta), :tuple, :any}, state}
{{:type, location(meta), :tuple, :any}, state}
end
defp typespec({:{}, meta, t}, vars, caller, state) when is_list(t) do
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, t)
{{:type, line(meta), :tuple, args}, state}
{{:type, location(meta), :tuple, args}, state}
end
defp typespec({left, right}, vars, caller, state) do
@@ -799,7 +809,7 @@ defmodule Kernel.Typespec do
defp typespec({name, meta, atom}, vars, caller, state) when is_atom(atom) do
if :lists.member(name, vars) do
state = update_local_vars(state, name)
{{:var, line(meta), name}, state}
{{:var, location(meta), name}, state}
else
typespec({name, meta, []}, vars, caller, state)
end
@@ -814,7 +824,7 @@ defmodule Kernel.Typespec do
IO.warn(warning, caller)
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
{{:type, line(meta), :string, args}, state}
{{:type, location(meta), :string, args}, state}
end
defp typespec({:nonempty_string, meta, args}, vars, caller, state) do
@@ -825,7 +835,7 @@ defmodule Kernel.Typespec do
IO.warn(warning, caller)
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
{{:type, line(meta), :nonempty_string, args}, state}
{{:type, location(meta), :nonempty_string, args}, state}
end
defp typespec({type, _meta, []}, vars, caller, state) when type in [:charlist, :char_list] do
@@ -855,7 +865,7 @@ defmodule Kernel.Typespec do
defp typespec({:fun, meta, args}, vars, caller, state) do
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
{{:type, line(meta), :fun, args}, state}
{{:type, location(meta), :fun, args}, state}
end
defp typespec({:..., _meta, _args}, _vars, caller, _state) do
@@ -872,7 +882,7 @@ defmodule Kernel.Typespec do
case :erl_internal.is_type(name, arity) do
true ->
{{:type, line(meta), name, args}, state}
{{:type, location(meta), name, args}, state}
false ->
if state.undefined_type_error_enabled? and
@@ -890,7 +900,7 @@ defmodule Kernel.Typespec do
%{state | used_type_pairs: [{name, arity} | state.used_type_pairs]}
end
{{:user_type, line(meta), name, args}, state}
{{:user_type, location(meta), name, args}, state}
end
end
@@ -963,7 +973,7 @@ defmodule Kernel.Typespec do
defp remote_type({remote, meta, name, args}, vars, caller, state) do
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
{{:remote_type, line(meta), [remote, name, args]}, state}
{{:remote_type, location(meta), [remote, name, args]}, state}
end
defp collect_union({:|, _, [a, b]}), do: [a | collect_union(b)]
@@ -996,16 +1006,16 @@ defmodule Kernel.Typespec do
end
defp fn_args(meta, [{:..., _, _}], _vars, _caller, state) do
{{:type, line(meta), :any}, state}
{{:type, location(meta), :any}, state}
end
defp fn_args(meta, args, vars, caller, state) do
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
{{:type, line(meta), :product, args}, state}
{{:type, location(meta), :product, args}, state}
end
defp variable({name, meta, args}) when is_atom(name) and is_atom(args) do
{:var, line(meta), name}
{:var, location(meta), name}
end
defp variable(expr), do: expr
+10 -10
View File
@@ -36,14 +36,14 @@ defmodule Kernel.Utils do
if is_list(funs) do
IO.warn(
"passing a list to Kernel.defdelegate/2 is deprecated, please define each delegate separately",
Macro.Env.stacktrace(env)
env
)
end
if Keyword.has_key?(opts, :append_first) do
IO.warn(
"Kernel.defdelegate/2 :append_first option is deprecated",
Macro.Env.stacktrace(env)
env
)
end
@@ -100,7 +100,7 @@ defmodule Kernel.Utils do
@doc """
Callback for defstruct.
"""
def defstruct(module, fields, bootstrapped?) do
def defstruct(module, fields, bootstrapped?, env) do
{set, bag} = :elixir_module.data_tables(module)
if :ets.member(set, :__struct__) do
@@ -152,7 +152,7 @@ defmodule Kernel.Utils do
end
# TODO: Make it raise on v2.0
warn_on_duplicate_struct_key(:lists.keysort(1, fields))
warn_on_duplicate_struct_key(:lists.keysort(1, fields), env)
foreach = fn
key when is_atom(key) ->
@@ -227,17 +227,17 @@ defmodule Kernel.Utils do
end
end
defp warn_on_duplicate_struct_key([]) do
defp warn_on_duplicate_struct_key([], _) do
:ok
end
defp warn_on_duplicate_struct_key([{key, _} | [{key, _} | _] = rest]) do
IO.warn("duplicate key #{inspect(key)} found in struct")
warn_on_duplicate_struct_key(rest)
defp warn_on_duplicate_struct_key([{key, _} | [{key, _} | _] = rest], env) do
IO.warn("duplicate key #{inspect(key)} found in struct", env)
warn_on_duplicate_struct_key(rest, env)
end
defp warn_on_duplicate_struct_key([_ | rest]) do
warn_on_duplicate_struct_key(rest)
defp warn_on_duplicate_struct_key([_ | rest], env) do
warn_on_duplicate_struct_key(rest, env)
end
@doc """
+5 -1
View File
@@ -5,7 +5,9 @@ defmodule Keyword do
The first element of these tuples is known as the *key*, and it must be an atom.
The second element, known as the *value*, can be any term.
Keywords are mostly used to work with optional values.
Keywords are mostly used to work with optional values. For a general introduction
to keywords and how the compare with maps, see our [Keyword and Maps](keywords-and-maps.md)
guide.
## Examples
@@ -110,6 +112,8 @@ defmodule Keyword do
iex> Keyword.from_keys([:foo, :bar, :baz], :atom)
[foo: :atom, bar: :atom, baz: :atom]
iex> Keyword.from_keys([], :atom)
[]
"""
@doc since: "1.14.0"
+5 -5
View File
@@ -57,8 +57,8 @@ defmodule List do
iex> list ++ [4] # slow
[1, 2, 3, 4]
Most of the functions in this module work in linear time. This means that,
that the time it takes to perform an operation grows at the same rate as the
Most of the functions in this module work in linear time. This means that
the time it takes to perform an operation grows at the same rate as the
length of the list. For example `length/1` and `last/1` will run in linear
time because they need to iterate through every element of the list, but
`first/1` will run in constant time because it only needs the first element.
@@ -927,7 +927,7 @@ defmodule List do
@doc """
Converts a charlist to an atom.
Elixir supports conversions from charlists which contains any Unicode
Elixir supports conversions from charlists which contain any Unicode
code point.
Inlined by the compiler.
@@ -949,7 +949,7 @@ defmodule List do
@doc """
Converts a charlist to an existing atom.
Elixir supports conversions from charlists which contains any Unicode
Elixir supports conversions from charlists which contain any Unicode
code point. Raises an `ArgumentError` if the atom does not exist.
Inlined by the compiler.
@@ -1168,7 +1168,7 @@ defmodule List do
An *edit script* is a keyword list. Each key describes the "editing action" to
take in order to bring `list1` closer to being equal to `list2`; a key can be
`:eq`, `:ins`, or `:del`. Each value is a sublist of either `list1` or `list2`
that should be inserted (if the corresponding key `:ins`), deleted (if the
that should be inserted (if the corresponding key is `:ins`), deleted (if the
corresponding key is `:del`), or left alone (if the corresponding key is
`:eq`) in `list1` in order to be closer to `list2`.
+45 -81
View File
@@ -61,75 +61,6 @@ defmodule Macro do
> The functions in this module do not evaluate code. In fact,
> evaluating code from macros is often an anti-pattern. For code
> evaluation, see the `Code` module.
## Custom Sigils
Macros are also commonly used to implement custom sigils.
Sigils start with `~` and are followed by one lowercase letter or by one
or more uppercase letters, and then a separator
(see the [Syntax Reference](syntax-reference.md)). One example is
`~D[2020-10-13]` to define a date.
To create a custom sigil, define a macro with the name `sigil_{identifier}`
that takes two arguments. The first argument will be the string, the second
will be a charlist containing any modifiers. If the sigil is lower case
(such as `sigil_x`) then the string argument will allow interpolation.
If the sigil is one or more upper case letters (such as `sigil_X` and
`sigil_EXAMPLE`) then the string will not be interpolated.
Valid modifiers are ASCII letters and digits. Any other character will
cause a syntax error.
Single-letter sigils are typically reserved to the language. Multi-letter
sigils are uppercased and extensively used by the community to embed
alternative markups and data-types within Elixir source code.
The module containing the custom sigil must be imported before the sigil
syntax can be used.
### Examples
As an example, let's define a sigil `~x` and sigil `~X` which
return its contents as a string. However, if the `r` modifier
is given, it reverses the string instead:
defmodule MySigils do
defmacro sigil_x(term, [?r]) do
quote do
unquote(term) |> String.reverse()
end
end
defmacro sigil_x(term, _modifiers) do
term
end
defmacro sigil_X(term, [?r]) do
quote do
unquote(term) |> String.reverse()
end
end
defmacro sigil_X(term, _modifiers) do
term
end
end
import MySigils
~x(with #{"inter" <> "polation"})
#=> "with interpolation"
~x(with #{"inter" <> "polation"})r
#=> "noitalopretni htiw"
~X(without #{"interpolation"})
#=> "without \#{"interpolation"}"
~X(without #{"interpolation"})r
#=> "}\"noitalopretni\"{# tuohtiw"
"""
alias Code.Identifier
@@ -181,6 +112,12 @@ defmodule Macro do
the compiler, each variable is identified by the combination of either
`name` and `metadata[:counter]`, or `name` and `context`.
* `:from_brackets` - Used to determine whether a call to `Access.get/3` is from
bracket syntax.
* `:from_interpolation` - Used to determine whether a call to `Kernel.to_string/1` is
from interpolation.
* `:generated` - Whether the code should be considered as generated by
the compiler or not. This means the compiler and tools like Dialyzer may not
emit certain warnings.
@@ -192,28 +129,37 @@ defmodule Macro do
* `:keep` - Used by `quote/2` with the option `location: :keep` to annotate
the file and the line number of the quoted source.
* `:line` - The line number of the AST node.
* `:from_brackets` - Used to determine whether a call to `Access.get/3` is from
bracket syntax or a function call.
* `:line` - The line number of the AST node. Note line information is discarded
from quoted code but can be enabled back via the `:line` option.
The following metadata keys are enabled by `Code.string_to_quoted/2`:
* `:closing` - contains metadata about the closing pair, such as a `}`
in a tuple or in a map, or such as the closing `)` in a function call
with parens. The `:closing` does not delimit the end of expression if
there are `:do` and `:end` metadata (when `:token_metadata` is true)
* `:column` - the column number of the AST node (when `:columns` is true)
with parens (when `:token_metadata` is true). If the function call
has a do-end block attached to it, its metadata is found under the
`:do` and `:end` metadata
* `:column` - the column number of the AST node (when `:columns` is true).
Note column information is always discarded from quoted code.
* `:delimiter` - contains the opening delimiter for sigils, strings,
and charlists as a string (such as `"{"`, `"/"`, `"'"`, and the like)
* `:format` - set to `:keyword` when an atom is defined as a keyword
* `:do` - contains metadata about the `do` location in a function call with
`do`-`end` blocks (when `:token_metadata` is true)
* `:end` - contains metadata about the `end` location in a function call with
`do`-`end` blocks (when `:token_metadata` is true)
* `:end_of_expression` - denotes when the end of expression effectively
happens. Available for all expressions except the last one inside a
`__block__` (when `:token_metadata` is true)
happens (when `:token_metadata` is true). This is only available for
direct children of a `__block__`, and it is either the location of a
newline or of the `;` character. The last expression of `__block__`
does not have this metadata.
* `:indentation` - indentation of a sigil heredoc
The following metadata keys are private:
@@ -438,10 +384,12 @@ defmodule Macro do
def generate_arguments(amount, context), do: generate_arguments(amount, context, &var/2)
@doc """
Returns the path to the node in `ast` which `fun` returns true.
Returns the path to the node in `ast` for which `fun` returns a truthy value.
The path is a list, starting with the node in which `fun` returns
true, followed by all of its parents.
a truthy value, followed by all of its parents.
Returns `nil` if `fun` returns only falsy values.
Computing the path can be an efficient operation when you want
to find a particular node in the AST within its context and then
@@ -452,6 +400,9 @@ defmodule Macro do
iex> Macro.path(quote(do: [1, 2, 3]), & &1 == 3)
[3, [1, 2, 3]]
iex> Macro.path(quote(do: [1, 2]), & &1 == 5)
nil
iex> Macro.path(quote(do: Foo.bar(3)), & &1 == 3)
[3, quote(do: Foo.bar(3))]
@@ -466,6 +417,7 @@ defmodule Macro do
"""
@doc since: "1.14.0"
@spec path(t, (t -> as_boolean(term))) :: [t] | nil
def path(ast, fun) when is_function(fun, 1) do
path(ast, [], fun)
end
@@ -1758,6 +1710,18 @@ defmodule Macro do
end
end
@doc """
Applies a `mod`, `function`, and `args` at compile-time in `caller`.
This is used when you want to programatically invoke a macro at
compile-time.
"""
@doc since: "1.16.0"
def compile_apply(mod, fun, args, caller) do
:elixir_env.trace({:remote_macro, [], mod, fun, length(args)}, caller)
Kernel.apply(mod, fun, args)
end
@doc """
Receives an AST node and expands it once.
@@ -2322,7 +2286,7 @@ defmodule Macro do
## Atom handling
@doc """
Classifies a runtime `atom` based on its possible AST placement.
Classifies an `atom` based on its possible AST placement.
It returns one of the following atoms:
+14 -4
View File
@@ -11,8 +11,8 @@ defmodule Macro.Env do
following trick:
def make_custom_env do
import SomeModule, only: [some_function: 2]
alias A.B.C
import SomeModule, only: [some_function: 2], warn: false
alias A.B.C, warn: false
__ENV__
end
@@ -97,7 +97,7 @@ defmodule Macro.Env do
]
# Define the __struct__ callbacks by hand for bootstrap reasons.
{struct, [], kv, body} = Kernel.Utils.defstruct(__MODULE__, fields, false)
{struct, [], kv, body} = Kernel.Utils.defstruct(__MODULE__, fields, false, __ENV__)
def __struct__(), do: unquote(:elixir_quote.escape(struct, false, :none))
def __struct__(unquote(kv)), do: unquote(body)
@@ -182,6 +182,8 @@ defmodule Macro.Env do
"""
@doc since: "1.13.0"
@spec fetch_alias(t, atom) :: {:ok, atom} | :error
def fetch_alias(env, atom)
def fetch_alias(%{__struct__: Macro.Env, aliases: aliases}, atom) when is_atom(atom),
do: Keyword.fetch(aliases, :"Elixir.#{atom}")
@@ -196,6 +198,8 @@ defmodule Macro.Env do
"""
@doc since: "1.13.0"
@spec fetch_macro_alias(t, atom) :: {:ok, atom} | :error
def fetch_macro_alias(env, atom)
def fetch_macro_alias(%{__struct__: Macro.Env, macro_aliases: aliases}, atom)
when is_atom(atom),
do: Keyword.fetch(aliases, :"Elixir.#{atom}")
@@ -225,6 +229,8 @@ defmodule Macro.Env do
"""
@doc since: "1.13.0"
@spec lookup_import(t, name_arity) :: [{:function | :macro, module}]
def lookup_import(env, name_arity)
def lookup_import(
%{__struct__: Macro.Env, functions: functions, macros: macros},
{name, arity} = pair
@@ -256,12 +262,14 @@ defmodule Macro.Env do
"""
@doc since: "1.15.0"
@spec lookup_alias_as(t, atom) :: [atom]
def lookup_alias_as(env, atom)
def lookup_alias_as(%{__struct__: Macro.Env, aliases: aliases}, atom) when is_atom(atom) do
for {name, ^atom} <- aliases, do: name
end
@doc """
Returns true if the given module has been required.
Returns `true` if the given module has been required.
## Examples
@@ -276,6 +284,8 @@ defmodule Macro.Env do
"""
@doc since: "1.13.0"
@spec required?(t, module) :: boolean
def required?(env, module)
def required?(%{__struct__: Macro.Env, requires: requires}, mod) when is_atom(mod),
do: mod in requires
+3 -3
View File
@@ -14,7 +14,7 @@ defmodule Map do
in the example above has a different order than the map that was created).
Maps do not impose any restriction on the key type: anything can be a key in a
map. As a key-value structure, maps do not allow duplicated keys. Keys are
map. As a key-value structure, maps do not allow duplicate keys. Keys are
compared using the exact-equality operator (`===/2`). If colliding keys are defined
in a map literal, the last one prevails.
@@ -147,7 +147,7 @@ defmodule Map do
## Examples
iex> Map.keys(%{a: 1, b: 2})
Map.keys(%{a: 1, b: 2})
[:a, :b]
"""
@@ -161,7 +161,7 @@ defmodule Map do
## Examples
iex> Map.values(%{a: 1, b: 2})
Map.values(%{a: 1, b: 2})
[1, 2]
"""
+60 -10
View File
@@ -283,6 +283,24 @@ defmodule Module do
end
end
Note that this is only valid for exceptions/diagnostics that come from the
definition inner scope (which includes its patterns and guards). For example:
defmodule MyModule do # <---- module definition
@file "hello.ex"
defp unused(a) do # <---- function definition
"world" # <---- function scope
end
@file "bye.ex"
def unused(_), do: true
end
If you run this code with the second "unused" definition commented, you will
see that `hello.ex` is used as the stacktrace when reporting warnings, but if
you uncomment it you'll see that the error will not mention `bye.ex`, because
it's a module-level error rather than an expression-level error.
### `@moduledoc`
Provides documentation for the current module.
@@ -305,6 +323,21 @@ defmodule Module do
Once this module is compiled, this information becomes available via
the `Code.fetch_docs/1` function.
### `@nifs` (since v1.16.0)
A list of functions and their arities which will be overridden
by a native implementation (NIF).
defmodule MyLibrary.MyModule do
@nifs [foo: 1, bar: 2]
def foo(arg1), do: :erlang.nif_error(:not_loaded)
def bar(arg1, arg2), do: :erlang.nif_error(:not_loaded)
end
See the Erlang documentation for more information:
https://www.erlang.org/doc/man/erl_nif
### `@on_definition`
A hook that will be invoked when each function or macro in the current
@@ -611,6 +644,7 @@ defmodule Module do
"""
@doc since: "1.12.0"
@spec reserved_attributes() :: map
def reserved_attributes() do
%{
after_compile: %{
@@ -1102,7 +1136,7 @@ defmodule Module do
Use `Kernel.function_exported?/3` and `Kernel.macro_exported?/3` to check for
public functions and macros respectively in compiled modules.
Note that `defines?` returns false for functions and macros that have
Note that `defines?` returns `false` for functions and macros that have
been defined but then marked as overridable and no other implementation
has been provided. You can check the overridable status by calling
`overridable?/2`.
@@ -1337,8 +1371,8 @@ defmodule Module do
@doc """
Deletes a definition from a module.
It returns true if the definition exists and it was removed,
otherwise it returns false.
It returns `true` if the definition exists and it was removed,
otherwise it returns `false`.
"""
@doc since: "1.12.0"
@spec delete_definition(module, definition) :: boolean()
@@ -1444,7 +1478,7 @@ defmodule Module do
Returns `true` if `tuple` in `module` was marked as overridable
at some point.
Note `overridable?/2` returns true even if the definition was
Note `overridable?/2` returns `true` even if the definition was
already overridden. You can use `defines?/2` to see if a definition
exists or one is pending.
"""
@@ -2099,8 +2133,7 @@ defmodule Module do
end
defp attribute_stack(module, line) do
file = String.to_charlist(Path.relative_to_cwd(:elixir_module.file(module)))
[{module, :__MODULE__, 0, file: file, line: line}]
struct!(Macro.Env, module: module, file: :elixir_module.file(module), line: line)
end
## Helpers
@@ -2190,6 +2223,16 @@ defmodule Module do
end
end
defp preprocess_attribute(:nifs, value) do
unless function_arity_list?(value) do
raise ArgumentError,
"@nifs is a built-in module attribute for specifying a list " <>
"of functions and their arities that are NIFs, got: #{inspect(value)}"
end
value
end
defp preprocess_attribute(:dialyzer, value) do
# From https://github.com/erlang/otp/blob/master/lib/stdlib/src/erl_lint.erl
:lists.foreach(
@@ -2208,17 +2251,22 @@ defmodule Module do
value
end
defp valid_dialyzer_attribute?({key, fun_arities}) when is_atom(key) do
(key == :nowarn_function or valid_dialyzer_attribute?(key)) and
defp function_arity_list?(fun_arities) do
is_list(fun_arities) and
:lists.all(
fn
{fun, arity} when is_atom(fun) and is_integer(arity) -> true
_ -> false
end,
List.wrap(fun_arities)
fun_arities
)
end
defp valid_dialyzer_attribute?({key, fun_arities}) when is_atom(key) do
(key == :nowarn_function or valid_dialyzer_attribute?(key)) and
function_arity_list?(List.wrap(fun_arities))
end
defp valid_dialyzer_attribute?(attr) do
:lists.member(
attr,
@@ -2226,7 +2274,9 @@ defmodule Module do
[:no_match, :no_opaque, :no_fail_call, :no_contracts] ++
[:no_behaviours, :no_undefined_callbacks, :unmatched_returns] ++
[:error_handling, :race_conditions, :no_missing_calls] ++
[:specdiffs, :overspecs, :underspecs, :unknown, :no_underspecs]
[:specdiffs, :overspecs, :underspecs, :unknown, :no_underspecs] ++
[:extra_return, :no_extra_return, :no_missing_return] ++
[:missing_return, :no_unknown]
)
end
+19 -14
View File
@@ -33,7 +33,7 @@ defmodule Module.LocalsTracker do
"""
def add_local({_set, bag}, from, to, meta, macro_dispatch?)
when is_tuple(from) and is_tuple(to) and is_boolean(macro_dispatch?) do
put_edge(bag, {:local, from}, {to, get_line(meta), macro_dispatch?})
put_edge(bag, {:local, from}, {to, get_position(meta), macro_dispatch?})
:ok
end
@@ -102,12 +102,21 @@ defmodule Module.LocalsTracker do
@doc """
Collect undefined functions based on local calls and existing definitions.
"""
def collect_undefined_locals({set, bag}, all_defined) do
def collect_undefined_locals({set, bag}, all_defined, file) do
undefined =
for {pair, _, meta, _} <- all_defined,
{local, line, macro_dispatch?} <- out_neighbours(bag, {:local, pair}),
error = undefined_local_error(set, local, macro_dispatch?),
do: {pair, build_meta(line, meta), local, error}
{{local_name, _} = local, position, macro_dispatch?} <-
out_neighbours(bag, {:local, pair}),
error = undefined_local_error(set, local, macro_dispatch?) do
file =
case Keyword.get(meta, :file) do
{keep_file, _keep_line} -> keep_file
nil -> file
end
meta = build_meta(position, local_name)
{pair, meta, file, local, error}
end
:lists.usort(undefined)
end
@@ -212,16 +221,12 @@ defmodule Module.LocalsTracker do
end
defp get_line(meta), do: Keyword.get(meta, :line)
defp get_position(meta), do: {get_line(meta), meta[:column]}
defp build_meta(nil, _meta), do: []
# We need to transform any file annotation in the function
# definition into a keep annotation that is used by the
# error handling system in order to respect line/file.
defp build_meta(line, meta) do
case Keyword.get(meta, :file) do
{file, _} -> [keep: {file, line}]
_ -> [line: line]
defp build_meta(position, function_name) do
case position do
{line, nil} -> [line: line]
{line, col} -> :elixir_env.calculate_span([line: line, column: col], function_name)
end
end
+58 -54
View File
@@ -155,8 +155,7 @@ defmodule Module.ParallelChecker do
case :erlang.get(:elixir_code_diagnostics) do
:undefined -> :ok
{tail, true} -> :erlang.put(:elixir_code_diagnostics, {diagnostics ++ tail, true})
{tail, false} -> :erlang.put(:elixir_code_diagnostics, {diagnostics ++ tail, false})
{tail, log?} -> :erlang.put(:elixir_code_diagnostics, {diagnostics ++ tail, log?})
end
diagnostics
@@ -168,8 +167,9 @@ defmodule Module.ParallelChecker do
defp collect_results(count, diagnostics) do
receive do
{:diagnostic, diagnostic} ->
diagnostic = format_diagnostic_file(diagnostic)
{:diagnostic, %{file: file} = diagnostic, read_snippet} ->
:elixir_errors.print_diagnostic(diagnostic, read_snippet)
diagnostic = %{diagnostic | file: file && Path.absname(file)}
collect_results(count, [diagnostic | diagnostics])
{__MODULE__, _module, new_diagnostics} ->
@@ -288,11 +288,6 @@ defmodule Module.ParallelChecker do
end
end
@doc false
def format_diagnostic_file(%{file: file} = diagnostic) do
%{diagnostic | file: file && Path.absname(file)}
end
## Warning helpers
defp group_warnings(warnings) do
@@ -309,79 +304,88 @@ defmodule Module.ParallelChecker do
Enum.flat_map(warnings, fn {module, warning, locations} ->
message = module.format_warning(warning)
diagnostics = Enum.map(locations, &to_diagnostic(message, &1))
log? and :elixir_errors.print_warning([message, ?\n, format_stacktraces(diagnostics)])
log? and print_warning(message, diagnostics)
diagnostics
end)
end
defp format_stacktraces([diagnostic]) do
format_diagnostic_stacktrace(diagnostic)
defp print_warning(message, [diagnostic]) do
:elixir_errors.print_warning(message, diagnostic)
end
defp format_stacktraces(diagnostics) do
[
"Invalid call found at #{length(diagnostics)} locations:\n",
Enum.map(diagnostics, &format_diagnostic_stacktrace/1)
]
defp print_warning(message, grouped_warnings) do
:elixir_errors.print_warning_group(message, grouped_warnings)
end
defp format_diagnostic_stacktrace(%{stacktrace: [stacktrace]}) do
[" ", Exception.format_stacktrace_entry(stacktrace), ?\n]
end
defp to_diagnostic(message, {file, line, mfa}) do
defp to_diagnostic(message, {file, position, mfa}) when is_list(position) do
%{
severity: :warning,
source: file,
file: file,
position: line,
position: position_to_tuple(position),
message: IO.iodata_to_binary(message),
stacktrace: [to_stacktrace(file, line, mfa)]
stacktrace: [to_stacktrace(file, position, mfa)],
span: nil
}
end
defp to_stacktrace(file, line, {module, fun, arity}),
do: {module, fun, arity, location(file, line)}
defp position_to_tuple(position) do
case position[:column] do
nil -> position[:line] || 0
col -> {position[:line], col}
end
end
defp to_stacktrace(file, line, nil),
do: {:elixir_compiler, :__FILE__, 1, location(file, line)}
defp to_stacktrace(file, pos, {module, fun, arity}),
do: {module, fun, arity, location(file, pos)}
defp to_stacktrace(file, line, module),
do: {module, :__MODULE__, 0, location(file, line)}
defp to_stacktrace(file, pos, nil),
do: {:elixir_compiler, :__FILE__, 1, location(file, pos)}
defp location(file, line) do
[file: String.to_charlist(Path.relative_to_cwd(file)), line: line]
defp to_stacktrace(file, pos, module),
do: {module, :__MODULE__, 0, location(file, pos)}
defp location(file, position) do
[{:file, String.to_charlist(Path.relative_to_cwd(file))} | position]
end
## Cache
defp cache_module({server, ets}, module) do
if lock(server, module) do
cache_from_chunk(ets, module) || cache_from_info(ets, module)
object_code = :code.get_object_code(module)
# The chunk has more information, so that's our preference
with {^module, binary, _filename} <- object_code,
{:ok, {^module, [{~c"ExCk", chunk}]}} <- :beam_lib.chunks(binary, [~c"ExCk"]),
{:elixir_checker_v1, contents} <- :erlang.binary_to_term(chunk) do
cache_chunk(ets, module, contents.exports)
else
_ ->
# Otherwise, if the module is loaded, use its info
case :erlang.module_loaded(module) do
true ->
{mode, exports} = info_exports(module)
deprecated = info_deprecated(module)
cache_info(ets, module, exports, deprecated, mode)
false ->
# Or load exports from chunk
with {^module, binary, _filename} <- object_code,
{:ok, {^module, [exports: exports]}} <- :beam_lib.chunks(binary, [:exports]) do
exports = Map.new(Enum.map(exports, &{&1, :def}))
cache_info(ets, module, exports, %{}, :erlang)
else
_ ->
:ets.insert(ets, {{:cached, module}, false})
end
end
end
unlock(server, module)
end
end
defp cache_from_chunk(ets, module) do
with {^module, binary, _filename} <- :code.get_object_code(module),
{:ok, {^module, [{~c"ExCk", chunk}]}} <- :beam_lib.chunks(binary, [~c"ExCk"]),
{:elixir_checker_v1, contents} <- :erlang.binary_to_term(chunk) do
cache_chunk(ets, module, contents.exports)
true
else
_ -> false
end
end
defp cache_from_info(ets, module) do
if Code.ensure_loaded?(module) do
{mode, exports} = info_exports(module)
deprecated = info_deprecated(module)
cache_info(ets, module, exports, deprecated, mode)
else
:ets.insert(ets, {{:cached, module}, false})
end
end
defp info_exports(module) do
map =
Map.new(
+12 -8
View File
@@ -137,7 +137,7 @@ defmodule Module.Types do
# Collect relevant information from context and traces to report error
def error_to_warning(:unable_apply, {mfa, args, expected, signature, stack}, context) do
{fun, arity} = context.function
location = {context.file, get_line(stack), {context.module, fun, arity}}
location = {context.file, get_position(stack), {context.module, fun, arity}}
traces = type_traces(stack, context)
{[signature | args], traces} = lift_all_types([signature | args], traces, context)
@@ -147,7 +147,7 @@ defmodule Module.Types do
def error_to_warning(:unable_unify, {left, right, stack}, context) do
{fun, arity} = context.function
location = {context.file, get_line(stack), {context.module, fun, arity}}
location = {context.file, get_position(stack), {context.module, fun, arity}}
traces = type_traces(stack, context)
{[left, right], traces} = lift_all_types([left, right], traces, context)
@@ -155,7 +155,9 @@ defmodule Module.Types do
{Module.Types, error, location}
end
defp get_line(stack), do: stack.last_expr |> get_meta() |> Keyword.get(:line, 0)
defp get_position(stack) do
get_meta(stack.last_expr)
end
# Collect relevant traces from context.traces using stack.unify_stack
defp type_traces(stack, context) do
@@ -349,8 +351,8 @@ defmodule Module.Types do
end)
end
defp format_location({file, line, _mfa}) do
format_location({file, line})
defp format_location({file, position, _mfa}) do
format_location({file, position[:line]})
end
defp format_location({file, line}) do
@@ -411,14 +413,14 @@ defmodule Module.Types do
defp format_message_hint(:inferred_dot) do
"""
HINT: "var.field" (without parentheses) implies "var" is a map() while \
#{hint()} "var.field" (without parentheses) implies "var" is a map() while \
"var.fun()" (with parentheses) implies "var" is an atom()
"""
end
defp format_message_hint(:inferred_bitstring_spec) do
"""
HINT: all expressions given to binaries are assumed to be of type \
#{hint()} all expressions given to binaries are assumed to be of type \
integer() unless said otherwise. For example, <<expr>> assumes "expr" \
is an integer. Pass a modifier, such as <<expr::float>> or <<expr::binary>>, \
to change the default behaviour.
@@ -427,11 +429,13 @@ defmodule Module.Types do
defp format_message_hint({:sized_and_unsize_tuples, {size, var}}) do
"""
HINT: use pattern matching or "is_tuple(#{Macro.to_string(var)}) and \
#{hint()} use pattern matching or "is_tuple(#{Macro.to_string(var)}) and \
tuple_size(#{Macro.to_string(var)}) == #{size}" to guard a sized tuple.
"""
end
defp hint, do: :elixir_errors.prefix(:hint)
defp format_type_hint(type, types, expr, hints) do
case format_type_hint(type, types, expr) do
{message, hint} -> {message, [hint | hints]}
+31 -19
View File
@@ -41,7 +41,8 @@ defmodule Module.Types.Behaviour do
end
defp warn(context, warning, meta \\ []) do
location = {meta[:file] || context.file, meta[:line] || context.line, context.module}
meta = Keyword.put_new(meta, :line, context.line)
location = {meta[:file] || context.file, meta, context.module}
update_in(context.warnings, &[{__MODULE__, warning, location} | &1])
end
@@ -68,7 +69,7 @@ defmodule Module.Types.Behaviour do
context =
case context.callbacks do
%{^callback => {_kind, conflict, _optional?}} ->
%{^callback => [{_kind, conflict, _optional?} | _]} ->
warn(
context,
{:duplicate_behaviour, context.module, behaviour, conflict, kind, callback}
@@ -78,11 +79,15 @@ defmodule Module.Types.Behaviour do
context
end
put_in(context.callbacks[callback], {kind, behaviour, original in optional_callbacks})
new_callback = {kind, behaviour, original in optional_callbacks}
callbacks = context.callbacks[callback] || []
put_in(context.callbacks[callback], [new_callback | callbacks])
end
defp check_callbacks(context, all_definitions) do
for {callback, {kind, behaviour, optional?}} <- context.callbacks,
for {callback, callbacks} <- context.callbacks,
{kind, behaviour, optional?} <- callbacks,
reduce: context do
context ->
case :lists.keyfind(callback, 1, all_definitions) do
@@ -132,7 +137,7 @@ defmodule Module.Types.Behaviour do
{:error, message} ->
warning = message |> Tuple.insert_at(1, kind) |> Tuple.insert_at(1, fa)
context = warn(context, warning, %{line: line, file: file})
context = warn(context, warning, line: line, file: file)
{context, impl_contexts}
end
end)
@@ -185,10 +190,10 @@ defmodule Module.Types.Behaviour do
end
defp behaviour_callbacks_for_impls([fa | tail], behaviour, callbacks) do
case callbacks[fa] do
{_, ^behaviour, _} ->
[{fa, behaviour} | behaviour_callbacks_for_impls(tail, behaviour, callbacks)]
with list when is_list(list) <- callbacks[fa],
true <- Enum.any?(list, &match?({_, ^behaviour, _}, &1)) do
[{fa, behaviour} | behaviour_callbacks_for_impls(tail, behaviour, callbacks)]
else
_ ->
behaviour_callbacks_for_impls(tail, behaviour, callbacks)
end
@@ -200,8 +205,12 @@ defmodule Module.Types.Behaviour do
defp callbacks_for_impls([fa | tail], callbacks) do
case callbacks[fa] do
{_, behaviour, _} -> [{fa, behaviour} | callbacks_for_impls(tail, callbacks)]
nil -> callbacks_for_impls(tail, callbacks)
list when is_list(list) ->
Enum.map(list, fn {_, behaviour, _} -> {fa, behaviour} end) ++
callbacks_for_impls(tail, callbacks)
nil ->
callbacks_for_impls(tail, callbacks)
end
end
@@ -213,11 +222,14 @@ defmodule Module.Types.Behaviour do
defp warn_missing_impls(context, impl_contexts, defs) do
for {pair, kind, meta, _clauses} <- defs, kind in [:def, :defmacro], reduce: context do
context ->
with {:ok, {_, behaviour, _}} <- Map.fetch(context.callbacks, pair),
true <- missing_impl_in_context?(meta, behaviour, impl_contexts) do
warn(context, {:missing_impl, pair, kind, behaviour}, %{
with {:ok, callbacks} <- Map.fetch(context.callbacks, pair),
{_, behaviour, _} <-
Enum.find(callbacks, fn {_, behaviour, _} ->
missing_impl_in_context?(meta, behaviour, impl_contexts)
end) do
warn(context, {:missing_impl, pair, kind, behaviour},
line: :elixir_utils.get_line(meta)
})
)
else
_ -> context
end
@@ -247,7 +259,7 @@ defmodule Module.Types.Behaviour do
defp known_callbacks(callbacks) do
formatted_callbacks =
for {{name, arity}, {kind, module, _}} <- callbacks do
for {{name, arity}, list} <- callbacks, {kind, module, _} <- list do
"\n * " <> Exception.format_mfa(module, name, arity) <> " (#{format_definition(kind)})"
end
@@ -289,7 +301,7 @@ defmodule Module.Types.Behaviour do
def format_warning({:duplicate_behaviour, module, behaviour, conflict, kind, callback})
when conflict == behaviour do
[
"the behavior ",
"the behaviour ",
inspect(behaviour),
" has been declared twice (conflict in ",
format_definition(kind, callback),
@@ -301,9 +313,9 @@ defmodule Module.Types.Behaviour do
def format_warning({:duplicate_behaviour, module, behaviour, conflict, kind, callback}) do
[
"conflicting behaviours found. ",
"conflicting behaviours found. Callback ",
format_definition(kind, callback),
" is required by ",
" is defined by both ",
inspect(conflict),
" and ",
inspect(behaviour),
+3 -3
View File
@@ -391,10 +391,10 @@ defmodule Module.Types.Expr do
end
# expr.fun(arg)
def of_expr({{:., meta1, [expr1, fun]}, _meta2, args} = expr2, _expected, stack, context) do
def of_expr({{:., _meta1, [expr1, fun]}, meta2, args} = expr2, _expected, stack, context) do
# TODO: Use expected type to infer intersection return type
context = Of.remote(expr1, fun, length(args), meta1, context)
context = Of.remote(expr1, fun, length(args), meta2, context)
stack = push_expr_stack(expr2, stack)
with {:ok, _expr_type, context} <- of_expr(expr1, :dynamic, stack, context),
@@ -407,7 +407,7 @@ defmodule Module.Types.Expr do
# &Foo.bar/1
def of_expr(
{:&, meta, [{:/, _, [{{:., _, [module, fun]}, _, []}, arity]}]},
{:&, _, [{:/, _, [{{:., _, [module, fun]}, meta, []}, arity]}]},
_expected,
_stack,
context
+3 -3
View File
@@ -284,11 +284,11 @@ defmodule Module.Types.Of do
defp check_deprecated(:erlang, module, fun, arity, _reason, meta, context) do
case :otp_internal.obsolete(module, fun, arity) do
{:deprecated, string} when is_list(string) ->
reason = string |> List.to_string() |> String.capitalize()
reason = string |> List.to_string() |> :string.titlecase()
warn(meta, context, {:deprecated, module, fun, arity, reason})
{:deprecated, string, removal} when is_list(string) and is_list(removal) ->
reason = string |> List.to_string() |> String.capitalize()
reason = string |> List.to_string() |> :string.titlecase()
reason = "It will be removed in #{removal}. #{reason}"
warn(meta, context, {:deprecated, module, fun, arity, reason})
@@ -323,7 +323,7 @@ defmodule Module.Types.Of do
defp warn(meta, context, warning) do
{fun, arity} = context.function
location = {context.file, meta[:line] || 0, {context.module, fun, arity}}
location = {context.file, meta, {context.module, fun, arity}}
%{context | warnings: [{__MODULE__, warning, location} | context.warnings]}
end
+1 -1
View File
@@ -531,7 +531,7 @@ defmodule Module.Types.Unify do
end
@doc """
Returns true if it is a singleton type.
Returns `true` if it is a singleton type.
Only atoms are singleton types. Unbound vars are not
considered singleton types.
+7 -1
View File
@@ -38,6 +38,12 @@ defmodule OptionParser do
]
defmodule ParseError do
@moduledoc """
An exception raised when parsing option fails.
For example, see `OptionParser.parse!/2`.
"""
defexception [:message]
end
@@ -128,7 +134,7 @@ defmodule OptionParser do
Switches can be specified with modifiers, which change how
they behave. The following modifiers are supported:
* `:keep` - keeps duplicated elements instead of overriding them;
* `:keep` - keeps duplicate elements instead of overriding them;
works with all types except `:count`. Specifying `switch_name: :keep`
assumes the type of `:switch_name` will be `:string`.
+7 -2
View File
@@ -13,6 +13,8 @@ defmodule PartitionSupervisor do
`name` is the name of the `PartitionSupervisor` and key is used
for routing.
This module was introduced in Elixir v1.14.0.
## Simple Example
Let's start with an example which is not useful per se, but shows how the
@@ -29,7 +31,7 @@ defmodule PartitionSupervisor do
end
def init(args) do
IO.inspect [__MODULE__, " got args ", args, " in ", self()]
IO.inspect([__MODULE__, " got args ", args, " in ", self()])
{:ok, _initial_state = []}
end
@@ -39,7 +41,7 @@ defmodule PartitionSupervisor do
def handle_call({:collect, msg}, _from, state) do
new_state = [msg | state]
IO.inspect ["current messages:", new_state, " in process", self()]
IO.inspect(["current messages:", new_state, " in process", self()])
{:reply, :ok, new_state}
end
end
@@ -134,6 +136,8 @@ defmodule PartitionSupervisor do
you can use `GenServer.whereis({:via, PartitionSupervisor, {name, key}})`.
"""
@moduledoc since: "1.14.0"
@behaviour Supervisor
@registry PartitionSupervisor.Registry
@@ -141,6 +145,7 @@ defmodule PartitionSupervisor do
@typedoc """
The name of the `PartitionSupervisor`.
"""
@typedoc since: "1.14.0"
@type name :: atom() | {:via, module(), term()}
@doc false
+199 -101
View File
@@ -44,17 +44,17 @@ defmodule Path do
"""
@spec absname(t) :: binary
def absname(path) do
absname(path, File.cwd!())
absname(path, &File.cwd!/0)
end
@doc """
Builds a path from `relative_to` to `path`.
If `path` is already an absolute path, `relative_to` is ignored. See also
`relative_to/2`.
`relative_to/3`. `relative_to` is either a path or an anonymous function,
which is invoked only when necessary, that returns a path.
Unlike `expand/2`, no attempt is made to
resolve `..`, `.` or `~`.
Unlike `expand/2`, no attempt is made to resolve `..`, `.` or `~`.
## Examples
@@ -65,19 +65,33 @@ defmodule Path do
"bar/../x"
"""
@spec absname(t, t) :: binary
@spec absname(t, t | (-> t)) :: binary
def absname(path, relative_to) do
path = IO.chardata_to_string(path)
case type(path) do
:relative ->
relative_to =
if is_function(relative_to, 0) do
relative_to.()
else
relative_to
end
absname_join([relative_to, path])
:absolute ->
absname_join([path])
:volumerelative ->
relative_to = IO.chardata_to_string(relative_to)
relative_to =
if is_function(relative_to, 0) do
relative_to.()
else
relative_to
end
|> IO.chardata_to_string()
absname_vr(split(path), split(relative_to), relative_to)
end
end
@@ -163,7 +177,7 @@ defmodule Path do
"""
@spec expand(t) :: binary
def expand(path) do
expand_dot(absname(expand_home(path), File.cwd!()))
expand_dot(absname(expand_home(path), &File.cwd!/0))
end
@doc """
@@ -192,7 +206,7 @@ defmodule Path do
"""
@spec expand(t, t) :: binary
def expand(path, relative_to) do
expand_dot(absname(absname(expand_home(path), expand_home(relative_to)), File.cwd!()))
expand_dot(absname(absname(expand_home(path), expand_home(relative_to)), &File.cwd!/0))
end
@doc """
@@ -226,6 +240,8 @@ defmodule Path do
@doc """
Forces the path to be a relative path.
If an absolute path is given, it is stripped from its root component.
## Examples
### Unix-like operating systems
@@ -242,6 +258,12 @@ defmodule Path do
Path.relative("/bar/foo.ex") #=> "bar/foo.ex"
"""
# Note this function does not expand paths because the behaviour
# is ambiguous. If we expand it before converting to relative, then
# "/usr/../../foo" means "/foo". If we expand it after, it means "../foo".
# We could expand only relative paths but it is best to say it never
# expands and then provide a `Path.expand_relative` function (or an
# option) if desired.
@spec relative(t) :: binary
def relative(name) do
relative(name, major_os_type())
@@ -297,52 +319,143 @@ defmodule Path do
defp win32_pathtype(relative), do: {:relative, relative}
@doc """
Returns the direct relative path from `path` in relation to `from`.
Returns the direct relative path from `path` in relation to `cwd`.
In other words, this function tries to strip the `from` prefix from `path`.
In other words, this function attempts to return a path such that
`Path.expand(result, cwd)` points to `path`. This function aims
to return a relative path whenever possible, but that's not guaranteed:
This function does not query the file system, so it assumes
no symlinks between the paths.
* If both paths are relative, a relative path is always returned
In case a direct relative path cannot be found, it returns
the original path.
* If both paths are absolute, a relative path may be returned if
they share a common prefix. You can pass the `:force` option to
force this function to traverse up, but even then a relative
path is not guaranteed (for example, if the absolute paths
belong to different drives on Windows)
* If a mixture of paths are given, the result will always match
the given `path` (the first argument)
This function expands `.` and `..` entries without traversing the
file system, so it assumes no symlinks between the paths. See
`safe_relative_to/2` for a safer alternative.
## Options
* `:force` - (boolean since v1.16.0) if `true` forces a relative
path to be returned by traversing the path up. Except if the paths
are in different volumes on Windows. Defaults to `false`.
## Examples
iex> Path.relative_to("/usr/local/foo", "/usr/local")
"foo"
### With relative `cwd`
iex> Path.relative_to("/usr/local/foo", "/")
"usr/local/foo"
If both paths are relative, a minimum path is computed:
iex> Path.relative_to("/usr/local/foo", "/etc")
"/usr/local/foo"
Path.relative_to("tmp/foo/bar", "tmp") #=> "foo/bar"
Path.relative_to("tmp/foo/bar", "tmp/foo") #=> "bar"
Path.relative_to("tmp/foo/bar", "tmp/bat") #=> "../foo/bar"
iex> Path.relative_to("/usr/local/foo", "/usr/local/foo")
"."
If an absolute path is given with relative `cwd`, it is returned as:
Path.relative_to("/usr/foo/bar", "tmp/bat") #=> "/usr/foo/bar"
### With absolute `cwd`
If both paths are absolute, a relative is computed if possible,
without traversing up:
Path.relative_to("/usr/local/foo", "/usr/local") #=> "foo"
Path.relative_to("/usr/local/foo", "/") #=> "usr/local/foo"
Path.relative_to("/usr/local/foo", "/etc") #=> "/usr/local/foo"
Path.relative_to("/usr/local/foo", "/usr/local/foo") #=> "."
Path.relative_to("/usr/local/../foo", "/usr/foo") #=> "."
Path.relative_to("/usr/local/../foo/bar", "/usr/foo") #=> "bar"
If `:force` is set to `true` paths are traversed up:
Path.relative_to("/usr", "/usr/local", force: true) #=> ".."
Path.relative_to("/usr/foo", "/usr/local", force: true) #=> "../foo"
Path.relative_to("/usr/../foo/bar", "/etc/foo", force: true) #=> "../../foo/bar"
If a relative path is given, it is assumed to be relative to the
given path, so the path is returned with "." and ".." expanded:
Path.relative_to(".", "/usr/local") #=> "."
Path.relative_to("foo", "/usr/local") #=> "foo"
Path.relative_to("foo/../bar", "/usr/local") #=> "bar"
Path.relative_to("foo/..", "/usr/local") #=> "."
Path.relative_to("../foo", "/usr/local") #=> "../foo"
"""
@spec relative_to(t, t) :: binary
def relative_to(path, from) do
path = IO.chardata_to_string(path)
relative_to(split(path), split(from), path)
@spec relative_to(t, t, keyword) :: binary
def relative_to(path, cwd, opts \\ []) when is_list(opts) do
os_type = major_os_type()
split_path = split(path)
split_cwd = split(cwd)
force = Keyword.get(opts, :force, false)
case {split_absolute?(split_path, os_type), split_absolute?(split_cwd, os_type)} do
{true, true} ->
split_path = expand_split(split_path)
split_cwd = expand_split(split_cwd)
case force do
true -> relative_to_forced(split_path, split_cwd, split_path)
false -> relative_to_unforced(split_path, split_cwd, split_path)
end
{false, false} ->
split_path = expand_relative(split_path, [], [])
split_cwd = expand_relative(split_cwd, [], [])
relative_to_forced(split_path, split_cwd, [])
{_, _} ->
join(expand_relative(split_path, [], []))
end
end
defp relative_to(path, path, _original) do
"."
defp relative_to_unforced(path, path, _original), do: "."
defp relative_to_unforced([h | t1], [h | t2], original),
do: relative_to_unforced(t1, t2, original)
defp relative_to_unforced([_ | _] = l1, [], _original), do: join(l1)
defp relative_to_unforced(_, _, original), do: join(original)
defp relative_to_forced(path, path, _original), do: "."
defp relative_to_forced(["."], _path, _original), do: "."
defp relative_to_forced(path, ["."], _original), do: join(path)
defp relative_to_forced([h | t1], [h | t2], original), do: relative_to_forced(t1, t2, original)
# this should only happen if we have two paths on different drives on windows
defp relative_to_forced(original, _, original), do: join(original)
defp relative_to_forced(l1, l2, _original) do
base = List.duplicate("..", length(l2))
join(base ++ l1)
end
defp relative_to([h | t1], [h | t2], original) do
relative_to(t1, t2, original)
end
defp expand_relative([".." | t], [_ | acc], up), do: expand_relative(t, acc, up)
defp expand_relative([".." | t], acc, up), do: expand_relative(t, acc, [".." | up])
defp expand_relative(["." | t], acc, up), do: expand_relative(t, acc, up)
defp expand_relative([h | t], acc, up), do: expand_relative(t, [h | acc], up)
defp expand_relative([], [], []), do: ["."]
defp expand_relative([], acc, up), do: up ++ :lists.reverse(acc)
defp relative_to([_ | _] = l1, [], _original) do
join(l1)
end
defp expand_split([head | tail]), do: expand_split(tail, [head])
defp expand_split([".." | t], [_, last | acc]), do: expand_split(t, [last | acc])
defp expand_split([".." | t], acc), do: expand_split(t, acc)
defp expand_split(["." | t], acc), do: expand_split(t, acc)
defp expand_split([h | t], acc), do: expand_split(t, [h | acc])
defp expand_split([], acc), do: :lists.reverse(acc)
defp relative_to(_, _, original) do
original
end
defp split_absolute?(split, :win32), do: win32_split_absolute?(split)
defp split_absolute?(split, _), do: match?(["/" | _], split)
defp win32_split_absolute?(["//" | _]), do: true
defp win32_split_absolute?([<<_, ":/">> | _]), do: true
defp win32_split_absolute?(_), do: false
@doc """
Convenience to get the path relative to the current working
@@ -350,11 +463,13 @@ defmodule Path do
If, for some reason, the current working directory
cannot be retrieved, this function returns the given `path`.
Check `relative_to/3` for the supported options.
"""
@spec relative_to_cwd(t) :: binary
def relative_to_cwd(path) do
@spec relative_to_cwd(t, keyword) :: binary
def relative_to_cwd(path, opts \\ []) when is_list(opts) do
case :file.get_cwd() do
{:ok, base} -> relative_to(path, IO.chardata_to_string(base))
{:ok, base} -> relative_to(path, IO.chardata_to_string(base), opts)
_ -> path
end
end
@@ -614,7 +729,7 @@ defmodule Path do
end
end
@doc """
@doc ~S"""
Traverses paths according to the given `glob` expression and returns a
list of matches.
@@ -646,9 +761,9 @@ defmodule Path do
You may call `Path.expand/1` to normalize the path before invoking
this function.
A character preceded by \ loses its special meaning.
Note that \ must be written as \\ in a string literal.
For example, "\\?*" will match any filename starting with ?.
A character preceded by `\\` loses its special meaning.
Note that `\\` must be written as `\\\\` in a string literal.
For example, `"\\\\?*"` will match any filename starting with `?.`.
By default, the patterns `*` and `?` do not match files starting
with a dot `.`. See the `:match_dot` option in the "Options" section
@@ -718,21 +833,18 @@ defmodule Path do
end
end
# expand_dot the given path by expanding "..", "." and "~".
defp expand_dot(<<"/", rest::binary>>), do: "/" <> do_expand_dot(rest)
# expands dots in an absolute path represented as a string
defp expand_dot(path) do
[head | tail] = :binary.split(path, "/", [:global])
IO.iodata_to_binary(expand_dot(tail, [head <> "/"]))
end
defp expand_dot(<<letter, ":/", rest::binary>>) when letter in ?a..?z,
do: <<letter, ":/">> <> do_expand_dot(rest)
defp expand_dot(path), do: do_expand_dot(path)
defp do_expand_dot(path), do: do_expand_dot(:binary.split(path, "/", [:global]), [])
defp do_expand_dot([".." | t], [_, _ | acc]), do: do_expand_dot(t, acc)
defp do_expand_dot([".." | t], []), do: do_expand_dot(t, [])
defp do_expand_dot(["." | t], acc), do: do_expand_dot(t, acc)
defp do_expand_dot([h | t], acc), do: do_expand_dot(t, ["/", h | acc])
defp do_expand_dot([], []), do: ""
defp do_expand_dot([], ["/" | acc]), do: IO.iodata_to_binary(:lists.reverse(acc))
defp expand_dot([".." | t], [_, _ | acc]), do: expand_dot(t, acc)
defp expand_dot([".." | t], acc), do: expand_dot(t, acc)
defp expand_dot(["." | t], acc), do: expand_dot(t, acc)
defp expand_dot([h | t], acc), do: expand_dot(t, ["/", h | acc])
defp expand_dot([], ["/", head | acc]), do: :lists.reverse([head | acc])
defp expand_dot([], acc), do: :lists.reverse(acc)
defp major_os_type do
:os.type() |> elem(0)
@@ -741,6 +853,18 @@ defmodule Path do
@doc """
Returns a relative path that is protected from directory-traversal attacks.
See `safe_relative/2` for a non-deprecated version of this API.
"""
# TODO: Deprecate me on Elixir v1.19
@doc since: "1.14.0", deprecated: "Use safe_relative/2 instead"
@spec safe_relative_to(t, t) :: {:ok, binary} | :error
def safe_relative_to(path, cwd) do
safe_relative(path, cwd)
end
@doc """
Returns a relative path that is protected from directory-traversal attacks.
The given relative path is sanitized by eliminating `..` and `.` components.
This function checks that, after expanding those components, the path is still "safe".
@@ -748,63 +872,37 @@ defmodule Path do
* The path is not relative, such as `"/foo/bar"`.
* A `..` component would make it so that the path would travers up above
* A `..` component would make it so that the path would traverse up above
the root of `relative_to`.
* A symbolic link in the path points to something above the root of `relative_to`.
## Examples
iex> Path.safe_relative_to("deps/my_dep/app.beam", "deps")
{:ok, "deps/my_dep/app.beam"}
iex> Path.safe_relative_to("deps/my_dep/./build/../app.beam", "deps")
{:ok, "deps/my_dep/app.beam"}
iex> Path.safe_relative_to("my_dep/../..", "deps")
:error
iex> Path.safe_relative_to("/usr/local", ".")
:error
"""
@doc since: "1.14.0"
@spec safe_relative_to(t, t) :: {:ok, binary} | :error
def safe_relative_to(path, relative_to) do
path = IO.chardata_to_string(path)
case :filelib.safe_relative_path(path, relative_to) do
:unsafe -> :error
relative_path -> {:ok, IO.chardata_to_string(relative_path)}
end
end
@doc """
Returns a path relative to the current working directory that is
protected from directory-traversal attacks.
Same as `safe_relative_to/2` with the current working directory as
the second argument. If there is an issue retrieving the current working
directory, this function raises an error.
* A symbolic link in the path points to something above the root of `cwd`.
## Examples
iex> Path.safe_relative("foo")
{:ok, "foo"}
iex> Path.safe_relative("foo/../bar")
{:ok, "bar"}
iex> Path.safe_relative("deps/my_dep/app.beam")
{:ok, "deps/my_dep/app.beam"}
iex> Path.safe_relative("foo/../..")
iex> Path.safe_relative("deps/my_dep/./build/../app.beam", File.cwd!())
{:ok, "deps/my_dep/app.beam"}
iex> Path.safe_relative("my_dep/../..")
:error
iex> Path.safe_relative("/usr/local")
iex> Path.safe_relative("/usr/local", File.cwd!())
:error
"""
@doc since: "1.14.0"
@spec safe_relative(t) :: {:ok, binary} | :error
def safe_relative(path) do
safe_relative_to(path, File.cwd!())
@spec safe_relative(t, t) :: {:ok, binary} | :error
def safe_relative(path, cwd \\ File.cwd!()) do
path = IO.chardata_to_string(path)
case :filelib.safe_relative_path(path, cwd) do
:unsafe -> :error
relative_path -> {:ok, IO.chardata_to_string(relative_path)}
end
end
end
+21
View File
@@ -79,6 +79,27 @@ defmodule Port do
are for advanced usage within the VM. Also consider using `System.cmd/3`
if all you want is to execute a program and retrieve its return value.
> #### Windows argument splitting and untrusted arguments {: .warning}
>
> On Unix systems, arguments are passed to a new operating system
> process as an array of strings but on Windows it is up to the child
> process to parse them and some Windows programs may apply their own
> rules, which are inconsistent with the standard C runtime `argv` parsing
>
> This is particularly troublesome when invoking `.bat` or `.com` files
> as these run implicitly through `cmd.exe`, whose argument parsing is
> vulnerable to malicious input and can be used to run arbitrary shell
> commands.
>
> Therefore, if you are running on Windows and you execute batch
> files or `.com` applications, you must not pass untrusted input as
> arguments to the program. You may avoid accidentally executing them
> by explicitly passing the extension of the program you want to run,
> such as `.exe`, and double check the program is indeed not a batch
> file or `.com` application.
>
> This affects both `spawn` and `spawn_executable`.
### spawn
The `:spawn` tuple receives a binary that is going to be executed as a
+2 -2
View File
@@ -504,7 +504,7 @@ defmodule Process do
If the process is already dead when calling `Process.monitor/1`, a
`:DOWN` message is delivered immediately.
See ["The need for monitoring"](https://elixir-lang.org/getting-started/mix-otp/genserver.html#the-need-for-monitoring)
See ["The need for monitoring"](genservers.md#the-need-for-monitoring)
for an example. See `:erlang.monitor/2` for more information.
Inlined by the compiler.
@@ -651,7 +651,7 @@ defmodule Process do
defdelegate unlink(pid_or_port), to: :erlang
@doc """
Registers the given `pid_or_port` under the given `name`.
Registers the given `pid_or_port` under the given `name` on the local node.
`name` must be an atom and can then be used instead of the
PID/port identifier when sending messages with `Kernel.send/2`.
+24 -22
View File
@@ -448,7 +448,7 @@ defmodule Protocol do
## Examples
# Get Elixir's ebin directory path and retrieve all protocols
iex> path = :code.lib_dir(:elixir, :ebin)
iex> path = Application.app_dir(:elixir, "ebin")
iex> mods = Protocol.extract_protocols([path])
iex> Enumerable in mods
true
@@ -477,7 +477,7 @@ defmodule Protocol do
## Examples
# Get Elixir's ebin directory path and retrieve all protocols
iex> path = :code.lib_dir(:elixir, :ebin)
iex> path = Application.app_dir(:elixir, "ebin")
iex> mods = Protocol.extract_impls(Enumerable, [path])
iex> List in mods
true
@@ -684,7 +684,7 @@ defmodule Protocol do
end
defp load_impl(protocol, for) do
Module.concat(protocol, for).__impl__(:target)
Module.concat(protocol, for)
end
# Finally compile the module and emit its bytecode.
@@ -826,7 +826,7 @@ defmodule Protocol do
quote bind_quoted: [built_in: __built_in__()] do
any_impl_for =
if @fallback_to_any do
quote do: unquote(__MODULE__.Any).__impl__(:target)
quote do: unquote(__MODULE__.Any)
else
nil
end
@@ -853,11 +853,9 @@ defmodule Protocol do
target = Module.concat(__MODULE__, mod)
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
try do
unquote(target).__impl__(:target)
rescue
UndefinedFunctionError ->
unquote(any_impl_for)
case Code.ensure_compiled(unquote(target)) do
{:module, module} -> module
{:error, _} -> unquote(any_impl_for)
end
end
end,
@@ -888,14 +886,11 @@ defmodule Protocol do
# Internal handler for Structs
Kernel.defp struct_impl_for(struct) do
target = Module.concat(__MODULE__, struct)
try do
target.__impl__(:target)
rescue
UndefinedFunctionError ->
unquote(any_impl_for)
end
target =
case Code.ensure_compiled(Module.concat(__MODULE__, struct)) do
{:module, module} -> module
{:error, _} -> unquote(any_impl_for)
end
end
# Inline struct implementation for performance
@@ -962,10 +957,8 @@ defmodule Protocol do
impl =
quote unquote: false do
@doc false
@spec __impl__(:target) :: __MODULE__
@spec __impl__(:for) :: unquote(for)
@spec __impl__(:protocol) :: unquote(protocol)
def __impl__(:target), do: __MODULE__
def __impl__(:for), do: unquote(for)
def __impl__(:protocol), do: unquote(protocol)
end
@@ -1025,21 +1018,30 @@ defmodule Protocol do
if function_exported?(mod, fun, length(args)) do
apply(mod, fun, args)
else
funs =
for {fun, arity} <- protocol.__protocol__(:functions) do
args = Macro.generate_arguments(arity, nil)
quote do
def unquote(fun)(unquote_splicing(args)),
do: unquote(impl).unquote(fun)(unquote_splicing(args))
end
end
quoted =
quote do
@behaviour unquote(protocol)
Module.register_attribute(__MODULE__, :__impl__, persist: true)
@__impl__ [protocol: unquote(protocol), for: unquote(for)]
@doc false
@spec __impl__(:target) :: unquote(impl)
@spec __impl__(:protocol) :: unquote(protocol)
@spec __impl__(:for) :: unquote(for)
def __impl__(:target), do: unquote(impl)
def __impl__(:protocol), do: unquote(protocol)
def __impl__(:for), do: unquote(for)
end
Module.create(Module.concat(protocol, for), quoted, Macro.Env.location(env))
Module.create(Module.concat(protocol, for), [quoted | funs], Macro.Env.location(env))
end
end)
end
+12
View File
@@ -244,6 +244,7 @@ defmodule Range do
"""
@doc since: "1.12.0"
@spec size(t) :: non_neg_integer
def size(range)
def size(first..last//step) when step > 0 and first > last, do: 0
def size(first..last//step) when step < 0 and first < last, do: 0
@@ -272,6 +273,7 @@ defmodule Range do
"""
@doc since: "1.14.0"
@spec shift(t, integer) :: t
def shift(first..last//step, steps_to_shift)
when is_integer(steps_to_shift) do
new(first + steps_to_shift * step, last + steps_to_shift * step, step)
@@ -363,6 +365,7 @@ defmodule Range do
"""
@doc since: "1.15.0"
@spec split(t, integer) :: {t, t}
def split(first..last//step = range, split) when is_integer(split) do
if split >= 0 do
split(first, last, step, split)
@@ -391,8 +394,17 @@ defmodule Range do
@doc """
Converts a range to a list.
## Examples
iex> Range.to_list(0..5)
[0, 1, 2, 3, 4, 5]
iex> Range.to_list(-3..0)
[-3, -2, -1, 0]
"""
@doc since: "1.15.0"
@spec to_list(t) :: list(integer)
def to_list(first..last//step)
when step > 0 and first <= last
when step < 0 and first >= last do
+39 -16
View File
@@ -7,7 +7,7 @@ defmodule Regex do
in the [`:re` module documentation](`:re`).
Regular expressions in Elixir can be created using the sigils
`~r` (see `sigil_r/2`) or `~R` (see `sigil_R/2`):
`~r` (see `sigil_r/2`):
# A simple regular expression that matches foo anywhere in the string
~r/foo/
@@ -34,6 +34,37 @@ defmodule Regex do
~r/(?<foo>.)(?<bar>.)/.source == ~r/(?<foo>.)(?<bar>.)/.source
## Escapes
Escape sequences are split into two categories.
### Non-printing characters
* `\a` - Alarm, that is, the BEL character (hex 07)
* `\e` - Escape (hex 1B)
* `\f` - Form feed (hex 0C)
* `\n` - Line feed (hex 0A)
* `\r` - Carriage return (hex 0D)
* `\t` - Tab (hex 09)
* `\xhh` - Character with hex code hh
* `\x{hhh..}` - Character with hex code hhh..
`\u` and `\U` are not supported. Other escape sequences, such as `\ddd`
for octals, are supported but discouraged.
### Generic character types
* `\d` - Any decimal digit
* `\D` - Any character that is not a decimal digit
* `\h` - Any horizontal whitespace character
* `\H` - Any character that is not a horizontal whitespace character
* `\s` - Any whitespace character
* `\S` - Any character that is not a whitespace character
* `\v` - Any vertical whitespace character
* `\V` - Any character that is not a vertical whitespace character
* `\w` - Any "word" character
* `\W` - Any "non-word" character
## Modifiers
The modifiers available when creating a Regex are:
@@ -158,6 +189,10 @@ defmodule Regex do
@type t :: %__MODULE__{re_pattern: term, source: binary, opts: binary | [term]}
defmodule CompileError do
@moduledoc """
An exception raised when a regular expression could not be compiled.
"""
defexception message: "regex could not be compiled"
end
@@ -295,7 +330,7 @@ defmodule Regex do
end
@doc false
@deprecated "Use Kernel.is_struct/2 or pattern match on %Regex{} instead"
@deprecated "Use Kernel.is_struct(term, Regex) or pattern match on %Regex{} instead"
def regex?(term)
def regex?(%Regex{}), do: true
def regex?(_), do: false
@@ -512,7 +547,8 @@ defmodule Regex do
* `:on` - specifies which captures to split the string on, and in what
order. Defaults to `:first` which means captures inside the regex do not
affect the splitting process.
affect the splitting process. Check the moduledoc for `Regex`
to see the possible capture values.
* `:include_captures` - when `true`, includes in the result the matches of
the regular expression. The matches are not counted towards the maximum
@@ -847,19 +883,6 @@ defmodule Regex do
# Helpers
@doc false
# Unescape map function used by Macro.unescape_string.
def unescape_map(:newline), do: true
def unescape_map(?f), do: ?\f
def unescape_map(?n), do: ?\n
def unescape_map(?r), do: ?\r
def unescape_map(?t), do: ?\t
def unescape_map(?v), do: ?\v
def unescape_map(?a), do: ?\a
def unescape_map(_), do: false
# Private Helpers
defp translate_options(<<?u, t::binary>>, acc), do: translate_options(t, [:unicode, :ucp | acc])
defp translate_options(<<?i, t::binary>>, acc), do: translate_options(t, [:caseless | acc])
defp translate_options(<<?x, t::binary>>, acc), do: translate_options(t, [:extended | acc])
+12 -12
View File
@@ -27,8 +27,8 @@ defmodule Registry do
`Registry.start_link/1`, it can be used to register and access named
processes using the `{:via, Registry, {registry, key}}` tuple:
{:ok, _} = Registry.start_link(keys: :unique, name: Registry.ViaTest)
name = {:via, Registry, {Registry.ViaTest, "agent"}}
{:ok, _} = Registry.start_link(keys: :unique, name: MyApp.Registry)
name = {:via, Registry, {MyApp.Registry, "agent"}}
{:ok, _} = Agent.start_link(fn -> 0 end, name: name)
Agent.get(name, & &1)
#=> 0
@@ -39,22 +39,22 @@ defmodule Registry do
In the previous example, we were not interested in associating a value to the
process:
Registry.lookup(Registry.ViaTest, "agent")
Registry.lookup(MyApp.Registry, "agent")
#=> [{self(), nil}]
However, in some cases it may be desired to associate a value to the process
using the alternate `{:via, Registry, {registry, key, value}}` tuple:
{:ok, _} = Registry.start_link(keys: :unique, name: Registry.ViaTest)
name = {:via, Registry, {Registry.ViaTest, "agent", :hello}}
{:ok, _} = Registry.start_link(keys: :unique, name: MyApp.Registry)
name = {:via, Registry, {MyApp.Registry, "agent", :hello}}
{:ok, agent_pid} = Agent.start_link(fn -> 0 end, name: name)
Registry.lookup(Registry.ViaTest, "agent")
Registry.lookup(MyApp.Registry, "agent")
#=> [{agent_pid, :hello}]
To this point, we have been starting `Registry` using `start_link/1`.
Typically the registry is started as part of a supervision tree though:
{Registry, keys: :unique, name: Registry.ViaTest}
{Registry, keys: :unique, name: MyApp.Registry}
Only registries with unique keys can be used in `:via`. If the name is
already taken, the case-specific `start_link` function (`Agent.start_link/2`
@@ -1283,7 +1283,7 @@ defmodule Registry do
variables like `:"$1"`, `:"$2"`, and so forth.
The third part, the body, is a list of shapes of the returned entries. Like guards, you have access to
assigned variables like `:"$1"`, which you can combine with hardcoded values to freely shape entries
assigned variables like `:"$1"`, which you can combine with hard-coded values to freely shape entries
Note that tuples have to be wrapped in an additional tuple. To get a result format like
`%{key: key, pid: pid, value: value}`, assuming you bound those variables in order in the match part,
you would provide a body like `[%{key: :"$1", pid: :"$2", value: :"$3"}]`. Like guards, you can use
@@ -1301,16 +1301,16 @@ defmodule Registry do
iex> Registry.start_link(keys: :unique, name: Registry.SelectAllTest)
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "hello", :value)
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "world", :value)
iex> Registry.select(Registry.SelectAllTest, [{{:"$1", :"$2", :"$3"}, [], [{{:"$1", :"$2", :"$3"}}]}])
[{"world", self(), :value}, {"hello", self(), :value}]
iex> Registry.select(Registry.SelectAllTest, [{{:"$1", :"$2", :"$3"}, [], [{{:"$1", :"$2", :"$3"}}]}]) |> Enum.sort()
[{"hello", self(), :value}, {"world", self(), :value}]
Get all keys in the registry:
iex> Registry.start_link(keys: :unique, name: Registry.SelectAllTest)
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "hello", :value)
iex> {:ok, _} = Registry.register(Registry.SelectAllTest, "world", :value)
iex> Registry.select(Registry.SelectAllTest, [{{:"$1", :_, :_}, [], [:"$1"]}])
["world", "hello"]
iex> Registry.select(Registry.SelectAllTest, [{{:"$1", :_, :_}, [], [:"$1"]}]) |> Enum.sort()
["hello", "world"]
"""
@doc since: "1.9.0"
+136 -11
View File
@@ -18,7 +18,7 @@ defmodule String do
"hello world"
The functions in this module act according to
[The Unicode Standard, Version 15.0.0](http://www.unicode.org/versions/Unicode15.0.0/).
[The Unicode Standard, Version 15.1.0](http://www.unicode.org/versions/Unicode15.1.0/).
## Interpolation
@@ -759,6 +759,7 @@ defmodule String do
"fi"
"""
@spec normalize(t, :nfd | :nfc | :nfkd | :nfkc) :: t
def normalize(string, form)
def normalize(string, :nfd) when is_binary(string) do
@@ -823,6 +824,7 @@ defmodule String do
iex> String.upcase("ıi", :turkic)
"Iİ"
Also see `downcase/2` and `capitalize/2` for other conversions.
"""
@spec upcase(t, :default | :ascii | :greek | :turkic) :: t
def upcase(string, mode \\ :default)
@@ -857,6 +859,8 @@ defmodule String do
lowercases only the letters A to Z. `:greek` includes the context sensitive
mappings found in Greek. `:turkic` properly handles the letter i with the dotless variant.
Also see `upcase/2` and `capitalize/2` for other conversions.
## Examples
iex> String.downcase("ABCD")
@@ -921,19 +925,25 @@ defmodule String do
Converts the first character in the given string to
uppercase and the remainder to lowercase according to `mode`.
`mode` may be `:default`, `:ascii`, `:greek` or `:turkic`. The `:default` mode considers
all non-conditional transformations outlined in the Unicode standard. `:ascii`
capitalizes only the letters A to Z. `:greek` includes the context sensitive
mappings found in Greek. `:turkic` properly handles the letter i with the dotless variant.
`mode` may be `:default`, `:ascii`, `:greek` or `:turkic`. The `:default` mode
considers all non-conditional transformations outlined in the Unicode standard.
`:ascii` capitalizes only the letters A to Z. `:greek` includes the context
sensitive mappings found in Greek. `:turkic` properly handles the letter `i`
with the dotless variant.
Also see `upcase/2` and `capitalize/2` for other conversions. If you want
a variation of this function that does not lowercase the rest of string,
see Erlang's `:string.titlecase/1`.
## Examples
iex> String.capitalize("abcd")
"Abcd"
iex> String.capitalize("ABCD")
"Abcd"
iex> String.capitalize("fin")
"Fin"
iex> String.capitalize("olá")
"Olá"
@@ -946,9 +956,22 @@ defmodule String do
<<char>> <> downcase(rest, :ascii)
end
@letter_I <<0x0049::utf8>>
@letter_i <<0x0069::utf8>>
@letter_I_dot_above <<0x0130::utf8>>
def capitalize(<<@letter_i, right::binary>>, mode) do
if(mode == :turkic, do: @letter_I_dot_above, else: @letter_I) <> downcase(right, mode)
end
def capitalize(string, mode) when is_binary(string) do
{char, rest} = String.Unicode.titlecase_once(string, mode)
char <> downcase(rest, mode)
case :unicode_util.gc(string) do
[gc] -> grapheme_to_binary(:string.titlecase([gc]))
[gc, rest] -> grapheme_to_binary(:string.titlecase([gc])) <> downcase(rest, mode)
[gc | rest] -> grapheme_to_binary(:string.titlecase([gc])) <> downcase(rest, mode)
[] -> ""
{:error, <<byte, rest::bits>>} -> <<byte>> <> downcase(rest, mode)
end
end
@doc false
@@ -1849,6 +1872,104 @@ defmodule String do
end
end
defguardp replace_invalid_ii_of_iii(i, ii)
when Bitwise.bor(Bitwise.bsl(i, 6), ii) in 32..863 or
Bitwise.bor(Bitwise.bsl(i, 6), ii) in 896..1023
defguardp replace_invalid_ii_of_iv(i, ii)
when Bitwise.bor(Bitwise.bsl(i, 6), ii) in 16..271
defguardp replace_invalid_iii_of_iv(i, ii, iii)
when Bitwise.bor(Bitwise.bor(Bitwise.bsl(i, 12), Bitwise.bsl(ii, 6)), iii) in 1024..17407
defguardp replace_invalid_is_next(next) when Bitwise.bsr(next, 6) !== 0b10
@doc ~S"""
Returns a new string created by replacing all invalid bytes with `replacement` (`"�"` by default).
## Examples
iex> String.replace_invalid("asd" <> <<0xFF::8>>)
"asd�"
iex> String.replace_invalid("nem rán bề bề")
"nem rán bề bề"
iex> String.replace_invalid("nem rán b" <> <<225, 187>> <> " bề")
"nem rán b� bề"
iex> String.replace_invalid("nem rán b" <> <<225, 187>> <> " bề", "ERROR!")
"nem rán bERROR! bề"
"""
@doc since: "1.16.0"
def replace_invalid(bytes, replacement \\ "�")
when is_binary(bytes) and is_binary(replacement) do
do_replace_invalid(bytes, replacement, <<>>)
end
# Valid ASCII (for better average speed)
defp do_replace_invalid(<<ascii::8, next::8, _::bytes>> = rest, rep, acc)
when ascii in 0..127 and replace_invalid_is_next(next) do
<<_::8, rest::bytes>> = rest
do_replace_invalid(rest, rep, acc <> <<ascii::8>>)
end
# Valid UTF-8
defp do_replace_invalid(<<grapheme::utf8, rest::bytes>>, rep, acc) do
do_replace_invalid(rest, rep, acc <> <<grapheme::utf8>>)
end
# 2/3 truncated sequence
defp do_replace_invalid(<<0b1110::4, i::4, 0b10::2, ii::6>>, rep, acc)
when replace_invalid_ii_of_iii(i, ii) do
acc <> rep
end
defp do_replace_invalid(<<0b1110::4, i::4, 0b10::2, ii::6, next::8, _::bytes>> = rest, rep, acc)
when replace_invalid_ii_of_iii(i, ii) and replace_invalid_is_next(next) do
<<_::16, rest::bytes>> = rest
do_replace_invalid(rest, rep, acc <> rep)
end
# 2/4
defp do_replace_invalid(<<0b11110::5, i::3, 0b10::2, ii::6>>, rep, acc)
when replace_invalid_ii_of_iv(i, ii) do
acc <> rep
end
defp do_replace_invalid(
<<0b11110::5, i::3, 0b10::2, ii::6, next::8, _::bytes>> = rest,
rep,
acc
)
when replace_invalid_ii_of_iv(i, ii) and replace_invalid_is_next(next) do
<<_::16, rest::bytes>> = rest
do_replace_invalid(rest, rep, acc <> rep)
end
# 3/4
defp do_replace_invalid(<<0b11110::5, i::3, 0b10::2, ii::6, 0b10::2, iii::6>>, rep, acc)
when replace_invalid_iii_of_iv(i, ii, iii) do
acc <> rep
end
defp do_replace_invalid(
<<0b11110::5, i::3, 0b10::2, ii::6, 0b10::2, iii::6, next::8, _::bytes>> = rest,
rep,
acc
)
when replace_invalid_iii_of_iv(i, ii, iii) and replace_invalid_is_next(next) do
<<_::24, rest::bytes>> = rest
do_replace_invalid(rest, rep, acc <> rep)
end
# Everything else
defp do_replace_invalid(<<_, rest::bytes>>, rep, acc),
do: do_replace_invalid(rest, rep, acc <> rep)
# Final
defp do_replace_invalid(<<>>, _, acc), do: acc
@doc ~S"""
Splits the string into chunks of characters that share a common trait.
@@ -2231,7 +2352,7 @@ defmodule String do
If the first position is after the string ends or after
the last position of the range, it returns an empty string:
iex> String.slice("elixir", 10..3)
iex> String.slice("elixir", 10..3//1)
""
iex> String.slice("a", 1..1500)
""
@@ -2239,12 +2360,16 @@ defmodule String do
"""
@spec slice(t, Range.t()) :: t
def slice(string, first..last//step = range) when is_binary(string) do
# TODO: Deprecate negative steps on Elixir v1.16
# TODO: Support negative steps as a reverse on Elixir v2.0.
cond do
step > 0 ->
slice_range(string, first, last, step)
step == -1 and first > last ->
IO.warn(
"negative steps are not supported in String.slice/2, pass #{first}..#{last}//1 instead"
)
slice_range(string, first, last, 1)
true ->
@@ -2936,7 +3061,7 @@ defmodule String do
defp codepoint_byte_size(_), do: 4
defp grapheme_to_binary(cp) when is_integer(cp), do: <<cp::utf8>>
defp grapheme_to_binary(gc), do: :unicode.characters_to_binary(gc)
defp grapheme_to_binary(gc) when is_list(gc), do: for(cp <- gc, do: <<cp::utf8>>, into: "")
defp grapheme_byte_size(cp) when is_integer(cp), do: codepoint_byte_size(cp)
defp grapheme_byte_size(cps), do: grapheme_byte_size(cps, 0)
+72 -43
View File
@@ -236,6 +236,27 @@ defmodule Supervisor do
}
end
Then the supervisor will call `Counter.start_link(arg)` to start the child
process. This flow is summarized in the diagram below. Caller is a process
which spawns the Supervisor process. The Supervisor then proceeds to call
your code (Module) to spawn its child process:
```mermaid
sequenceDiagram
participant C as Caller (Process)
participant S as Supervisor (Process)
participant M as Module (Code)
note right of C: child is a {module, arg} specification
C->>+S: Supervisor.start_link([child])
S-->>+M: module.child_spec(arg)
M-->>-S: %{id: term, start: {module, :start_link, [arg]}}
S-->>+M: module.start_link(arg)
M->>M: Spawns child process (child_pid)
M-->>-S: {:ok, child_pid} | :ignore | {:error, reason}
S->>-C: {:ok, supervisor_pid} | {:error, reason}
```
Luckily for us, `use GenServer` already defines a `Counter.child_spec/1`
exactly like above, so you don't need to write the definition above yourself.
If you want to customize the automatically generated `child_spec/1` function,
@@ -392,10 +413,10 @@ defmodule Supervisor do
The difference between the two approaches is that a module-based
supervisor gives you more direct control over how the supervisor
is initialized. Instead of calling `Supervisor.start_link/2` with
a list of child specifications that are automatically initialized, we manually
initialize the children by calling `Supervisor.init/2` inside its
`c:init/1` callback. `Supervisor.init/2` accepts the same `:strategy`,
`:max_restarts`, and `:max_seconds` options as `start_link/2`.
a list of child specifications that are implicitly initialized for us,
we must explicitly initialize the children by calling `Supervisor.init/2`
inside its `c:init/1` callback. `Supervisor.init/2` accepts the same
`:strategy`, `:max_restarts`, and `:max_seconds` options as `start_link/2`.
> #### `use Supervisor` {: .info}
>
@@ -537,13 +558,13 @@ defmodule Supervisor do
{sup_flags(), [child_spec() | (old_erlang_child_spec :: :supervisor.child_spec())]}}
| :ignore
@typedoc "Return values of `start_link` functions"
@typedoc "Return values of `start_link/2` and `start_link/3`."
@type on_start ::
{:ok, pid}
| :ignore
| {:error, {:already_started, pid} | {:shutdown, term} | term}
@typedoc "Return values of `start_child` functions"
@typedoc "Return values of `start_child/2`."
@type on_start_child ::
{:ok, child}
| {:ok, child, info :: term}
@@ -557,13 +578,13 @@ defmodule Supervisor do
"""
@type child :: pid | :undefined
@typedoc "The supervisor name"
@typedoc "The supervisor name."
@type name :: atom | {:global, term} | {:via, module, term}
@typedoc "Option values used by the `start*` functions"
@typedoc "Option values used by the `start_link/2` and `start_link/3` functions."
@type option :: {:name, name}
@typedoc "The supervisor flags returned on init"
@typedoc "The supervisor flags returned on init."
@type sup_flags() :: %{
strategy: strategy(),
intensity: non_neg_integer(),
@@ -571,32 +592,32 @@ defmodule Supervisor do
auto_shutdown: auto_shutdown()
}
@typedoc "The supervisor reference"
@typedoc "The supervisor reference."
@type supervisor :: pid | name | {atom, node}
@typedoc "Options given to `start_link/2` and `init/2`"
@typedoc "Options given to `start_link/2` and `c:init/1`."
@type init_option ::
{:strategy, strategy}
| {:max_restarts, non_neg_integer}
| {:max_seconds, pos_integer}
| {:auto_shutdown, auto_shutdown}
@typedoc "Supported restart options"
@typedoc "Supported restart options."
@type restart :: :permanent | :transient | :temporary
@typedoc "Supported shutdown options"
@typedoc "Supported shutdown options."
@type shutdown :: timeout() | :brutal_kill
@typedoc "Supported strategies"
@typedoc "Supported strategies."
@type strategy :: :one_for_one | :one_for_all | :rest_for_one
@typedoc "Supported automatic shutdown options"
@typedoc "Supported automatic shutdown options."
@type auto_shutdown :: :never | :any_significant | :all_significant
@typedoc """
Supervisor type.
Type of a supervised child.
Whether the supervisor is a worker or a supervisor.
Whether the supervised child is a worker or a supervisor.
"""
@type type :: :worker | :supervisor
@@ -616,18 +637,39 @@ defmodule Supervisor do
optional(:significant) => boolean()
}
@typedoc """
A module-based child spec.
This is a form of child spec that you can pass to functions such as `child_spec/2`,
`start_child/2`, and `start_link/2`, in addition to the normalized `t:child_spec/0`.
A module-based child spec can be:
* a **module** — the supervisor calls `module.child_spec([])` to retrieve the
child specification
* a **two-element tuple** in the shape of `{module, arg}` — the supervisor
calls `module.child_spec(arg)` to retrieve the child specification
"""
@typedoc since: "1.16.0"
@type module_spec :: {module(), args :: term()} | module()
@doc """
Starts a supervisor with the given children.
`children` is a list of the following forms:
* a [child specification](`t:child_spec/0`)
* a child specification (see `t:child_spec/0`)
* a module, where `module.child_spec([])` will be invoked to retrieve
its child specification
* a module, where the supervisor calls `module.child_spec([])`
to retrieve the child specification (see `t:module_spec/0`)
* a two-element tuple in the shape of `{module, arg}`, where `module.child_spec(arg)`
will be invoked to retrieve its child specification
* a `{module, arg}` tuple, where the supervisor calls `module.child_spec(arg)`
to retrieve the child specification (see `t:module_spec/0`)
* a (old) Erlang-style child specification (see
[`:supervisor.child_spec()`](`t::supervisor.child_spec/0`))
A strategy is required to be provided through the `:strategy` option. See
"Supervisor strategies and options" for examples and other options.
@@ -654,14 +696,10 @@ defmodule Supervisor do
with `:normal` reason.
"""
@spec start_link(
[
child_spec()
| {module, term}
| module
| (old_erlang_child_spec :: :supervisor.child_spec())
],
[child_spec | module_spec | (old_erlang_child_spec :: :supervisor.child_spec())],
[option | init_option]
) :: {:ok, pid} | {:error, {:already_started, pid} | {:shutdown, term} | term}
) ::
{:ok, pid} | {:error, {:already_started, pid} | {:shutdown, term} | term}
def start_link(children, options) when is_list(children) do
{sup_opts, start_opts} =
Keyword.split(options, [:strategy, :max_seconds, :max_restarts, :auto_shutdown])
@@ -709,12 +747,7 @@ defmodule Supervisor do
"""
@doc since: "1.5.0"
@spec init(
[
child_spec()
| {module, term}
| module
| (old_erlang_child_spec :: :supervisor.child_spec())
],
[child_spec | module_spec | (old_erlang_child_spec :: :supervisor.child_spec())],
[init_option]
) ::
{:ok,
@@ -842,7 +875,7 @@ defmodule Supervisor do
`module.child_spec([])`.
After the child specification is retrieved, the fields on `overrides`
are directly applied on the child spec. If `overrides` has keys that
are directly applied to the child spec. If `overrides` has keys that
do not map to any child specification field, an error is raised.
See the "Child specification" section in the module documentation
@@ -859,7 +892,7 @@ defmodule Supervisor do
#=> start: {Agent, :start_link, [fn -> :ok end]}}
"""
@spec child_spec(child_spec() | {module, arg :: term} | module, keyword) :: child_spec()
@spec child_spec(child_spec() | module_spec(), keyword()) :: child_spec()
def child_spec(module_or_map, overrides)
def child_spec({_, _, _, _, _, _} = tuple, _overrides) do
@@ -956,12 +989,8 @@ defmodule Supervisor do
"""
@spec start_child(
supervisor,
child_spec()
| {module, term}
| module
| (old_erlang_child_spec :: :supervisor.child_spec())
) ::
on_start_child
child_spec | module_spec | (old_erlang_child_spec :: :supervisor.child_spec())
) :: on_start_child
def start_child(supervisor, {_, _, _, _, _, _} = child_spec) do
call(supervisor, {:start_child, child_spec})
end
+1 -1
View File
@@ -194,7 +194,7 @@ defmodule Supervisor.Spec do
defp assert_unique_ids([id | rest]) do
if id in rest do
raise ArgumentError,
"duplicated ID #{inspect(id)} found in the supervisor specification, " <>
"duplicate ID #{inspect(id)} found in the supervisor specification, " <>
"please explicitly pass the :id option when defining this worker/supervisor"
else
assert_unique_ids(rest)
+32 -4
View File
@@ -64,6 +64,12 @@ defmodule System do
"""
defmodule EnvError do
@moduledoc """
An exception raised when a system environment variable is not set.
For example, see `System.fetch_env!/1`.
"""
defexception [:env]
@impl true
@@ -749,9 +755,12 @@ defmodule System do
Sets multiple environment variables.
Sets a new value for each environment variable corresponding
to each `{key, value}` pair in `enum`. Keys are automatically
converted to strings, values are sent as is. `nil` values erase
to each `{key, value}` pair in `enum`. Keys and non-nil values
are automatically converted to charlists. `nil` values erase
the given keys.
Overall, this is a convenience wrapper around `put_env/2` and
`delete_env/2` with support for different key and value formats.
"""
@spec put_env(Enumerable.t()) :: :ok
def put_env(enum) do
@@ -996,6 +1005,25 @@ defmodule System do
`Port` module describes this problem and possible solutions under
the "Zombie processes" section.
> #### Windows argument splitting and untrusted arguments {: .warning}
>
> On Unix systems, arguments are passed to a new operating system
> process as an array of strings but on Windows it is up to the child
> process to parse them and some Windows programs may apply their own
> rules, which are inconsistent with the standard C runtime `argv` parsing
>
> This is particularly troublesome when invoking `.bat` or `.com` files
> as these run implicitly through `cmd.exe`, whose argument parsing is
> vulnerable to malicious input and can be used to run arbitrary shell
> commands.
>
> Therefore, if you are running on Windows and you execute batch
> files or `.com` applications, you must not pass untrusted input as
> arguments to the program. You may avoid accidentally executing them
> by explicitly passing the extension of the program you want to run,
> such as `.exe`, and double check the program is indeed not a batch
> file or `.com` application.
## Examples
iex> System.cmd("echo", ["hello"])
@@ -1040,8 +1068,8 @@ defmodule System do
* `: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 is `false`, and can be set on system startup by passing the
[`+spp`](https://www.erlang.org/doc/man/erl.html#+spp) flag to `--erl`.
The default is `false`, and can be set on system startup by passing the
[`+spp`](https://www.erlang.org/doc/man/erl.html#+spp) flag to `--erl`.
Use `:erlang.system_info(:port_parallelism)` to check if enabled.
## Error reasons
+52 -14
View File
@@ -40,14 +40,42 @@ defmodule Task do
as they are *always* sent. If you are not expecting a reply,
consider using `Task.start_link/1` as detailed below.
2. async tasks link the caller and the spawned process. This
2. Async tasks link the caller and the spawned process. This
means that, if the caller crashes, the task will crash
too and vice-versa. This is on purpose: if the process
meant to receive the result no longer exists, there is
no purpose in completing the computation.
no purpose in completing the computation. If this is not
desired, you will want to use supervised tasks, described
in a subsequent section.
If this is not desired, you will want to use supervised
tasks, described next.
## Tasks are processes
Tasks are processes and so data will need to be completely copied
to them. Take the following code as an example:
large_data = fetch_large_data()
task = Task.async(fn -> do_some_work(large_data) end)
res = do_some_other_work()
res + Task.await(task)
The code above copies over all of `large_data`, which can be
resource intensive depending on the size of the data.
There are two ways to address this.
First, if you need to access only part of `large_data`,
consider extracting it before the task:
large_data = fetch_large_data()
subset_data = large_data.some_field
task = Task.async(fn -> do_some_work(subset_data) end)
Alternatively, if you can move the data loading altogether
to the task, it may be even better:
task = Task.async(fn ->
large_data = fetch_large_data()
do_some_work(large_data)
end)
## Dynamically supervised tasks
@@ -1109,14 +1137,15 @@ defmodule Task do
* `{:ok, term}` if the task has successfully reported its
result back in the given time interval
* `{:exit, reason}` if the task has died
* `nil` if the task keeps running past the timeout
* `nil` if the task keeps running, either because a limit
has been reached or past the timeout
Check `yield/2` for more information.
## Example
`Task.yield_many/2` allows developers to spawn multiple tasks
and retrieve the results received in a given timeframe.
and retrieve the results received in a given time frame.
If we combine it with `Task.shutdown/2` (or `Task.ignore/1`),
it allows us to gather those results and cancel (or ignore)
the tasks that have not replied in time.
@@ -1162,6 +1191,10 @@ defmodule Task do
The second argument is either a timeout or options, which defaults
to this:
* `:limit` - the maximum amount of tasks to wait for.
If the limit is reached before the timeout, this function
returns immediately without triggering the `:on_timeout` behaviour
* `:timeout` - the maximum amount of time (in milliseconds or `:infinity`)
each task is allowed to execute for. Defaults to `5000`.
@@ -1173,7 +1206,11 @@ defmodule Task do
* `:kill_task` - the task that timed out is killed.
"""
@spec yield_many([t], timeout) :: [{t, {:ok, term} | {:exit, term} | nil}]
@spec yield_many([t], timeout: timeout, on_timeout: :nothing | :ignore | :kill_task) ::
@spec yield_many([t],
limit: pos_integer(),
timeout: timeout,
on_timeout: :nothing | :ignore | :kill_task
) ::
[{t, {:ok, term} | {:exit, term} | nil}]
def yield_many(tasks, opts \\ [])
@@ -1182,9 +1219,6 @@ defmodule Task do
end
def yield_many(tasks, opts) when is_list(opts) do
on_timeout = Keyword.get(opts, :on_timeout, :nothing)
timeout = Keyword.get(opts, :timeout, 5_000)
refs =
Map.new(tasks, fn %Task{ref: ref, owner: owner} = task ->
if owner != self() do
@@ -1194,6 +1228,9 @@ defmodule Task do
{ref, nil}
end)
on_timeout = Keyword.get(opts, :on_timeout, :nothing)
timeout = Keyword.get(opts, :timeout, 5_000)
limit = Keyword.get(opts, :limit, map_size(refs))
timeout_ref = make_ref()
timer_ref =
@@ -1202,16 +1239,17 @@ defmodule Task do
end
try do
yield_many(map_size(refs), refs, timeout_ref, timer_ref)
yield_many(limit, refs, timeout_ref, timer_ref)
catch
{:noconnection, reason} ->
exit({reason, {__MODULE__, :yield_many, [tasks, timeout]}})
else
refs ->
{timed_out?, refs} ->
for task <- tasks do
value =
with nil <- Map.fetch!(refs, task.ref) do
case on_timeout do
_ when not timed_out? -> nil
:nothing -> nil
:kill_task -> shutdown(task, :brutal_kill)
:ignore -> ignore(task)
@@ -1226,7 +1264,7 @@ defmodule Task do
defp yield_many(0, refs, timeout_ref, timer_ref) do
timer_ref && Process.cancel_timer(timer_ref)
receive do: (^timeout_ref -> :ok), after: (0 -> :ok)
refs
{false, refs}
end
defp yield_many(limit, refs, timeout_ref, timer_ref) do
@@ -1243,7 +1281,7 @@ defmodule Task do
end
^timeout_ref ->
refs
{true, refs}
end
end
+14 -4
View File
@@ -152,7 +152,7 @@ defmodule Task.Supervisor do
Starts a task that can be awaited on.
The `supervisor` must be a reference as defined in `Supervisor`.
The task will still be linked to the caller, see `Task.async/3` for
The task will still be linked to the caller, see `Task.async/1` for
more information and `async_nolink/3` for a non-linked variant.
Raises an error if `supervisor` has reached the maximum number of
@@ -162,6 +162,7 @@ defmodule Task.Supervisor do
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
or an integer indicating the timeout value, defaults to 5000 milliseconds.
The tasks must trap exits for the timeout to have an effect.
"""
@spec async(Supervisor.supervisor(), (-> any), Keyword.t()) :: Task.t()
@@ -173,7 +174,7 @@ defmodule Task.Supervisor do
Starts a task that can be awaited on.
The `supervisor` must be a reference as defined in `Supervisor`.
The task will still be linked to the caller, see `Task.async/3` for
The task will still be linked to the caller, see `Task.async/1` for
more information and `async_nolink/3` for a non-linked variant.
Raises an error if `supervisor` has reached the maximum number of
@@ -183,6 +184,7 @@ defmodule Task.Supervisor do
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
or an integer indicating the timeout value, defaults to 5000 milliseconds.
The tasks must trap exits for the timeout to have an effect.
"""
@spec async(Supervisor.supervisor(), module, atom, [term], Keyword.t()) :: Task.t()
@@ -194,7 +196,7 @@ defmodule Task.Supervisor do
Starts a task that can be awaited on.
The `supervisor` must be a reference as defined in `Supervisor`.
The task won't be linked to the caller, see `Task.async/3` for
The task won't be linked to the caller, see `Task.async/1` for
more information.
Raises an error if `supervisor` has reached the maximum number of
@@ -208,6 +210,7 @@ defmodule Task.Supervisor do
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
or an integer indicating the timeout value, defaults to 5000 milliseconds.
The tasks must trap exits for the timeout to have an effect.
## Compatibility with OTP behaviours
@@ -280,7 +283,7 @@ defmodule Task.Supervisor do
Starts a task that can be awaited on.
The `supervisor` must be a reference as defined in `Supervisor`.
The task won't be linked to the caller, see `Task.async/3` for
The task won't be linked to the caller, see `Task.async/1` for
more information.
Raises an error if `supervisor` has reached the maximum number of
@@ -335,8 +338,14 @@ defmodule Task.Supervisor do
* `:kill_task` - the task that timed out is killed. The value
emitted for that task is `{:exit, :timeout}`.
* `:zip_input_on_exit` - (since v1.14.0) adds the original
input to `:exit` tuples. The value emitted for that task is
`{:exit, {input, reason}}`, where `input` is the collection element
that caused an exited during processing. Defaults to `false`.
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
or an integer indicating the timeout value. Defaults to `5000` milliseconds.
The tasks must trap exits for the timeout to have an effect.
## Examples
@@ -455,6 +464,7 @@ defmodule Task.Supervisor do
* `:shutdown` - `:brutal_kill` if the task must be killed directly on shutdown
or an integer indicating the timeout value, defaults to 5000 milliseconds.
The task must trap exits for the timeout to have an effect.
"""
@spec start_child(Supervisor.supervisor(), (-> any), keyword) ::
+24 -16
View File
@@ -38,6 +38,12 @@ defmodule URI do
@opaque authority :: nil | binary
defmodule Error do
@moduledoc """
An exception raised when an error occurs when a `URI` is invalid.
For example, see `URI.new!/1`.
"""
defexception [:action, :reason, :part]
@doc false
@@ -356,22 +362,24 @@ defmodule URI do
end
@doc """
Percent-escapes all characters that require escaping in `string`.
Percent-encodes all characters that require escaping in `string`.
This means reserved characters, such as `:` and `/`, and the
so-called unreserved characters, which have the same meaning both
escaped and unescaped, won't be escaped by default.
By default, this function is meant to escape the whole URI, and
therefore it will escape all characters which are foreign to the
URI specification. Reserved characters (such as `:` and `/`) or
unreserved (such as letters and numbers) are not escaped.
Because different components of a URI require different escaping
rules, this function also accepts a `predicate` function as an optional
argument. If passed, this function will be called with each byte
in `string` as its argument and should return a truthy value (anything other
than `false` or `nil`) if the given byte should be left as is, or
return a falsy value (`false` or `nil`) if the character should be
escaped. Defaults to `URI.char_unescaped?/1`.
See `encode_www_form/1` if you are interested in escaping reserved
characters too.
This function also accepts a `predicate` function as an optional
argument. If passed, this function will be called with each byte
in `string` as its argument and should return a truthy value (anything other
than `false` or `nil`) if the given byte should be left as is, or return a
falsy value (`false` or `nil`) if the character should be escaped. Defaults
to `URI.char_unescaped?/1`.
## Examples
iex> URI.encode("ftp://s-ite.tld/?value=put it+й")
@@ -648,16 +656,16 @@ defmodule URI do
scheme = String.downcase(scheme, :ascii)
case map do
%{port: port} when port != :undefined ->
%{port: port} when is_integer(port) ->
%{uri | scheme: scheme}
%{} ->
case default_port(scheme) do
nil -> %{uri | scheme: scheme}
port -> %{uri | scheme: scheme, port: port}
end
%{uri | scheme: scheme, port: default_port(scheme)}
end
%{port: :undefined} ->
%{uri | port: nil}
%{} ->
uri
end
+12
View File
@@ -217,6 +217,12 @@ defmodule Version do
end
defmodule InvalidRequirementError do
@moduledoc """
An exception raised when a version requirement is invalid.
For example, see `Version.parse_requirement!/1`.
"""
defexception [:requirement]
@impl true
@@ -231,6 +237,12 @@ defmodule Version do
end
defmodule InvalidVersionError do
@moduledoc """
An exception raised when a version is invalid.
For example, see `Version.parse!/1`.
"""
defexception [:version]
@impl true
@@ -0,0 +1,537 @@
# Code-related anti-patterns
This document outlines potential anti-patterns related to your code and particular Elixir idioms and features.
## Comments overuse
#### Problem
When you overuse comments or comment self-explanatory code, it can have the effect of making code *less readable*.
#### Example
```elixir
# Returns the Unix timestamp of 5 minutes from the current time
defp unix_five_min_from_now do
# Get the current time
now = DateTime.utc_now()
# Convert it to a Unix timestamp
unix_now = DateTime.to_unix(now, :second)
# Add five minutes in seconds
unix_now + (60 * 5)
end
```
#### Refactoring
Prefer clear and self-explanatory function names, module names, and variable names when possible. In the example above, the function name explains well what the function does, so you likely won't need the comment before it. The code also explains the operations well through variable names and clear function calls.
You could refactor the code above like this:
```elixir
@five_min_in_seconds 60 * 5
defp unix_five_min_from_now do
now = DateTime.utc_now()
unix_now = DateTime.to_unix(now, :second)
unix_now + @five_min_in_seconds
end
```
We removed the unnecessary comments. We also added a `@five_min_in_seconds` module attribute, which serves the additional purpose of giving a name to the "magic" number `60 * 5`, making the code clearer and more expressive.
#### Additional remarks
Elixir makes a clear distinction between **documentation** and code comments. The language has built-in first-class support for documentation through `@doc`, `@moduledoc`, and more. See the ["Writing documentation"](../getting-started/writing-documentation.md) guide for more information.
## Complex `else` clauses in `with`
#### Problem
This anti-pattern refers to `with` statements that flatten all its error clauses into a single complex `else` block. This situation is harmful to the code readability and maintainability because it's difficult to know from which clause the error value came.
#### Example
An example of this anti-pattern, as shown below, is a function `open_decoded_file/1` that reads a Base64-encoded string content from a file and returns a decoded binary string. This function uses a `with` statement that needs to handle two possible errors, all of which are concentrated in a single complex `else` block.
```elixir
def open_decoded_file(path) do
with {:ok, encoded} <- File.read(path),
{:ok, decoded} <- Base.decode64(encoded) do
{:ok, String.trim(decoded)}
else
{:error, _} -> {:error, :badfile}
:error -> {:error, :badencoding}
end
end
```
In the code above, it is unclear how each pattern on the left side of `<-` relates to their error at the end. The more patterns in a `with`, the less clear the code gets, and the more likely it is that unrelated failures will overlap each other.
#### Refactoring
In this situation, instead of concentrating all error handling within a single complex `else` block, it is better to normalize the return types in specific private functions. In this way, `with` can focus on the success case and the errors are normalized closer to where they happen, leading to better organized and maintainable code.
```elixir
def open_decoded_file(path) do
with {:ok, encoded} <- file_read(path),
{:ok, decoded} <- base_decode64(encoded) do
{:ok, String.trim(decoded)}
end
end
defp file_read(path) do
case File.read(path) do
{:ok, contents} -> {:ok, contents}
{:error, _} -> {:error, :badfile}
end
end
defp base_decode64(contents) do
case Base.decode64(contents) do
{:ok, decoded} -> {:ok, decoded}
:error -> {:error, :badencoding}
end
end
```
## Complex extractions in clauses
#### Problem
When we use multi-clause functions, it is possible to extract values in the clauses for further usage and for pattern matching/guard checking. This extraction itself does not represent an anti-pattern, but when you have *extractions made across several clauses and several arguments of the same function*, it becomes hard to know which extracted parts are used for pattern/guards and what is used only inside the function body. This anti-pattern is related to [Unrelated multi-clause function](design-anti-patterns.md#unrelated-multi-clause-function), but with implications of its own. It impairs the code readability in a different way.
#### Example
The multi-clause function `drive/1` is extracting fields of an `%User{}` struct for usage in the clause expression (`age`) and for usage in the function body (`name`):
```elixir
def drive(%User{name: name, age: age}) when age >= 18 do
"#{name} can drive"
end
def drive(%User{name: name, age: age}) when age < 18 do
"#{name} cannot drive"
end
```
While the example above is small and does not configure an anti-pattern, it is an example of mixed extraction and pattern matching. A situation where `drive/1` was more complex, having many more clauses, arguments, and extractions, would make it hard to know at a glance which variables are used for pattern/guards and which ones are not.
#### Refactoring
As shown below, a possible solution to this anti-pattern is to extract only pattern/guard related variables in the signature once you have many arguments or multiple clauses:
```elixir
def drive(%User{age: age} = user) when age >= 18 do
%User{name: name} = user
"#{name} can drive"
end
def drive(%User{age: age} = user) when age < 18 do
%User{name: name} = user
"#{name} cannot drive"
end
```
## Dynamic atom creation
#### Problem
An `Atom` is an Elixir basic type whose value is its own name. Atoms are often useful to identify resources or express the state, or result, of an operation. Creating atoms dynamically is not an anti-pattern by itself; however, atoms are not garbage collected by the Erlang Virtual Machine, so values of this type live in memory during a software's entire execution lifetime. The Erlang VM limits the number of atoms that can exist in an application by default to *1_048_576*, which is more than enough to cover all atoms defined in a program, but attempts to serve as an early limit for applications which are "leaking atoms" through dynamic creation.
For these reason, creating atoms dynamically can be considered an anti-pattern when the developer has no control over how many atoms will be created during the software execution. This unpredictable scenario can expose the software to unexpected behaviour caused by excessive memory usage, or even by reaching the maximum number of *atoms* possible.
#### Example
Picture yourself implementing code that converts string values into atoms. These strings could have been received from an external system, either as part of a request into our application, or as part of a response to your application. This dynamic and unpredictable scenario poses a security risk, as these uncontrolled conversions can potentially trigger out-of-memory errors.
```elixir
defmodule MyRequestHandler do
def parse(%{"status" => status, "message" => message} = _payload) do
%{status: String.to_atom(status), message: message}
end
end
```
```elixir
iex> MyRequestHandler.parse(%{"status" => "ok", "message" => "all good"})
%{status: :ok, message: "all good"}
```
When we use the `String.to_atom/1` function to dynamically create an atom, it essentially gains potential access to create arbitrary atoms in our system, causing us to lose control over adhering to the limits established by the BEAM. This issue could be exploited by someone to create enough atoms to shut down a system.
#### Refactoring
To eliminate this anti-pattern, developers must either perform explicit conversions by mapping strings to atoms or replace the use of `String.to_atom/1` with `String.to_existing_atom/1`. An explicit conversion could be done as follows:
```elixir
defmodule MyRequestHandler do
def parse(%{"status" => status, "message" => message} = _payload) do
%{status: convert_status(status), message: message}
end
defp convert_status("ok"), do: :ok
defp convert_status("error"), do: :error
defp convert_status("redirect"), do: :redirect
end
```
```elixir
iex> MyRequestHandler.parse(%{"status" => "status_not_seen_anywhere", "message" => "all good"})
** (FunctionClauseError) no function clause matching in MyRequestHandler.convert_status/1
```
By explicitly listing all supported statuses, you guarantee only a limited number of conversions may happen. Passing an invalid status will lead to a function clause error.
An alternative is to use `String.to_existing_atom/1`, which will only convert a string to atom if the atom already exists in the system:
```elixir
defmodule MyRequestHandler do
def parse(%{"status" => status, "message" => message} = _payload) do
%{status: String.to_existing_atom(status), message: message}
end
end
```
```elixir
iex> MyRequestHandler.parse(%{"status" => "status_not_seen_anywhere", "message" => "all good"})
** (ArgumentError) errors were found at the given arguments:
* 1st argument: not an already existing atom
```
In such cases, passing an unknown status will raise as long as the status was not defined anywhere as an atom in the system. However, assuming `status` can be either `:ok`, `:error`, or `:redirect`, how can you guarantee those atoms exist? You must ensure those atoms exist somewhere **in the same module** where `String.to_existing_atom/1` is called. For example, if you had this code:
```elixir
defmodule MyRequestHandler do
def parse(%{"status" => status, "message" => message} = _payload) do
%{status: String.to_existing_atom(status), message: message}
end
def handle(%{status: status}) do
case status do
:ok -> ...
:error -> ...
:redirect -> ...
end
end
end
```
All valid statuses are defined as atoms within the same module, and that's enough. If you want to be explicit, you could also have a function that lists them:
```elixir
def valid_statuses do
[:ok, :error, :redirect]
end
```
However, keep in mind using a module attribute or defining the atoms in the module body, outside of a function, are not sufficient, as the module body is only executed during compilation and it is not necessarily part of the compiled module loaded at runtime.
## Long parameter list
#### Problem
In a functional language like Elixir, functions tend to explicitly receive all inputs and return all relevant outputs, instead of relying on mutations or side-effects. As functions grow in complexity, the amount of arguments (parameters) they need to work with may grow, to a point where the function's interface becomes confusing and prone to errors during use.
#### Example
In the following example, the `loan/6` functions takes too many arguments, causing its interface to be confusing and potentially leading developers to introduce errors during calls to this function.
```elixir
defmodule Library do
# Too many parameters that can be grouped!
def loan(user_name, email, password, user_alias, book_title, book_ed) do
...
end
end
```
#### Refactoring
To address this anti-pattern, related arguments can be grouped using key-value data structures, such as maps, structs, or even keyword lists in the case of optional arguments. This effectively reduces the number of arguments and the key-value data structures adds clarity to the caller.
For this particular example, the arguments to `loan/6` can be grouped into two different maps, thereby reducing its arity to `loan/2`:
```elixir
defmodule Library do
def loan(%{name: name, email: email, password: password, alias: alias} = user, %{title: title, ed: ed} = book) do
...
end
end
```
In some cases, the function with too many arguments may be a private function, which gives us more flexibility over how to separate the function arguments. One possible suggestion for such scenarios is to split the arguments in two maps (or tuples): one map keeps the data that may change, and the other keeps the data that won't change (read-only). This gives us a mechanical option to refactor the code.
Other times, a function may legitimately take half a dozen or more completely unrelated arguments. This may suggest the function is trying to do too much and would be better broken into multiple functions, each responsible for a smaller piece of the overall responsibility.
## Namespace trespassing
#### Problem
This anti-pattern manifests when a package author or a library defines modules outside of its "namespace". A library should use its name as a "prefix" for all of its modules. For example, a package named `:my_lib` should define all of its modules within the `MyLib` namespace, such as `MyLib.User`, `MyLib.SubModule`, `MyLib.Application`, and `MyLib` itself.
This is important because the Erlang VM can only load one instance of a module at a time. So if there are multiple libraries that define the same module, then they are incompatible with each other due to this limitation. By always using the library name as a prefix, it avoids module name clashes due to the unique prefix.
#### Example
This problem commonly manifests when writing an extension of another library. For example, imagine you are writing a package that adds authentication to [Plug](https://github.com/elixir-plug/plug) called `:plug_auth`. You must avoid defining modules within the `Plug` namespace:
```elixir
defmodule Plug.Auth do
# ...
end
```
Even if `Plug` does not currently define a `Plug.Auth` module, it may add such a module in the future, which would ultimately conflict with `plug_auth`'s definition.
#### Refactoring
Given the package is named `:plug_auth`, it must define modules inside the `PlugAuth` namespace:
```elixir
defmodule PlugAuth do
# ...
end
```
#### Additional remarks
There are few known exceptions to this anti-pattern:
* [Protocol implementations](`Kernel.defimpl/2`) are, by design, defined under the protocol namespace
* In some scenarios, the namespace owner may allow exceptions to this rule. For example, in Elixir itself, you defined [custom Mix tasks](`Mix.Task`) by placing them under the `Mix.Tasks` namespace, such as `Mix.Tasks.PlugAuth`
* If you are the maintainer for both `plug` and `plug_auth`, then you may allow `plug_auth` to define modules with the `Plug` namespace, such as `Plug.Auth`. However, you are responsible for avoiding or managing any conflicts that may arise in the future
## Non-assertive map access
#### Problem
In Elixir, it is possible to access values from `Map`s, which are key-value data structures, either statically or dynamically.
When a key is expected to exist in a map, it must be accessed using the `map.key` notation, making it clear to developers (and the compiler) that the key must exist. If the key does not exist, an exception is raised (and in some cases also compiler warnings). This is also known as the static notation, as the key is known at the time of writing the code.
When a key is optional, the `map[:key]` notation must be used instead. This way, if the informed key does not exist, `nil` is returned. This is the dynamic notation, as it also supports dynamic key access, such as `map[some_var]`.
When you use `map[:key]` to access a key that always exists in the map, you are making the code less clear for developers and for the compiler, as they now need to work with the assumption the key may not be there. This mismatch may also make it harder to track certain bugs. If the key is unexpectedly missing, you will have a `nil` value propagate through the system, instead of raising on map access.
#### Example
The function `plot/1` tries to draw a graphic to represent the position of a point in a cartesian plane. This function receives a parameter of `Map` type with the point attributes, which can be a point of a 2D or 3D cartesian coordinate system. This function uses dynamic access to retrieve values for the map keys:
```elixir
defmodule Graphics do
def plot(point) do
# Some other code...
{point[:x], point[:y], point[:z]}
end
end
```
```elixir
iex> point_2d = %{x: 2, y: 3}
%{x: 2, y: 3}
iex> point_3d = %{x: 5, y: 6, z: 7}
%{x: 5, y: 6, z: 7}
iex> Graphics.plot(point_2d)
{2, 3, nil}
iex> Graphics.plot(point_3d)
{5, 6, 7}
```
Given we want to plot both 2D and 3D points, the behaviour above is expected. But what happens if we forget to pass a point with either `:x` or `:y`?
```elixir
iex> bad_point = %{y: 3, z: 4}
%{y: 3, z: 4}
iex> Graphics.plot(bad_point)
{nil, 3, 4}
```
The behaviour above is unexpected because our function should not work with points without a `:x` key. This leads to subtle bugs, as we may now pass `nil` to another function, instead of raising early on.
#### Refactoring
To remove this anti-pattern, we must use the dynamic `map[:key]` syntax and the static `map.key` notation according to our requirements. We expect `:x` and `:y` to always exist, but not `:z`. The next code illustrates the refactoring of `plot/1`, removing this anti-pattern:
```elixir
defmodule Graphics do
def plot(point) do
# Some other code...
{point.x, point.y, point[:z]}
end
end
```
```elixir
iex> Graphics.plot(point_2d)
{2, 3, nil}
iex> Graphics.plot(bad_point)
** (KeyError) key :x not found in: %{y: 3, z: 4} # <= explicitly warns that
graphic.ex:4: Graphics.plot/1 # <= the :x key does not exist!
```
Overall, the usage of `map.key` and `map[:key]` encode important information about your data structure, allowing developers to be clear about their intent. See both `Map` and `Access` module documentation for more information and examples.
An alternative to refactor this anti-pattern is to use pattern matching, defining explicit clauses for 2d vs 3d points:
```elixir
defmodule Graphics do
# 3d
def plot(%{x: x, y: y, z: z}) do
# Some other code...
{x, y, z}
end
# 2d
def plot(%{x: x, y: y}) do
# Some other code...
{x, y}
end
end
```
Pattern-matching is specially useful when matching over multiple keys as well as on the values themselves at once.
Another option is to use structs. By default, structs only support static access to its fields. In such scenarios, you may consider defining structs for both 2D and 3D points:
```elixir
defmodule Point2D do
@enforce_keys [:x, :y]
defstruct [x: nil, y: nil]
end
```
Generally speaking, structs are useful when sharing data structures across modules, at the cost of adding a compile time dependency between these modules. If module `A` uses a struct defined in module `B`, `A` must be recompiled if the fields in the struct `B` change.
#### Additional remarks
This anti-pattern was formerly known as [Accessing non-existent map/struct fields](https://github.com/lucasvegi/Elixir-Code-Smells#accessing-non-existent-mapstruct-fields).
## Non-assertive pattern matching
#### Problem
Overall, Elixir systems are composed of many supervised processes, so the effects of an error are localized to a single process, and don't propagate to the entire application. A supervisor detects the failing process, reports it, and possibly restarts it. This anti-pattern arises when developers write defensive or imprecise code, capable of returning incorrect values which were not planned for, instead of programming in an assertive style through pattern matching and guards.
#### Example
The function `get_value/2` tries to extract a value from a specific key of a URL query string. As it is not implemented using pattern matching, `get_value/2` always returns a value, regardless of the format of the URL query string passed as a parameter in the call. Sometimes the returned value will be valid. However, if a URL query string with an unexpected format is used in the call, `get_value/2` will extract incorrect values from it:
```elixir
defmodule Extract do
def get_value(string, desired_key) do
parts = String.split(string, "&")
Enum.find_value(parts, fn pair ->
key_value = String.split(pair, "=")
Enum.at(key_value, 0) == desired_key && Enum.at(key_value, 1)
end)
end
end
```
```elixir
# URL query string with the planned format - OK!
iex> Extract.get_value("name=Lucas&university=UFMG&lab=ASERG", "lab")
"ASERG"
iex> Extract.get_value("name=Lucas&university=UFMG&lab=ASERG", "university")
"UFMG"
# Unplanned URL query string format - Unplanned value extraction!
iex> Extract.get_value("name=Lucas&university=institution=UFMG&lab=ASERG", "university")
"institution" # <= why not "institution=UFMG"? or only "UFMG"?
```
#### Refactoring
To remove this anti-pattern, `get_value/2` can be refactored through the use of pattern matching. So, if an unexpected URL query string format is used, the function will crash instead of returning an invalid value. This behaviour, shown below, allows clients to decide how to handle these errors and doesn't give a false impression that the code is working correctly when unexpected values are extracted:
```elixir
defmodule Extract do
def get_value(string, desired_key) do
parts = String.split(string, "&")
Enum.find_value(parts, fn pair ->
[key, value] = String.split(pair, "=") # <= pattern matching
key == desired_key && value
end)
end
end
```
```elixir
# URL query string with the planned format - OK!
iex> Extract.get_value("name=Lucas&university=UFMG&lab=ASERG", "name")
"Lucas"
# Unplanned URL query string format - Crash explaining the problem to the client!
iex> Extract.get_value("name=Lucas&university=institution=UFMG&lab=ASERG", "university")
** (MatchError) no match of right hand side value: ["university", "institution", "UFMG"]
extract.ex:7: anonymous fn/2 in Extract.get_value/2 # <= left hand: [key, value] pair
iex> Extract.get_value("name=Lucas&university&lab=ASERG", "university")
** (MatchError) no match of right hand side value: ["university"]
extract.ex:7: anonymous fn/2 in Extract.get_value/2 # <= left hand: [key, value] pair
```
Elixir and pattern matching promote an assertive style of programming where you handle the known cases. Once an unexpected scenario arises, you can decide to address it accordingly based on practical examples, or conclude the scenario is indeed invalid and the exception is the desired choice.
`case/2` is another important construct in Elixir that help us write assertive code, by matching on specific patterns. For example, if a function returns `{:ok, ...}` or `{:error, ...}`, prefer to explicitly match on both patterns:
```elixir
case some_function(arg) do
{:ok, value} -> # ...
{:error, _} -> # ...
end
```
In particular, avoid matching solely on `_`, as shown below:
```elixir
case some_function(arg) do
{:ok, value} -> # ...
_ -> # ...
end
```
Matching on `_` is less clear in intent and it may hide bugs if `some_function/1` adds new return values in the future.
#### Additional remarks
This anti-pattern was formerly known as [Speculative assumptions](https://github.com/lucasvegi/Elixir-Code-Smells#speculative-assumptions).
## Non-assertive truthiness
#### Problem
Elixir provides the concept of truthiness: `nil` and `false` are considered "falsy" and all other values are "truthy". Many constructs in the language, such as `&&/2`, `||/2`, and `!/1` handle truthy and falsy values. Using those operators is not an anti-pattern. However, using those operators when all operands are expected to be booleans, may be an anti-pattern.
#### Example
The simplest scenario where this anti-pattern manifests is in conditionals, such as:
```elixir
if is_binary(name) && is_integer(age) do
# ...
else
# ...
end
```
Given both operands of `&&/2` are booleans, the code is more generic than necessary, and potentially unclear.
#### Refactoring
To remove this anti-pattern, we can replace `&&/2`, `||/2`, and `!/1` by `and/2`, `or/2`, and `not/1` respectively. These operators assert at least their first argument is a boolean:
```elixir
if is_binary(name) and is_integer(age) do
# ...
else
# ...
end
```
This technique may be particularly important when working with Erlang code. Erlang does not have the concept of truthiness. It never returns `nil`, instead its functions may return `:error` or `:undefined` in places an Elixir developer would return `nil`. Therefore, to avoid accidentally interpreting `:undefined` or `:error` as a truthy value, you may prefer to use `and/2`, `or/2`, and `not/1` exclusively when interfacing with Erlang APIs.
@@ -0,0 +1,473 @@
# Design-related anti-patterns
This document outlines potential anti-patterns related to your modules, functions, and the role they play within a codebase.
## Alternative return types
#### Problem
This anti-pattern refers to functions that receive options (typically as a *keyword list* parameter) that drastically change their return type. Because options are optional and sometimes set dynamically, if they also change the return type, it may be hard to understand what the function actually returns.
#### Example
An example of this anti-pattern, as shown below, is when a function has many alternative return types, depending on the options received as a parameter.
```elixir
defmodule AlternativeInteger do
@spec parse(String.t(), keyword()) :: integer() | {integer(), String.t()} | :error
def parse(string, options \\ []) when is_list(options) do
if Keyword.get(options, :discard_rest, false) do
Integer.parse(string)
else
case Integer.parse(string) do
{int, _rest} -> int
:error -> :error
end
end
end
end
```
```elixir
iex> AlternativeInteger.parse("13")
13
iex> AlternativeInteger.parse("13", discard_rest: true)
13
iex> AlternativeInteger.parse("13", discard_rest: false)
{13, ""}
```
#### Refactoring
To refactor this anti-pattern, as shown next, add a specific function for each return type (for example, `parse_discard_rest/1`), no longer delegating this to options passed as arguments.
```elixir
defmodule AlternativeInteger do
@spec parse(String.t()) :: {integer(), String.t()} | :error
def parse(string) do
Integer.parse(string)
end
@spec parse_discard_rest(String.t()) :: integer() | :error
def parse_discard_rest(string) do
case Integer.parse(string) do
{int, _rest} -> int
:error -> :error
end
end
end
```
```elixir
iex> AlternativeInteger.parse("13")
{13, ""}
iex> AlternativeInteger.parse_discard_rest("13")
13
```
## Boolean obsession
#### Problem
This anti-pattern happens when booleans are used instead of atoms to encode information. The usage of booleans themselves is not an anti-pattern, but whenever multiple booleans are used with overlapping states, replacing the booleans by atoms (or composite data types such as *tuples*) may lead to clearer code.
This is a special case of [*Primitive obsession*](#primitive-obsession), specific to boolean values.
#### Example
An example of this anti-pattern is a function that receives two or more options, such as `editor: true` and `admin: true`, to configure its behaviour in overlapping ways. In the code below, the `:editor` option has no effect if `:admin` is set, meaning that the `:admin` option has higher priority than `:editor`, and they are ultimately related.
```elixir
defmodule MyApp do
def process(invoice, options \\ []) do
cond do
options[:admin] -> # Is an admin
options[:editor] -> # Is an editor
true -> # Is none
end
end
end
```
#### Refactoring
Instead of using multiple options, the code above could be refactored to receive a single option, called `:role`, that can be either `:admin`, `:editor`, or `:default`:
```elixir
defmodule MyApp do
def process(invoice, options \\ []) do
case Keyword.get(options, :role, :default) do
:admin -> # Is an admin
:editor -> # Is an editor
:default -> # Is none
end
end
end
```
This anti-pattern may also happen in our own data structures. For example, we may define a `User` struct with two boolean fields, `:editor` and `:admin`, while a single field named `:role` may be preferred.
Finally, it is worth noting that using atoms may be preferred even when we have a single boolean argument/option. For example, consider an invoice which may be set as approved/unapproved. One option is to provide a function that expects a boolean:
```elixir
MyApp.update(invoice, approved: true)
```
However, using atoms may read better and make it simpler to add further states (such as pending) in the future:
```elixir
MyApp.update(invoice, status: :approved)
```
Remember booleans are internally represented as atoms. Therefore there is no performance penalty in one approach over the other.
## Exceptions for control-flow
#### Problem
This anti-pattern refers to code that uses `Exception`s for control flow. Exception handling itself does not represent an anti-pattern, but developers must prefer to use `case` and pattern matching to change the flow of their code, instead of `try/rescue`. In turn, library authors should provide developers with APIs to handle errors without relying on exception handling. When developers have no freedom to decide if an error is exceptional or not, this is considered an anti-pattern.
#### Example
An example of this anti-pattern, as shown below, is using `try/rescue` to deal with file operations:
```elixir
defmodule MyModule do
def print_file(file) do
try do
IO.puts(File.read!(file))
rescue
e -> IO.puts(:stderr, Exception.message(e))
end
end
end
```
```elixir
iex> MyModule.print_file("valid_file")
This is a valid file!
:ok
iex> MyModule.print_file("invalid_file")
could not read file "invalid_file": no such file or directory
:ok
```
#### Refactoring
To refactor this anti-pattern, as shown next, use `File.read/1`, which returns tuples instead of raising when a file cannot be read:
```elixir
defmodule MyModule do
def print_file(file) do
case File.read(file) do
{:ok, binary} -> IO.puts(binary)
{:error, reason} -> IO.puts(:stderr, "could not read file #{file}: #{reason}")
end
end
end
```
This is only possible because the `File` module provides APIs for reading files with tuples as results (`File.read/1`), as well as a version that raises an exception (`File.read!/1`). The bang (exclamation point) is effectively part of [Elixir's naming conventions](naming-conventions.md#trailing-bang-foo).
Library authors are encouraged to follow the same practices. In practice, the bang variant is implemented on top of the non-raising version of the code. For example, `File.read!/1` is implemented as:
```elixir
def read!(path) do
case read(path) do
{:ok, binary} ->
binary
{:error, reason} ->
raise File.Error, reason: reason, action: "read file", path: IO.chardata_to_string(path)
end
end
```
A common practice followed by the community is to make the non-raising version return `{:ok, result}` or `{:error, Exception.t}`. For example, an HTTP client may return `{:ok, %HTTP.Response{}}` on success cases and `{:error, %HTTP.Error{}}` for failures, where `HTTP.Error` is [implemented as an exception](`Kernel.defexception/1`). This makes it convenient for anyone to raise an exception by simply calling `Kernel.raise/1`.
#### Additional remarks
This anti-pattern was formerly known as [Using exceptions for control-flow](https://github.com/lucasvegi/Elixir-Code-Smells#using-exceptions-for-control-flow).
## Primitive obsession
#### Problem
This anti-pattern happens when Elixir basic types (for example, *integer*, *float*, and *string*) are excessively used to carry structured information, rather than creating specific composite data types (for example, *tuples*, *maps*, and *structs*) that can better represent a domain.
#### Example
An example of this anti-pattern is the use of a single *string* to represent an `Address`. An `Address` is a more complex structure than a simple basic (aka, primitive) value.
```elixir
defmodule MyApp do
def extract_postal_code(address) when is_binary(address) do
# Extract postal code with address...
end
def fill_in_country(address) when is_binary(address) do
# Fill in missing country...
end
end
```
While you may receive the `address` as a string from a database, web request, or a third-party, if you find yourself frequently manipulating or extracting information from the string, it is a good indicator you should convert the address into structured data:
Another example of this anti-pattern is using floating numbers to model money and currency, when [richer data structures should be preferred](https://hexdocs.pm/ex_money/).
#### Refactoring
Possible solutions to this anti-pattern is to use maps or structs to model our address. The example below creates an `Address` struct, better representing this domain through a composite type. Additionally, we introduce a `parse/1` function, that converts the string into an `Address`, which will simplify the logic of remainng functions. With this modification, we can extract each field of this composite type individually when needed.
```elixir
defmodule Address do
defstruct [:street, :city, :state, :postal_code, :country]
end
```
```elixir
defmodule MyApp do
def parse(address) when is_binary(address) do
# Returns %Address{}
end
def extract_postal_code(%Address{} = address) do
# Extract postal code with address...
end
def fill_in_country(%Address{} = address) do
# Fill in missing country...
end
end
```
## Unrelated multi-clause function
#### Problem
Using multi-clause functions is a powerful Elixir feature. However, some developers may abuse this feature to group *unrelated* functionality, which is an anti-pattern.
#### Example
A frequent example of this usage of multi-clause functions occurs when developers mix unrelated business logic into the same function definition, in a way that the behaviour of each clause becomes completely distinct from the others. Such functions often have too broad specifications, making it difficult for other developers to understand and maintain them.
Some developers may use documentation mechanisms such as `@doc` annotations to compensate for poor code readability, however the documentation itself may end-up full of conditionals to describe how the function behaves for each different argument combination. This is a good indicator that the clauses are ultimately unrelated.
```elixir
@doc """
Updates a struct.
If given a product, it will...
If given an animal, it will...
"""
def update(%Product{count: count, material: material}) do
# ...
end
def update(%Animal{count: count, skin: skin}) do
# ...
end
```
If updating an animal is completely different from updating a product and requires a different set of rules, it may be worth splitting those over different functions or even different modules.
#### Refactoring
As shown below, a possible solution to this anti-pattern is to break the business rules that are mixed up in a single unrelated multi-clause function in simple functions. Each function can have a specific name and `@doc`, describing its behavior and parameters received. While this refactoring sounds simple, it can impact the function's callers, so be careful!
```elixir
@doc """
Updates a product.
It will...
"""
def update_product(%Product{count: count, material: material}) do
# ...
end
@doc """
Updates an animal.
It will...
"""
def update_animal(%Animal{count: count, skin: skin}) do
# ...
end
```
These functions may still be implemented with multiple clauses, as long as the clauses group related funtionality. For example, `update_product` could be in practice implemented as follows:
```elixir
def update_product(%Product{count: 0}) do
# ...
end
def update_product(%Product{material: material})
when material in ["metal", "glass"] do
# ...
end
def update_product(%Product{material: material})
when material not in ["metal", "glass"] do
# ...
end
```
You can see this pattern in practice within Elixir itself. The `+/2` operator can add `Integer`s and `Float`s together, but not `String`s, which instead use the `<>/2` operator. In this sense, it is reasonable to handle integers and floats in the same operation, but strings are unrelated enough to deserve their own function.
You will also find examples in Elixir of functions that work with any struct, which would seemingly be an occurrence of this anti-pattern, such as `struct/2`:
```elixir
iex> struct(URI.parse("/foo/bar"), path: "/bar/baz")
%URI{
scheme: nil,
userinfo: nil,
host: nil,
port: nil,
path: "/bar/baz",
query: nil,
fragment: nil
}
```
The difference here is that the `struct/2` function behaves precisely the same for any struct given, therefore there is no question of how the function handles different inputs. If the behaviour is clear and consistent for all inputs, then the anti-pattern does not take place.
## Using application configuration for libraries
#### Problem
The [*application environment*](https://hexdocs.pm/elixir/Application.html#module-the-application-environment) can be used to parameterize global values that can be used in an Elixir system. This mechanism can be very useful and therefore is not considered an anti-pattern by itself. However, library authors should avoid using the application environment to configure their library. The reason is exactly that the application environment is a **global** state, so there can only be a single value for each key in the environment for an application. This makes it impossible for multiple applications depending on the same library to configure the same aspect of the library in different ways.
#### Example
The `DashSplitter` module represents a library that configures the behavior of its functions through the global application environment. These configurations are concentrated in the *config/config.exs* file, shown below:
```elixir
import Config
config :app_config,
parts: 3
import_config "#{config_env()}.exs"
```
One of the functions implemented by the `DashSplitter` library is `split/1`. This function aims to separate a string received via a parameter into a certain number of parts. The character used as a separator in `split/1` is always `"-"` and the number of parts the string is split into is defined globally by the application environment. This value is retrieved by the `split/1` function by calling `Application.fetch_env!/2`, as shown next:
```elixir
defmodule DashSplitter do
def split(string) when is_binary(string) do
parts = Application.fetch_env!(:app_config, :parts) # <= retrieve parameterized value
String.split(string, "-", parts: parts) # <= parts: 3
end
end
```
Due to this parameterized value used by the `DashSplitter` library, all applications dependent on it can only use the `split/1` function with identical behavior about the number of parts generated by string separation. Currently, this value is equal to 3, as we can see in the use examples shown below:
```elixir
iex> DashSplitter.split("Lucas-Francisco-Vegi")
["Lucas", "Francisco", "Vegi"]
iex> DashSplitter.split("Lucas-Francisco-da-Matta-Vegi")
["Lucas", "Francisco", "da-Matta-Vegi"]
```
#### Refactoring
To remove this anti-pattern, this type of configuration should be performed using a parameter passed to the function. The code shown below performs the refactoring of the `split/1` function by accepting [keyword lists](`Keyword`) as a new optional parameter. With this new parameter, it is possible to modify the default behavior of the function at the time of its call, allowing multiple different ways of using `split/2` within the same application:
```elixir
defmodule DashSplitter do
def split(string, opts \\ []) when is_binary(string) and is_list(opts) do
parts = Keyword.get(opts, :parts, 2) # <= default config of parts == 2
String.split(string, "-", parts: parts)
end
end
```
```elixir
iex> DashSplitter.split("Lucas-Francisco-da-Matta-Vegi", [parts: 5])
["Lucas", "Francisco", "da", "Matta", "Vegi"]
iex> DashSplitter.split("Lucas-Francisco-da-Matta-Vegi") #<= default config is used!
["Lucas", "Francisco-da-Matta-Vegi"]
```
Of course, not all uses of the application environment by libraries are incorrect. One example is using configuration to replace a component (or dependency) of a library by another that must behave the exact same. Consider a library that needs to parse CSV files. The library author may pick one package to use as default parser but allow its users to swap to different implementations via the application environment. At the end of the day, choosing a different CSV parser should not change the outcome, and library authors can even enforce this by [defining behaviours](../references/typespecs.md#behaviours) with the exact semantics they expect.
#### Additional remarks: Supervision trees
In practice, libraries may require additional configuration beyond keyword lists. For example, if a library needs to start a supervision tree, how can the user of said library customize its supervision tree? Given the supervision tree itself is global (as it belongs to the library), library authors may be tempted to use the application configuration once more.
One solution is for the library to provide its own child specification, instead of starting the supervision tree itself. This allows the user to start all necessary processes under its own supervision tree, potentially passing custom configuration options during initialization.
You can see this pattern in practice in projects like [Nx](https://github.com/elixir-nx/nx) and [DNS Cluster](https://github.com/phoenixframework/dns_cluster). These libraries require that you list processes under your own supervision tree:
```elixir
children = [
{DNSCluster, query: "my.subdomain"}
]
```
In such cases, if the users of `DNSCluster` need to configure DNSCluster per environment, they can be the ones reading from the application environment, without the library forcing them to:
```elixir
children = [
{DNSCluster, query: Application.get_env(:my_app, :dns_cluster_query) || :ignore}
]
```
Some libraries, such as [Ecto](https://github.com/elixir-ecto/ecto), allow you to pass your application name as an option (called `:otp_app` or similar) and then automatically read the environment from *your* application. While this addresses the issue with the application environment being global, as they read from each individual application, it comes at the cost of some indirection, compared to the example above where users explicitly read their application environment from their own code, whenever desired.
#### Additional remarks: Compile-time configuration
A similar discussion entails compile-time configuration. What if a library author requires some configuration to be provided at compilation time?
Once again, instead of forcing users of your library to provide compile-time configuration, you may want to allow users of your library to generate the code themselves. That's the approach taken by libraries such as [Ecto](https://github.com/elixir-ecto/ecto):
```elixir
defmodule MyApp.Repo do
use Ecto.Repo, adapter: Ecto.Adapters.Postgres
end
```
Instead of forcing developers to share a single repository, Ecto allows its users to define as many repositories as they want. Given the `:adapter` configuration is required at compile-time, it is a required value on `use Ecto.Repo`. If developers want to configure the adapter per environment, then it is their choice:
```elixir
defmodule MyApp.Repo do
use Ecto.Repo, adapter: Application.compile_env(:my_app, :repo_adapter)
end
```
On the other hand, [code generation comes with its own anti-patterns](macro-anti-patterns.md), and must be considered carefully. That's to say: while using the application environment for libraries is discouraged, especially compile-time configuration, in some cases they may be the best option. For example, consider a library needs to parse CSV or JSON files to generate code based on data files. In such cases, it is best to provide reasonable defaults and make them customizable via the application environment, instead of asking each user of your library to generate the exact same code.
#### Additional remarks: Mix tasks
For Mix tasks and related tools, it may be necessary to provide per-project configuration. For example, imagine you have a `:linter` project, which supports setting the output file and the verbosity level. You may choose to configure it through application environment:
```elixir
config :linter,
output_file: "/path/to/output.json",
verbosity: 3
```
However, `Mix` allows tasks to read per-project configuration via `Mix.Project.config/0`. In this case, you can configure the `:linter` directly in the `mix.exs` file:
```elixir
def project do
[
app: :my_app,
version: "1.0.0",
linter: [
output_file: "/path/to/output.json",
verbosity: 3
],
...
]
end
```
Additionally, if a Mix task is available, you can also accept these options as command line arguments (see `OptionParser`):
```bash
mix linter --output-file /path/to/output.json --verbosity 3
```
@@ -0,0 +1,213 @@
# Meta-programming anti-patterns
This document outlines potential anti-patterns related to meta-programming.
## Large code generation
#### Problem
This anti-pattern is related to macros that generate too much code. When a macro generates a large amount of code, it impacts how the compiler and/or the runtime work. The reason for this is that Elixir may have to expand, compile, and execute the code multiple times, which will make compilation slower and the resulting compiled artifacts larger.
#### Example
Imagine you are defining a router for a web application, where you could have macros like `get/2`. On every invocation of the macro (which could be hundreds), the code inside `get/2` will be expanded and compiled, which can generate a large volume of code overall.
```elixir
defmodule Routes do
defmacro get(route, handler) do
quote do
route = unquote(route)
handler = unquote(handler)
if not is_binary(route) do
raise ArgumentError, "route must be a binary"
end
if not is_atom(handler) do
raise ArgumentError, "handler must be a module"
end
@store_route_for_compilation {route, handler}
end
end
end
```
#### Refactoring
To remove this anti-pattern, the developer should simplify the macro, delegating part of its work to other functions. As shown below, by encapsulating the code inside `quote/1` inside the function `__define__/3` instead, we reduce the code that is expanded and compiled on every invocation of the macro, and instead we dispatch to a function to do the bulk of the work.
```elixir
defmodule Routes do
defmacro get(route, handler) do
quote do
Routes.__define__(__MODULE__, unquote(route), unquote(handler))
end
end
def __define__(module, route, handler) do
if not is_binary(route) do
raise ArgumentError, "route must be a binary"
end
if not is_atom(handler) do
raise ArgumentError, "handler must be a module"
end
Module.put_attribute(module, :store_route_for_compilation, {route, handler})
end
end
```
## Unnecessary macros
#### Problem
*Macros* are powerful meta-programming mechanisms that can be used in Elixir to extend the language. While using macros is not an anti-pattern in itself, this meta-programming mechanism should only be used when absolutely necessary. Whenever a macro is used, but it would have been possible to solve the same problem using functions or other existing Elixir structures, the code becomes unnecessarily more complex and less readable. Because macros are more difficult to implement and reason about, their indiscriminate use can compromise the evolution of a system, reducing its maintainability.
#### Example
The `MyMath` module implements the `sum/2` macro to perform the sum of two numbers received as parameters. While this code has no syntax errors and can be executed correctly to get the desired result, it is unnecessarily more complex. By implementing this functionality as a macro rather than a conventional function, the code became less clear:
```elixir
defmodule MyMath do
defmacro sum(v1, v2) do
quote do
unquote(v1) + unquote(v2)
end
end
end
```
```elixir
iex> require MyMath
MyMath
iex> MyMath.sum(3, 5)
8
iex> MyMath.sum(3 + 1, 5 + 6)
15
```
#### Refactoring
To remove this anti-pattern, the developer must replace the unnecessary macro with structures that are simpler to write and understand, such as named functions. The code shown below is the result of the refactoring of the previous example. Basically, the `sum/2` macro has been transformed into a conventional named function. Note that the `require/2` call is no longer needed:
```elixir
defmodule MyMath do
def sum(v1, v2) do # <= The macro became a named function
v1 + v2
end
end
```
```elixir
iex> MyMath.sum(3, 5)
8
iex> MyMath.sum(3+1, 5+6)
15
```
## `use` instead of `import`
#### Problem
Elixir has mechanisms such as `import/1`, `alias/1`, and `use/1` to establish dependencies between modules. Code implemented with these mechanisms does not characterize a smell by itself. However, while the `import/1` and `alias/1` directives have lexical scope and only facilitate a module calling functions of another, the `use/1` directive has a *broader scope*, which can be problematic.
The `use/1` directive allows a module to inject any type of code into another, including propagating dependencies. In this way, using the `use/1` directive makes code harder to read, because to understand exactly what will happen when it references a module, it is necessary to have knowledge of the internal details of the referenced module.
#### Example
The code shown below is an example of this anti-pattern. It defines three modules -- `ModuleA`, `Library`, and `ClientApp`. `ClientApp` is reusing code from the `Library` via the `use/1` directive, but is unaware of its internal details. This makes it harder for the author of `ClientApp` to visualize which modules and functionality are now available within its module. To make matters worse, `Library` also imports `ModuleA`, which defines a `foo/0` function that conflicts with a local function defined in `ClientApp`:
```elixir
defmodule ModuleA do
def foo do
"From Module A"
end
end
```
```elixir
defmodule Library do
defmacro __using__(_opts) do
quote do
import Library
import ModuleA # <= propagating dependencies!
end
end
def from_lib do
"From Library"
end
end
```
```elixir
defmodule ClientApp do
use Library
def foo do
"Local function from client app"
end
def from_client_app do
from_lib() <> " - " <> foo()
end
end
```
When we try to compile `ClientApp`, Elixir detects the conflict and throws the following error:
```text
error: imported ModuleA.foo/0 conflicts with local function
└ client_app.ex:4:
```
#### Refactoring
To remove this anti-pattern, we recommend library authors avoid providing `__using__/1` callbacks whenever it can be replaced by `alias/1` or `import/1` directives. In the following code, we assume `use Library` is no longer available and `ClientApp` was refactored in this way, and with that, the code is clearer and the conflict as previously shown no longer exists:
```elixir
defmodule ClientApp do
import Library
def foo do
"Local function from client app"
end
def from_client_app do
from_lib() <> " - " <> foo()
end
end
```
```elixir
iex> ClientApp.from_client_app()
"From Library - Local function from client app"
```
#### Additional remarks
In situations where you need to do more than importing and aliasing modules, providing `use MyModule` may be necessary, as it provides a common extension point within the Elixir ecosystem.
Therefore, to provide guidance and clarity, we recommend library authors to include an admonition block in their `@moduledoc` that explains how `use MyModule` impacts the developer's code. As an example, the `GenServer` documentation outlines:
> #### `use GenServer` {: .info}
>
> When you `use GenServer`, the `GenServer` module will
> set `@behaviour GenServer` and define a `child_spec/1`
> function, so your module can be used as a child
> in a supervision tree.
Think of this summary as a ["Nutrition facts label"](https://en.wikipedia.org/wiki/Nutrition_facts_label) for code generation. Make sure to only list changes made to the public API of the module. For example, if `use Library` sets an internal attribute called `@_some_module_info` and this attribute is never meant to be public, avoid documenting it in the nutrition facts.
For convenience, the markup notation to generate the admonition block above is this:
```markdown
> #### `use GenServer` {: .info}
>
> When you `use GenServer`, the `GenServer` module will
> set `@behaviour GenServer` and define a `child_spec/1`
> function, so your module can be used as a child
> in a supervision tree.
```
@@ -0,0 +1,353 @@
# Process-related anti-patterns
This document outlines potential anti-patterns related to processes and process-based abstractions.
## Code organization by process
#### Problem
This anti-pattern refers to code that is unnecessarily organized by processes. A process itself does not represent an anti-pattern, but it should only be used to model runtime properties (such as concurrency, access to shared resources, error isolation, etc). When you use a process for code organization, it can create bottlenecks in the system.
#### Example
An example of this anti-pattern, as shown below, is a module that implements arithmetic operations (like `add` and `subtract`) by means of a `GenServer` process. If the number of calls to this single process grows, this code organization can compromise the system performance, therefore becoming a bottleneck.
```elixir
defmodule Calculator do
@moduledoc """
Calculator that performs basic arithmetic operations.
This code is unnecessarily organized in a GenServer process.
"""
use GenServer
def add(a, b, pid) do
GenServer.call(pid, {:add, a, b})
end
def subtract(a, b, pid) do
GenServer.call(pid, {:subtract, a, b})
end
@impl GenServer
def init(init_arg) do
{:ok, init_arg}
end
@impl GenServer
def handle_call({:add, a, b}, _from, state) do
{:reply, a + b, state}
end
def handle_call({:subtract, a, b}, _from, state) do
{:reply, a - b, state}
end
end
```
```elixir
iex> {:ok, pid} = GenServer.start_link(Calculator, :init)
{:ok, #PID<0.132.0>}
iex> Calculator.add(1, 5, pid)
6
iex> Calculator.subtract(2, 3, pid)
-1
```
#### Refactoring
In Elixir, as shown next, code organization must be done only through modules and functions. Whenever possible, a library should not impose specific behavior (such as parallelization) on its users. It is better to delegate this behavioral decision to the developers of clients, thus increasing the potential for code reuse of a library.
```elixir
defmodule Calculator do
def add(a, b) do
a + b
end
def subtract(a, b) do
a - b
end
end
```
```elixir
iex> Calculator.add(1, 5)
6
iex> Calculator.subtract(2, 3)
-1
```
## Scattered process interfaces
#### Problem
In Elixir, the use of an `Agent`, a `GenServer`, or any other process abstraction is not an anti-pattern in itself. However, when the responsibility for direct interaction with a process is spread throughout the entire system, it can become problematic. This bad practice can increase the difficulty of code maintenance and make the code more prone to bugs.
#### Example
The following code seeks to illustrate this anti-pattern. The responsibility for interacting directly with the `Agent` is spread across four different modules (`A`, `B`, `C`, and `D`).
```elixir
defmodule A do
def update(process) do
# Some other code...
Agent.update(process, fn _list -> 123 end)
end
end
```
```elixir
defmodule B do
def update(process) do
# Some other code...
Agent.update(process, fn content -> %{a: content} end)
end
end
```
```elixir
defmodule C do
def update(process) do
# Some other code...
Agent.update(process, fn content -> [:atom_value | content] end)
end
end
```
```elixir
defmodule D do
def get(process) do
# Some other code...
Agent.get(process, fn content -> content end)
end
end
```
This spreading of responsibility can generate duplicated code and make code maintenance more difficult. Also, due to the lack of control over the format of the shared data, complex composed data can be shared. This freedom to use any format of data is dangerous and can induce developers to introduce bugs.
```elixir
# start an agent with initial state of an empty list
iex> {:ok, agent} = Agent.start_link(fn -> [] end)
{:ok, #PID<0.135.0>}
# many data formats (for example, List, Map, Integer, Atom) are
# combined through direct access spread across the entire system
iex> A.update(agent)
iex> B.update(agent)
iex> C.update(agent)
# state of shared information
iex> D.get(agent)
[:atom_value, %{a: 123}]
```
For a `GenServer` and other behaviours, this anti-pattern will manifest when scattering calls to `GenServer.call/3` and `GenServer.cast/2` throughout multiple modules, instead of encapsulating all the interaction with the `GenServer` in a single place.
#### Refactoring
Instead of spreading direct access to a process abstraction, such as `Agent`, over many places in the code, it is better to refactor this code by centralizing the responsibility for interacting with a process in a single module. This refactoring improves maintainability by removing duplicated code; it also allows you to limit the accepted format for shared data, reducing bug-proneness. As shown below, the module `Foo.Bucket` is centralizing the responsibility for interacting with the `Agent`. Any other place in the code that needs to access shared data must now delegate this action to `Foo.Bucket`. Also, `Foo.Bucket` now only allows data to be shared in `Map` format.
```elixir
defmodule Foo.Bucket do
use Agent
def start_link(_opts) do
Agent.start_link(fn -> %{} end)
end
def get(bucket, key) do
Agent.get(bucket, &Map.get(&1, key))
end
def put(bucket, key, value) do
Agent.update(bucket, &Map.put(&1, key, value))
end
end
```
The following are examples of how to delegate access to shared data (provided by an `Agent`) to `Foo.Bucket`.
```elixir
# start an agent through `Foo.Bucket`
iex> {:ok, bucket} = Foo.Bucket.start_link(%{})
{:ok, #PID<0.114.0>}
# add shared values to the keys `milk` and `beer`
iex> Foo.Bucket.put(bucket, "milk", 3)
iex> Foo.Bucket.put(bucket, "beer", 7)
# access shared data of specific keys
iex> Foo.Bucket.get(bucket, "beer")
7
iex> Foo.Bucket.get(bucket, "milk")
3
```
#### Additional remarks
This anti-pattern was formerly known as [Agent obsession](https://github.com/lucasvegi/Elixir-Code-Smells/tree/main#agent-obsession).
## Sending unnecessary data
#### Problem
Sending a message to a process can be an expensive operation if the message is big enough. That's because that message will be fully copied to the receiving process, which may be CPU and memory intensive. This is due to Erlang's "share nothing" architecture, where each process has its own memory, which simplifies and speeds up garbage collection.
This is more obvious when using `send/2`, `GenServer.call/3`, or the initial data in `GenServer.start_link/3`. Notably this also happens when using `spawn/1`, `Task.async/1`, `Task.async_stream/3`, and so on. It is more subtle here as the anonymous function passed to these functions captures the variables it references, and all captured variables will be copied over. By doing this, you can accidentally send way more data to a process than you actually need.
#### Example
Imagine you were to implement some simple reporting of IP addresses that made requests against your application. You want to do this asynchronously and not block processing, so you decide to use `spawn/1`. It may seem like a good idea to hand over the whole connection because we might need more data later. However passing the connection results in copying a lot of unnecessary data like the request body, params, etc.
```elixir
# log_request_ip send the ip to some external service
spawn(fn -> log_request_ip(conn) end)
```
This problem also occurs when accessing only the relevant parts:
```elixir
spawn(fn -> log_request_ip(conn.remote_ip) end)
```
This will still copy over all of `conn`, because the `conn` variable is being captured inside the spawned function. The function then extracts the `remote_ip` field, but only after the whole `conn` has been copied over.
`send/2` and the `GenServer` APIs also rely on message passing. In the example below, the `conn` is once again copied to the underlying `GenServer`:
```elixir
GenServer.cast(pid, {:report_ip_address, conn})
```
#### Refactoring
This anti-pattern has many potential remedies:
* Limit the data you send to the absolute necessary minimum instead of sending an entire struct. For example, don't send an entire `conn` struct if all you need is a couple of fields.
* If the only process that needs data is the one you are sending to, consider making the process fetch that data instead of passing it.
* Some abstractions, such as [`:persistent_term`](https://www.erlang.org/doc/man/persistent_term.html), allows you to share data between processes, as long as such data changes infrequently.
In our case, limiting the input data is a reasonable strategy. If all we need *right now* is the IP address, then let's only work with that and make sure we're only passing the IP address into the closure, like so:
```elixir
ip_address = conn.remote_ip
spawn(fn -> log_request_ip(ip_address) end)
```
Or in the `GenServer` case:
```elixir
GenServer.cast(pid, {:report_ip_address, conn.remote_ip})
```
## Unsupervised processes
#### Problem
In Elixir, creating a process outside a supervision tree is not an anti-pattern in itself. However, when you spawn many long-running processes outside of supervision trees, this can make visibility and monitoring of these processes difficult, preventing developers from fully controlling their applications.
#### Example
The following code example seeks to illustrate a library responsible for maintaining a numerical `Counter` through a `GenServer` process *outside a supervision tree*. Multiple counters can be created simultaneously by a client (one process for each counter), making these *unsupervised* processes difficult to manage. This can cause problems with the initialization, restart, and shutdown of a system.
```elixir
defmodule Counter do
@moduledoc """
Global counter implemented through a GenServer process.
"""
use GenServer
@doc "Starts a counter process."
def start_link(opts \\ []) do
initial_value = Keyword.get(opts, :initial_value, 0)
name = Keyword.get(opts, :name, __MODULE__)
GenServer.start(__MODULE__, initial_value, name: name)
end
@doc "Gets the current value of the given counter."
def get(pid_name \\ __MODULE__) do
GenServer.call(pid_name, :get)
end
@doc "Bumps the value of the given counter."
def bump(pid_name \\ __MODULE__, value) do
GenServer.call(pid_name, {:bump, value})
end
@impl true
def init(counter) do
{:ok, counter}
end
@impl true
def handle_call(:get, _from, counter) do
{:reply, counter, counter}
end
def handle_call({:bump, value}, _from, counter) do
{:reply, counter, counter + value}
end
end
```
```elixir
iex> Counter.start_link()
{:ok, #PID<0.115.0>}
iex> Counter.get()
0
iex> Counter.start_link(initial_value: 15, name: :other_counter)
{:ok, #PID<0.120.0>}
iex> Counter.get(:other_counter)
15
iex> Counter.bump(:other_counter, -3)
12
iex> Counter.bump(Counter, 7)
7
```
#### Refactoring
To ensure that clients of a library have full control over their systems, regardless of the number of processes used and the lifetime of each one, all processes must be started inside a supervision tree. As shown below, this code uses a `Supervisor` as a supervision tree. When this Elixir application is started, two different counters (`Counter` and `:other_counter`) are also started as child processes of the `Supervisor` named `App.Supervisor`. One is initialized with `0`, the other with `15`. By means of this supervision tree, it is possible to manage the lifecycle of all child processes (stopping or restarting each one), improving the visibility of the entire app.
```elixir
defmodule SupervisedProcess.Application do
use Application
@impl true
def start(_type, _args) do
children = [
# With the default values for counter and name
Counter,
# With custom values for counter, name, and a custom ID
Supervisor.child_spec(
{Counter, name: :other_counter, initial_value: 15},
id: :other_counter
)
]
Supervisor.start_link(children, strategy: :one_for_one, name: App.Supervisor)
end
end
```
```elixir
iex> Supervisor.count_children(App.Supervisor)
%{active: 2, specs: 2, supervisors: 0, workers: 2}
iex> Counter.get(Counter)
0
iex> Counter.get(:other_counter)
15
iex> Counter.bump(Counter, 7)
7
iex> Supervisor.terminate_child(App.Supervisor, Counter)
iex> Supervisor.count_children(App.Supervisor) # Only one active child
%{active: 1, specs: 2, supervisors: 0, workers: 2}
iex> Counter.get(Counter) # The process was terminated
** (EXIT) no process: the process is not alive...
iex> Supervisor.restart_child(App.Supervisor, Counter)
iex> Counter.get(Counter) # After the restart, this process can be used again
0
```
@@ -0,0 +1,43 @@
# What are anti-patterns?
Anti-patterns describe common mistakes or indicators of problems in code.
They are also known as "code smells".
The goal of these guides is to document potential anti-patterns found in Elixir software
and teach developers how to identify them and their pitfalls. If an existing piece
of code matches an anti-pattern, it does not mean your code must be rewritten.
Sometimes, even if a snippet matches a potential anti-pattern and its limitations,
it may be the best approach to the problem at hand. No codebase is free of anti-patterns
and one should not aim to remove all of them.
The anti-patterns in these guides are broken into 4 main categories:
* **Code-related anti-patterns:** related to your code and particular
language idioms and features;
* **Design-related anti-patterns:** related to your modules, functions,
and the role they play within a codebase;
* **Process-related anti-patterns:** related to processes and process-based
abstractions;
* **Meta-programming anti-patterns:** related to meta-programming.
Each anti-pattern is documented using the following structure:
* **Name:** Unique identifier of the anti-pattern. This name is important to facilitate
communication between developers;
* **Problem:** How the anti-pattern can harm code quality and what impacts this can have
for developers;
* **Example:** Code and textual descriptions to illustrate the occurrence of the anti-pattern;
* **Refactoring:** Ways to change your code to improve its qualities. Examples of refactored
code are presented to illustrate these changes.
An additional section with "Additional Remarks" may be provided. Those may include known scenarios where the anti-pattern does not apply.
The initial catalog of anti-patterns was proposed by Lucas Vegi and Marco Tulio Valente, from [ASERG/DCC/UFMG](http://aserg.labsoft.dcc.ufmg.br/). For more info, see [Understanding Code Smells in Elixir Functional Language](https://github.com/lucasvegi/Elixir-Code-Smells/blob/main/etc/2023-emse-code-smells-elixir.pdf) and [the associated code repository](https://github.com/lucasvegi/Elixir-Code-Smells).
Additionally, the Security Working Group of the [Erlang Ecosystem Foundation](https://erlef.github.io/security-wg/) publishes [documents with security resources and best-practices of both Erlang and Elixir, including detailed guides for web applications](https://erlef.github.io/security-wg/).
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,239 @@
# alias, require, and import
In order to facilitate software reuse, Elixir provides three directives (`alias`, `require` and `import`) plus a macro called `use` summarized below:
```elixir
# Alias the module so it can be called as Bar instead of Foo.Bar
alias Foo.Bar, as: Bar
# Require the module in order to use its macros
require Foo
# Import functions from Foo so they can be called without the `Foo.` prefix
import Foo
# Invokes the custom code defined in Foo as an extension point
use Foo
```
We are going to explore them in detail now. Keep in mind the first three are called directives because they have *lexical scope*, while `use` is a common extension point that allows the used module to inject code.
## alias
`alias` allows you to set up aliases for any given module name.
Imagine a module uses a specialized list implemented in `Math.List`. The `alias` directive allows referring to `Math.List` just as `List` within the module definition:
```elixir
defmodule Stats do
alias Math.List, as: List
# In the remaining module definition List expands to Math.List.
end
```
The original `List` can still be accessed within `Stats` by the fully-qualified name `Elixir.List`.
> All modules defined in Elixir are defined inside the main `Elixir` namespace, such as `Elixir.String`. However, for convenience, you can omit "Elixir." when referencing them.
Aliases are frequently used to define shortcuts. In fact, calling `alias` without an `:as` option sets the alias automatically to the last part of the module name, for example:
```elixir
alias Math.List
```
Is the same as:
```elixir
alias Math.List, as: List
```
Note that `alias` is *lexically scoped*, which allows you to set aliases inside specific functions:
```elixir
defmodule Math do
def plus(a, b) do
alias Math.List
# ...
end
def minus(a, b) do
# ...
end
end
```
In the example above, since we are invoking `alias` inside the function `plus/2`, the alias will be valid only inside the function `plus/2`. `minus/2` won't be affected at all.
## require
Elixir provides macros as a mechanism for meta-programming (writing code that generates code). Macros are expanded at compile time.
Public functions in modules are globally available, but in order to use macros, you need to opt-in by requiring the module they are defined in.
```elixir
iex> Integer.is_odd(3)
** (UndefinedFunctionError) function Integer.is_odd/1 is undefined or private. However, there is a macro with the same name and arity. Be sure to require Integer if you intend to invoke this macro
(elixir) Integer.is_odd(3)
iex> require Integer
Integer
iex> Integer.is_odd(3)
true
```
In Elixir, `Integer.is_odd/1` is defined as a macro so that it can be used as a guard. This means that, in order to invoke `Integer.is_odd/1`, we need to first require the `Integer` module.
Note that like the `alias` directive, `require` is also lexically scoped. We will talk more about macros in a later chapter.
## import
We use `import` whenever we want to access functions or macros from other modules without using the fully-qualified name. Note we can only import public functions, as private functions are never accessible externally.
For example, if we want to use the `duplicate/2` function from the `List` module several times, we can import it:
```elixir
iex> import List, only: [duplicate: 2]
List
iex> duplicate(:ok, 3)
[:ok, :ok, :ok]
```
We imported only the function `duplicate` (with arity 2) from `List`. Although `:only` is optional, its usage is recommended in order to avoid importing all the functions of a given module inside the current scope. `:except` could also be given as an option in order to import everything in a module except a list of functions.
Note that `import` is *lexically scoped* too. This means that we can import specific macros or functions inside function definitions:
```elixir
defmodule Math do
def some_function do
import List, only: [duplicate: 2]
duplicate(:ok, 10)
end
end
```
In the example above, the imported `List.duplicate/2` is only visible within that specific function. `duplicate/2` won't be available in any other function in that module (or any other module for that matter).
While `import`s can be a useful for frameworks and libraries to build abstractions, developers should generally prefer `alias` to `import` on their own codebases, as aliases make the origin of the function being invoked clearer.
## use
The `use` macro is frequently used as an extension point. This means that, when you `use` a module `FooBar`, you allow that module to inject *any* code in the current module, such as importing itself or other modules, defining new functions, setting a module state, etc.
For example, in order to write tests using the ExUnit framework, a developer should use the `ExUnit.Case` module:
```elixir
defmodule AssertionTest do
use ExUnit.Case, async: true
test "always pass" do
assert true
end
end
```
Behind the scenes, `use` requires the given module and then calls the `__using__/1` callback on it allowing the module to inject some code into the current context. Some modules (for example, the above `ExUnit.Case`, but also `Supervisor` and `GenServer`) use this mechanism to populate your module with some basic behaviour, which your module is intended to override or complete.
Generally speaking, the following module:
```elixir
defmodule Example do
use Feature, option: :value
end
```
is compiled into
```elixir
defmodule Example do
require Feature
Feature.__using__(option: :value)
end
```
Since `use` allows any code to run, we can't really know the side-effects of using a module without reading its documentation. Therefore use this function with care and only if strictly required. Don't use `use` where an `import` or `alias` would do.
## Understanding Aliases
At this point, you may be wondering: what exactly is an Elixir alias and how is it represented?
An alias in Elixir is a capitalized identifier (like `String`, `Keyword`, etc) which is converted to an atom during compilation. For instance, the `String` alias translates by default to the atom `:"Elixir.String"`:
```elixir
iex> is_atom(String)
true
iex> to_string(String)
"Elixir.String"
iex> :"Elixir.String" == String
true
```
By using the `alias/2` directive, we are changing the atom the alias expands to.
Aliases expand to atoms because in the Erlang Virtual Machine (and consequently Elixir) modules are always represented by atoms:
```elixir
iex> List.flatten([1, [2], 3])
[1, 2, 3]
iex> :"Elixir.List".flatten([1, [2], 3])
[1, 2, 3]
```
That's the mechanism we use to call Erlang modules:
```elixir
iex> :lists.flatten([1, [2], 3])
[1, 2, 3]
```
## Module nesting
Now that we have talked about aliases, we can talk about nesting and how it works in Elixir. Consider the following example:
```elixir
defmodule Foo do
defmodule Bar do
end
end
```
The example above will define two modules: `Foo` and `Foo.Bar`. The second can be accessed as `Bar` inside `Foo` as long as they are in the same lexical scope.
If, later, the `Bar` module is moved outside the `Foo` module definition, it must be referenced by its full name (`Foo.Bar`) or an alias must be set using the `alias` directive discussed above.
**Note**: in Elixir, you don't have to define the `Foo` module before being able to define the `Foo.Bar` module, as they are effectively independent. The above could also be written as:
```elixir
defmodule Foo.Bar do
end
defmodule Foo do
alias Foo.Bar
# Can still access it as `Bar`
end
```
Aliasing a nested module does not bring parent modules into scope. Consider the following example:
```elixir
defmodule Foo do
defmodule Bar do
defmodule Baz do
end
end
end
alias Foo.Bar.Baz
# The module `Foo.Bar.Baz` is now available as `Baz`
# However, the module `Foo.Bar` is *not* available as `Bar`
```
As we will see in later chapters, aliases also play a crucial role in macros, to guarantee they are hygienic.
## Multi alias/import/require/use
It is possible to `alias`, `import`, `require`, or `use` multiple modules at once. This is particularly useful once we start nesting modules, which is very common when building Elixir applications. For example, imagine you have an application where all modules are nested under `MyApp`, you can alias the modules `MyApp.Foo`, `MyApp.Bar` and `MyApp.Baz` at once as follows:
```elixir
alias MyApp.{Foo, Bar, Baz}
```
With this, we have finished our tour of Elixir modules. The next topic to cover is module attributes.
@@ -0,0 +1,130 @@
# Anonymous functions
Anonymous functions allow us to store and pass executable code around as if it was an integer or a string. Let's learn more.
## Defining anonymous functions
Anonymous functions in Elixir are delimited by the keywords `fn` and `end`:
```elixir
iex> add = fn a, b -> a + b end
#Function<12.71889879/2 in :erl_eval.expr/5>
iex> add.(1, 2)
3
iex> is_function(add)
true
```
In the example above, we defined an anonymous function that receives two arguments, `a` and `b`, and returns the result of `a + b`. The arguments are always on the left-hand side of `->` and the code to be executed on the right-hand side. The anonymous function is stored in the variable `add`.
We can invoke anonymous functions by passing arguments to it. Note that a dot (`.`) between the variable and parentheses is required to invoke an anonymous function. The dot makes it clear when you are calling an anonymous function, stored in the variable `add`, opposed to a function named `add/2`. For example, if you have an anonymous function stored in the variable `is_atom`, there is no ambiguity between `is_atom.(:foo)` and `is_atom(:foo)`. If both used the same `is_atom(:foo)` syntax, the only way to know the actual behaviour of `is_atom(:foo)` would be by scanning all code thus far for a possible definition of the `is_atom` variable. This scanning hurts maintainability as it requires developers to track additional context in their head when reading and writing code.
Anonymous functions in Elixir are also identified by the number of arguments they receive. We can check if a function is of any given arity by using `is_function/2`:
```elixir
# check if add is a function that expects exactly 2 arguments
iex> is_function(add, 2)
true
# check if add is a function that expects exactly 1 argument
iex> is_function(add, 1)
false
```
## Closures
Anonymous functions can also access variables that are in scope when the function is defined. This is typically referred to as closures, as they close over their scope. Let's define a new anonymous function that uses the `add` anonymous function we have previously defined:
```elixir
iex> double = fn a -> add.(a, a) end
#Function<6.71889879/1 in :erl_eval.expr/5>
iex> double.(2)
4
```
A variable assigned inside a function does not affect its surrounding environment:
```elixir
iex> x = 42
42
iex> (fn -> x = 0 end).()
0
iex> x
42
```
## Clauses and guards
Similar to `case/2`, we can pattern match on the arguments of anonymous functions as well as define multiple clauses and guards:
```elixir
iex> f = fn
...> x, y when x > 0 -> x + y
...> x, y -> x * y
...> end
#Function<12.71889879/2 in :erl_eval.expr/5>
iex> f.(1, 3)
4
iex> f.(-1, 3)
-3
```
The number of arguments in each anonymous function clause needs to be the same, otherwise an error is raised.
```elixir
iex> f2 = fn
...> x, y when x > 0 -> x + y
...> x, y, z -> x * y + z
...> end
** (CompileError) iex:1: cannot mix clauses with different arities in anonymous functions
```
## The capture operator
Throughout this guide, we have been using the notation `name/arity` to refer to functions. It happens that this notation can actually be used to capture an existing function into a data-type we can pass around, similar to how anonymous functions behave.
```elixir
iex> fun = &is_atom/1
&:erlang.is_atom/1
iex> is_function(fun)
true
iex> fun.(:hello)
true
iex> fun.(123)
false
```
As you can see, once a function is captured, we can pass it as argument or invoke it using the anonymous function notation. The returned value above also hints we can capture functions defined in modules:
```elixir
iex> fun = &String.length/1
&String.length/1
iex> fun.("hello")
5
```
You can also capture operators:
```elixir
iex> add = &+/2
&:erlang.+/2
iex> add.(1, 2)
3
```
The capture syntax can also be used as a shortcut for creating functions. This is handy when you want to create functions that are mostly wrapping existing functions or operators:
```elixir
iex> fun = &(&1 + 1)
#Function<6.71889879/1 in :erl_eval.expr/5>
iex> fun.(1)
2
iex> fun2 = &"Good #{&1}"
#Function<6.127694169/1 in :erl_eval.expr/5>
iex> fun2.("morning")
"Good morning"
```
The `&1` represents the first argument passed into the function. `&(&1 + 1)` above is exactly the same as `fn x -> x + 1 end`. You can read more about the capture operator `&` in [its documentation](`&/1`).
Next let's revisit some of the data-types we learned in the past and dig deeper into how they work.
@@ -0,0 +1,330 @@
# Basic types
In this chapter we will learn more about Elixir basic types: integers, floats, booleans, atoms, and strings. Other data types, such as lists and tuples, will be explored in the next chapter.
```elixir
iex> 1 # integer
iex> 0x1F # integer
iex> 1.0 # float
iex> true # boolean
iex> :atom # atom / symbol
iex> "elixir" # string
iex> [1, 2, 3] # list
iex> {1, 2, 3} # tuple
```
## Basic arithmetic
Open up `iex` and type the following expressions:
```elixir
iex> 1 + 2
3
iex> 5 * 5
25
iex> 10 / 2
5.0
```
Notice that `10 / 2` returned a float `5.0` instead of an integer `5`. This is expected. In Elixir, the operator `/` always returns a float. If you want to do integer division or get the division remainder, you can invoke the `div` and `rem` functions:
```elixir
iex> div(10, 2)
5
iex> div 10, 2
5
iex> rem 10, 3
1
```
Notice that Elixir allows you to drop the parentheses when invoking functions that expect one or more arguments. This feature gives a cleaner syntax when writing declarations and control-flow constructs. However, Elixir developers generally prefer to use parentheses.
Elixir also supports shortcut notations for entering binary, octal, and hexadecimal numbers:
```elixir
iex> 0b1010
10
iex> 0o777
511
iex> 0x1F
31
```
Float numbers require a dot followed by at least one digit and also support `e` for scientific notation:
```elixir
iex> 1.0
1.0
iex> 1.0e-10
1.0e-10
```
Floats in Elixir are 64-bit precision.
You can invoke the `round` function to get the closest integer to a given float, or the `trunc` function to get the integer part of a float.
```elixir
iex> round(3.58)
4
iex> trunc(3.58)
3
```
Finally, we work with different data types, we will learn Elixir provides several predicate functions to check for the type of a value. For example, the `is_integer` can be used to check if a value is an integer or not:
```elixir
iex> is_integer(1)
true
iex> is_integer(2.0)
false
```
You can also use `is_float` or `is_number` to check, respectively, if an argument is a float, or either an integer or float.
## Identifying functions and documentation
Before we move on to the next data type, let's talk about how Elixir identifies functions.
Functions in Elixir are identified by both their name and their arity. The arity of a function describes the number of arguments that the function takes. From this point on we will use both the function name and its arity to describe functions throughout the documentation. `trunc/1` identifies the function which is named `trunc` and takes `1` argument, whereas `trunc/2` identifies a different (nonexistent) function with the same name but with an arity of `2`.
We can also use this syntax to access documentation. The Elixir shell defines the `h` function, which you can use to access documentation for any function. For example, typing `h trunc/1` is going to print the documentation for the `trunc/1` function:
```elixir
iex> h trunc/1
def trunc()
Returns the integer part of number.
```
`h trunc/1` works because it is defined in the `Kernel` module. All functions in the `Kernel` module are automatically imported into our namespace. Most often you will also include the module name when looking up for documentation for a given function:
```elixir
iex> h Kernel.trunc/1
def trunc()
Returns the integer part of number.
```
You can use the module+function to lookup for anything, including operators (try `h Kernel.+/2`). Invoking `h` without arguments displays the documentation for `IEx.Helpers`, which is where `h` and other functionality is defined.
## Booleans and `nil`
Elixir supports `true` and `false` as booleans:
```elixir
iex> true
true
iex> true == false
false
```
Elixir also provides three boolean operators: `or/2`, `and/2`, and `not/1`. These operators are strict in the sense that they expect something that evaluates to a boolean (`true` or `false`) as their first argument:
```elixir
iex> true and true
true
iex> false or is_boolean(true)
true
```
Providing a non-boolean will raise an exception:
```elixir
iex> 1 and true
** (BadBooleanError) expected a boolean on left-side of "and", got: 1
```
`or` and `and` are short-circuit operators. They only execute the right side if the left side is not enough to determine the result:
```elixir
iex> false and raise("This error will never be raised")
false
iex> true or raise("This error will never be raised")
true
```
Elixir also provides the concept of `nil`, to indicate the absence of a value, and a set of logical operators that also manipulate `nil`: `||/2`, `&&/2`, and `!/1`. For these operators, `false` and `nil` are considered "falsy", all other values are considered "truthy":
```elixir
# or
iex> 1 || true
1
iex> false || 11
11
# and
iex> nil && 13
nil
iex> true && 17
17
# not
iex> !true
false
iex> !1
false
iex> !nil
true
```
## Atoms
An atom is a constant whose value is its own name. Some other languages call these symbols. They are often useful to enumerate over distinct values, such as:
```elixir
iex> :apple
:apple
iex> :orange
:orange
iex> :watermelon
:watermelon
```
Atoms are equal if their names are equal.
```elixir
iex> :apple == :apple
true
iex> :apple == :orange
false
```
Often they are used to express the state of an operation, by using values such as `:ok` and `:error`.
The booleans `true` and `false` are also atoms:
```elixir
iex> true == :true
true
iex> is_atom(false)
true
iex> is_boolean(:false)
true
```
Elixir allows you to skip the leading `:` for the atoms `false`, `true` and `nil`.
## Strings
Strings in Elixir are delimited by double quotes, and they are encoded in UTF-8:
```elixir
iex> "hellö"
"hellö"
```
> Note: if you are running on Windows, there is a chance your terminal does not use UTF-8 by default. You can change the encoding of your current session by running `chcp 65001` before entering IEx.
You can concatenate two strings with the `<>/2` operator:
```elixir
iex> "hello " <> "world!"
"hello world!"
```
Elixir also supports string interpolation:
```elixir
iex> string = "world"
iex> "hello #{string}!"
"hello world!"
```
String concatenation requires both sides to be strings but interpolation supports any data type that may be converted to a string:
```elixir
iex> number = 42
iex> "i am #{number} years old!"
"i am 42 years old!"
```
Strings can have line breaks in them. You can introduce them using escape sequences:
```elixir
iex> "hello
...> world"
"hello\nworld"
iex> "hello\nworld"
"hello\nworld"
```
You can print a string using the `IO.puts/1` function from the `IO` module:
```elixir
iex> IO.puts("hello\nworld")
hello
world
:ok
```
Notice that the `IO.puts/1` function returns the atom `:ok` after printing.
Strings in Elixir are represented internally by contiguous sequences of bytes known as binaries:
```elixir
iex> is_binary("hellö")
true
```
We can also get the number of bytes in a string:
```elixir
iex> byte_size("hellö")
6
```
Notice that the number of bytes in that string is 6, even though it has 5 graphemes. That's because the grapheme "ö" takes 2 bytes to be represented in UTF-8. We can get the actual length of the string, based on the number of graphemes, by using the `String.length/1` function:
```elixir
iex> String.length("hellö")
5
```
The `String` module contains a bunch of functions that operate on strings as defined in the Unicode standard:
```elixir
iex> String.upcase("hellö")
"HELLÖ"
```
## Structural comparison
Elixir also provides `==`, `!=`, `<=`, `>=`, `<` and `>` as comparison operators. We can compare numbers:
```elixir
iex> 1 == 1
true
iex> 1 != 2
true
iex> 1 < 2
true
```
But also atoms, strings, booleans, etc:
```elixir
iex> "foo" == "foo"
true
iex> "foo" == "bar"
false
```
Integers and floats compare the same if they have the same value:
```elixir
iex> 1 == 1.0
true
iex> 1 == 2.0
false
```
However, you can use the strict comparison operator `===` and `!==` if you want to distinguish between integers and floats (that's the only difference between these operators):
```elixir
iex> 1 === 1.0
false
```
The comparison operators in Elixir can compare across any data type. We say these operators perform _structural comparison_. For more information, you can read our documentation on [Structural vs Semantic comparisons](`Kernel#module-structural-comparison`).
Elixir also provides data-types for expressing collections, such as lists and tuples, which we learn next. When we talk about concurrency and fault-tolerance via processes, we will also discuss ports, pids, and references, but that will come on later chapters. Let's move forward.
@@ -0,0 +1,300 @@
# Binaries, strings, and charlists
In ["Basic types"](basic-types.md), we learned a bit about strings and we used the `is_binary/1` function for checks:
```elixir
iex> string = "hello"
"hello"
iex> is_binary(string)
true
```
In this chapter, we will gain clarity on what exactly binaries are, how they relate to strings, and what single-quoted values, `'like this'`, mean in Elixir. Although strings are one of the most common data types in computer languages, they are subtly complex and are often misunderstood. To understand strings in Elixir, we have to educate ourselves about [Unicode](https://en.wikipedia.org/wiki/Unicode) and character encodings, specifically the [UTF-8](https://en.wikipedia.org/wiki/UTF-8) encoding.
## Unicode and Code Points
In order to facilitate meaningful communication between computers across multiple languages, a standard is required so that the ones and zeros on one machine mean the same thing when they are transmitted to another. The [Unicode Standard](https://unicode.org/standard/standard.html) acts as an official registry of virtually all the characters we know: this includes characters from classical and historical texts, emoji, and formatting and control characters as well.
Unicode organizes all of the characters in its repertoire into code charts, and each character is given a unique numerical index. This numerical index is known as a [Code Point](https://en.wikipedia.org/wiki/Code_point).
In Elixir you can use a `?` in front of a character literal to reveal its code point:
```elixir
iex> ?a
97
iex> ?ł
322
```
Note that most Unicode code charts will refer to a code point by its hexadecimal (hex) representation, e.g. `97` translates to `0061` in hex, and we can represent any Unicode character in an Elixir string by using the `\uXXXX` notation and the hex representation of its code point number:
```elixir
iex> "\u0061" == "a"
true
iex> 0x0061 = 97 = ?a
97
```
The hex representation will also help you look up information about a code point, e.g. [https://codepoints.net/U+0061](https://codepoints.net/U+0061) has a data sheet all about the lower case `a`, a.k.a. code point 97.
## UTF-8 and Encodings
Now that we understand what the Unicode standard is and what code points are, we can finally talk about encodings. Whereas the code point is **what** we store, an encoding deals with **how** we store it: encoding is an implementation. In other words, we need a mechanism to convert the code point numbers into bytes so they can be stored in memory, written to disk, etc.
Elixir uses UTF-8 to encode its strings, which means that code points are encoded as a series of 8-bit bytes. UTF-8 is a **variable width** character encoding that uses one to four bytes to store each code point. It is capable of encoding all valid Unicode code points. Let's see an example:
```elixir
iex> string = "héllo"
"héllo"
iex> String.length(string)
5
iex> byte_size(string)
6
```
Although the string above has 5 characters, it uses 6 bytes, as two bytes are used to represent the character `é`.
> Note: if you are running on Windows, there is a chance your terminal does not use UTF-8 by default. You can change the encoding of your current session by running `chcp 65001` before entering `iex` (`iex.bat`).
Besides defining characters, UTF-8 also provides a notion of graphemes. Graphemes may consist of multiple characters that are often perceived as one. For example, the [woman firefighter emoji](https://emojipedia.org/woman-firefighter/) is represented as the combination of three characters: the woman emoji (👩), a hidden zero-width joiner, and the fire engine emoji (🚒):
```elixir
iex> String.codepoints("👩‍🚒")
["👩", "‍", "🚒"]
iex> String.graphemes("👩‍🚒")
["👩‍🚒"]
```
However, Elixir is smart enough to know they are seen as a single character, and therefore the length is still one:
```elixir
iex> String.length("👩‍🚒")
1
```
> Note: if you can't see the emoji above in your terminal, you need to make sure your terminal supports emoji and that you are using a font that can render them.
Although these rules may sound complicated, UTF-8 encoded documents are everywhere. This page itself is encoded in UTF-8. The encoding information is given to your browser which then knows how to render all of the bytes, characters, and graphemes accordingly.
If you want to see the exact bytes that a string would be stored in a file, a common trick is to concatenate the null byte `<<0>>` to it:
```elixir
iex> "hełło" <> <<0>>
<<104, 101, 197, 130, 197, 130, 111, 0>>
```
Alternatively, you can view a string's binary representation by using `IO.inspect/2`:
```elixir
iex> IO.inspect("hełło", binaries: :as_binaries)
<<104, 101, 197, 130, 197, 130, 111>>
```
We are getting a little bit ahead of ourselves. Let's talk about bitstrings to learn about what exactly the `<<>>` constructor means.
## Bitstrings
Although we have covered code points and UTF-8 encoding, we still need to go a bit deeper into how exactly we store the encoded bytes, and this is where we introduce the **bitstring**. A bitstring is a fundamental data type in Elixir, denoted with the `<<>>/1` syntax. **A bitstring is a contiguous sequence of bits in memory.**
By default, 8 bits (i.e. 1 byte) is used to store each number in a bitstring, but you can manually specify the number of bits via a `::n` modifier to denote the size in `n` bits, or you can use the more verbose declaration `::size(n)`:
```elixir
iex> <<42>> == <<42::8>>
true
iex> <<3::4>>
<<3::size(4)>>
```
For example, the decimal number `3` when represented with 4 bits in base 2 would be `0011`, which is equivalent to the values `0`, `0`, `1`, `1`, each stored using 1 bit:
```elixir
iex> <<0::1, 0::1, 1::1, 1::1>> == <<3::4>>
true
```
Any value that exceeds what can be stored by the number of bits provisioned is truncated:
```elixir
iex> <<1>> == <<257>>
true
```
Here, 257 in base 2 would be represented as `100000001`, but since we have reserved only 8 bits for its representation (by default), the left-most bit is ignored and the value becomes truncated to `00000001`, or simply `1` in decimal.
## Binaries
**A binary is a bitstring where the number of bits is divisible by 8.** That means that every binary is a bitstring, but not every bitstring is a binary. We can use the `is_bitstring/1` and `is_binary/1` functions to demonstrate this.
```elixir
iex> is_bitstring(<<3::4>>)
true
iex> is_binary(<<3::4>>)
false
iex> is_bitstring(<<0, 255, 42>>)
true
iex> is_binary(<<0, 255, 42>>)
true
iex> is_binary(<<42::16>>)
true
```
We can pattern match on binaries / bitstrings:
```elixir
iex> <<0, 1, x>> = <<0, 1, 2>>
<<0, 1, 2>>
iex> x
2
iex> <<0, 1, x>> = <<0, 1, 2, 3>>
** (MatchError) no match of right hand side value: <<0, 1, 2, 3>>
```
Note that unless you explicitly use `::` modifiers, each entry in the binary pattern is expected to match a single byte (exactly 8 bits). If we want to match on a binary of unknown size, we can use the `binary` modifier at the end of the pattern:
```elixir
iex> <<0, 1, x::binary>> = <<0, 1, 2, 3>>
<<0, 1, 2, 3>>
iex> x
<<2, 3>>
```
There are a couple other modifiers that can be useful when doing pattern matches on binaries. The `binary-size(n)` modifier will match `n` bytes in a binary:
```elixir
iex> <<head::binary-size(2), rest::binary>> = <<0, 1, 2, 3>>
<<0, 1, 2, 3>>
iex> head
<<0, 1>>
iex> rest
<<2, 3>>
```
**A string is a UTF-8 encoded binary**, where the code point for each character is encoded using 1 to 4 bytes. Thus every string is a binary, but due to the UTF-8 standard encoding rules, not every binary is a valid string.
```elixir
iex> is_binary("hello")
true
iex> is_binary(<<239, 191, 19>>)
true
iex> String.valid?(<<239, 191, 19>>)
false
```
The string concatenation operator `<>` is actually a binary concatenation operator:
```elixir
iex> "a" <> "ha"
"aha"
iex> <<0, 1>> <> <<2, 3>>
<<0, 1, 2, 3>>
```
Given that strings are binaries, we can also pattern match on strings:
```elixir
iex> <<head, rest::binary>> = "banana"
"banana"
iex> head == ?b
true
iex> rest
"anana"
```
However, remember that binary pattern matching works on *bytes*, so matching on the string like "über" with multibyte characters won't match on the *character*, it will match on the *first byte of that character*:
```elixir
iex> "ü" <> <<0>>
<<195, 188, 0>>
iex> <<x, rest::binary>> = "über"
"über"
iex> x == ?ü
false
iex> rest
<<188, 98, 101, 114>>
```
Above, `x` matched on only the first byte of the multibyte `ü` character.
Therefore, when pattern matching on strings, it is important to use the `utf8` modifier:
```elixir
iex> <<x::utf8, rest::binary>> = "über"
"über"
iex> x == ?ü
true
iex> rest
"ber"
```
## Charlists
Our tour of our bitstrings, binaries, and strings is nearly complete, but we have one more data type to explain: the charlist.
**A charlist is a list of integers where all the integers are valid code points.** In practice, you will not come across them often, only in specific scenarios such as interfacing with older Erlang libraries that do not accept binaries as arguments.
```elixir
iex> ~c"hello"
~c"hello"
iex> [?h, ?e, ?l, ?l, ?o]
~c"hello"
```
The `~c` sigil (we'll cover sigils later in the ["Sigils"](sigils.md) chapter) indicates the fact that we are dealing with a charlist and not a regular string.
Instead of containing bytes, a charlist contains integer code points. However, the list is only printed as a sigil if all code points are within the ASCII range:
```elixir
iex> ~c"hełło"
[104, 101, 322, 322, 111]
iex> is_list(~c"hełło")
true
```
This is done to ease interoperability with Erlang, even though it may lead to some surprising behaviour. For example, if you are storing a list of integers that happen to range between 0 and 127, by default IEx will interpret this as a charlist and it will display the corresponding ASCII characters.
```elixir
iex> heartbeats_per_minute = [99, 97, 116]
~c"cat"
```
You can always force charlists to be printed in their list representation by calling the `inspect/2` function:
```elixir
iex> inspect(heartbeats_per_minute, charlists: :as_list)
"[99, 97, 116]"
```
Furthermore, you can convert a charlist to a string and back by using the `to_string/1` and `to_charlist/1`:
```elixir
iex> to_charlist("hełło")
[104, 101, 322, 322, 111]
iex> to_string(~c"hełło")
"hełło"
iex> to_string(:hello)
"hello"
iex> to_string(1)
"1"
```
The functions above are polymorphic, in other words, they accept many shapes: not only do they convert charlists to strings (and vice-versa), they can also convert integers, atoms, and so on.
String (binary) concatenation uses the `<>` operator but charlists, being lists, use the list concatenation operator `++`:
```elixir
iex> ~c"this " <> ~c"fails"
** (ArgumentError) expected binary argument in <> operator but got: ~c"this "
(elixir) lib/kernel.ex:1821: Kernel.wrap_concatenation/3
(elixir) lib/kernel.ex:1808: Kernel.extract_concatenations/2
(elixir) expanding macro: Kernel.<>/2
iex:1: (file)
iex> ~c"this " ++ ~c"works"
~c"this works"
iex> "he" ++ "llo"
** (ArgumentError) argument error
:erlang.++("he", "llo")
iex> "he" <> "llo"
"hello"
```
With binaries, strings, and charlists out of the way, it is time to talk about key-value data structures.
@@ -0,0 +1,171 @@
# case, cond, and if
In this chapter, we will learn about the `case`, `cond`, and `if` control flow structures.
## case
`case` allows us to compare a value against many patterns until we find a matching one:
```elixir
iex> case {1, 2, 3} do
...> {4, 5, 6} ->
...> "This clause won't match"
...> {1, x, 3} ->
...> "This clause will match and bind x to 2 in this clause"
...> _ ->
...> "This clause would match any value"
...> end
"This clause will match and bind x to 2 in this clause"
```
If you want to pattern match against an existing variable, you need to use the `^` operator:
```elixir
iex> x = 1
1
iex> case 10 do
...> ^x -> "Won't match"
...> _ -> "Will match"
...> end
"Will match"
```
Clauses also allow extra conditions to be specified via guards:
```elixir
iex> case {1, 2, 3} do
...> {1, x, 3} when x > 0 ->
...> "Will match"
...> _ ->
...> "Would match, if guard condition were not satisfied"
...> end
"Will match"
```
The first clause above will only match when `x` is positive.
Keep in mind errors in guards do not leak but simply make the guard fail:
```elixir
iex> hd(1)
** (ArgumentError) argument error
iex> case 1 do
...> x when hd(x) -> "Won't match"
...> x -> "Got #{x}"
...> end
"Got 1"
```
If none of the clauses match, an error is raised:
```elixir
iex> case :ok do
...> :error -> "Won't match"
...> end
** (CaseClauseError) no case clause matching: :ok
```
The documentation for the `Kernel` module lists all available guards in its sidebar. You can also consult the complete [Patterns and Guards](../references/patterns-and-guards.md#guards) reference for in-depth documentation.
## cond
`case` is useful when you need to match against different values. However, in many circumstances, we want to check different conditions and find the first one that does not evaluate to `nil` or `false`. In such cases, one may use `cond`:
```elixir
iex> cond do
...> 2 + 2 == 5 ->
...> "This will not be true"
...> 2 * 2 == 3 ->
...> "Nor this"
...> 1 + 1 == 2 ->
...> "But this will"
...> end
"But this will"
```
This is equivalent to `else if` clauses in many imperative languages - although used less frequently in Elixir.
If all of the conditions return `nil` or `false`, an error (`CondClauseError`) is raised. For this reason, it may be necessary to add a final condition, equal to `true`, which will always match:
```elixir
iex> cond do
...> 2 + 2 == 5 ->
...> "This is never true"
...> 2 * 2 == 3 ->
...> "Nor this"
...> true ->
...> "This is always true (equivalent to else)"
...> end
"This is always true (equivalent to else)"
```
Finally, note `cond` considers any value besides `nil` and `false` to be true:
```elixir
iex> cond do
...> hd([1, 2, 3]) ->
...> "1 is considered as true"
...> end
"1 is considered as true"
```
## if/unless
Besides `case` and `cond`, Elixir also provides `if/2` and `unless/2`, which are useful when you need to check for only one condition:
```elixir
iex> if true do
...> "This works!"
...> end
"This works!"
iex> unless true do
...> "This will never be seen"
...> end
nil
```
If the condition given to `if/2` returns `false` or `nil`, the body given between `do`-`end` is not executed and instead it returns `nil`. The opposite happens with `unless/2`.
They also support `else` blocks:
```elixir
iex> if nil do
...> "This won't be seen"
...> else
...> "This will"
...> end
"This will"
```
This is also a good opportunity to talk about variable scoping in Elixir. If any variable is declared or changed inside `if`, `case`, and similar constructs, the declaration and change will only be visible inside the construct. For example:
```elixir
iex> x = 1
1
iex> if true do
...> x = x + 1
...> end
2
iex> x
1
```
In said cases, if you want to change a value, you must return the value from the `if`:
```elixir
iex> x = 1
1
iex> x = if true do
...> x + 1
...> else
...> x
...> end
2
```
> #### `if` and `unless` are macros {: .info}
>
> An interesting note regarding `if/2` and `unless/2` is that they are implemented as macros in the language: they aren't special language constructs as they would be in many languages. You can check the documentation and their source for more information.
We have concluded the introduction to the most fundamental control-flow constructs in Elixir. Now
let's learn where code and data meet with anonymous functions.
@@ -0,0 +1,110 @@
# Comprehensions
In Elixir, it is common to loop over an Enumerable, often filtering out some results and mapping values into another list. Comprehensions are syntactic sugar for such constructs: they group those common tasks into the `for` special form.
For example, we can map a list of integers into their squared values:
```elixir
iex> for n <- [1, 2, 3, 4], do: n * n
[1, 4, 9, 16]
```
A comprehension is made of three parts: generators, filters, and collectables.
## Generators and filters
In the expression above, `n <- [1, 2, 3, 4]` is the **generator**. It is literally generating values to be used in the comprehension. Any enumerable can be passed on the right-hand side of the generator expression:
```elixir
iex> for n <- 1..4, do: n * n
[1, 4, 9, 16]
```
Generator expressions also support pattern matching on their left-hand side; all non-matching patterns are *ignored*. Imagine that, instead of a range, we have a keyword list where the key is the atom `:good` or `:bad` and we only want to compute the square of the `:good` values:
```elixir
iex> values = [good: 1, good: 2, bad: 3, good: 4]
iex> for {:good, n} <- values, do: n * n
[1, 4, 16]
```
Alternatively to pattern matching, filters can be used to select some particular elements. For example, we can select the multiples of 3 and discard all others:
```elixir
iex> for n <- 0..5, rem(n, 3) == 0, do: n * n
[0, 9]
```
Comprehensions discard all elements for which the filter expression returns `false` or `nil`; all other values are selected.
Comprehensions generally provide a much more concise representation than using the equivalent functions from the `Enum` and `Stream` modules. Furthermore, comprehensions also allow multiple generators and filters to be given. Here is an example that receives a list of directories and gets the size of each file in those directories:
```elixir
dirs = ["/home/mikey", "/home/james"]
for dir <- dirs,
file <- File.ls!(dir),
path = Path.join(dir, file),
File.regular?(path) do
File.stat!(path).size
end
```
Multiple generators can also be used to calculate the cartesian product of two lists:
```elixir
iex> for i <- [:a, :b, :c], j <- [1, 2], do: {i, j}
[a: 1, a: 2, b: 1, b: 2, c: 1, c: 2]
```
Finally, keep in mind that variable assignments inside the comprehension, be it in generators, filters or inside the block, are not reflected outside of the comprehension.
## Bitstring generators
Bitstring generators are also supported and are very useful when you need to comprehend over bitstring streams. The example below receives a list of pixels from a binary with their respective red, green and blue values and converts them into tuples of three elements each:
```elixir
iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
iex> for <<r::8, g::8, b::8 <- pixels>>, do: {r, g, b}
[{213, 45, 132}, {64, 76, 32}, {76, 0, 0}, {234, 32, 15}]
```
A bitstring generator can be mixed with "regular" enumerable generators, and supports filters as well.
## The `:into` option
In the examples above, all the comprehensions returned lists as their result. However, the result of a comprehension can be inserted into different data structures by passing the `:into` option to the comprehension.
For example, a bitstring generator can be used with the `:into` option in order to easily remove all spaces in a string:
```elixir
iex> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>
"helloworld"
```
Sets, maps, and other dictionaries can also be given to the `:into` option. In general, `:into` accepts any structure that implements the `Collectable` protocol.
A common use case of `:into` can be transforming values in a map:
```elixir
iex> for {key, val} <- %{"a" => 1, "b" => 2}, into: %{}, do: {key, val * val}
%{"a" => 1, "b" => 4}
```
Let's make another example using streams. Since the `IO` module provides streams (that are both `Enumerable`s and `Collectable`s), an echo terminal that echoes back the upcased version of whatever is typed can be implemented using comprehensions:
```elixir
iex> stream = IO.stream(:stdio, :line)
iex> for line <- stream, into: stream do
...> String.upcase(line) <> "\n"
...> end
```
Now type any string into the terminal and you will see that the same value will be printed in upper-case. Unfortunately, this example also got your IEx shell stuck in the comprehension, so you will need to hit `Ctrl+C` twice to get out of it. :)
## Other options
Comprehensions support other options, such as `:reduce` and `:uniq`. Here are additional resources to learn more about comprehensions:
* [`for` official reference in Elixir documentation](`for/1`)
* [Mitchell Hanberg's comprehensive guide to Elixir's comprehensions](https://www.mitchellhanberg.com/the-comprehensive-guide-to-elixirs-for-comprehension/)
@@ -0,0 +1,169 @@
# Debugging
There are a number of ways to debug code in Elixir. In this chapter we will cover some of the more common ways of doing so.
## IO.inspect/2
What makes `IO.inspect(item, opts \\ [])` really useful in debugging is that it returns the `item` argument passed to it without affecting the behavior of the original code. Let's see an example.
```elixir
(1..10)
|> IO.inspect()
|> Enum.map(fn x -> x * 2 end)
|> IO.inspect()
|> Enum.sum()
|> IO.inspect()
```
Prints:
```elixir
1..10
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
110
```
As you can see `IO.inspect/2` makes it possible to "spy" on values almost anywhere in your code without altering the result, making it very helpful inside of a pipeline like in the above case.
`IO.inspect/2` also provides the ability to decorate the output with a `label` option. The label will be printed before the inspected `item`:
```elixir
[1, 2, 3]
|> IO.inspect(label: "before")
|> Enum.map(&(&1 * 2))
|> IO.inspect(label: "after")
|> Enum.sum
```
Prints:
```elixir
before: [1, 2, 3]
after: [2, 4, 6]
```
It is also very common to use `IO.inspect/2` with `binding/0`, which returns all variable names and their values:
```elixir
def some_fun(a, b, c) do
IO.inspect binding()
...
end
```
When `some_fun/3` is invoked with `:foo`, `"bar"`, `:baz` it prints:
```elixir
[a: :foo, b: "bar", c: :baz]
```
See `IO.inspect/2` and `Inspect.Opts` respectively to learn more about the function and read about all supported options.
## dbg/2
Elixir v1.14 introduced `dbg/2`. `dbg` is similar to `IO.inspect/2` but specifically tailored for debugging. It prints the value passed to it and returns it (just like `IO.inspect/2`), but it also prints the code and location.
```elixir
# In my_file.exs
feature = %{name: :dbg, inspiration: "Rust"}
dbg(feature)
dbg(Map.put(feature, :in_version, "1.14.0"))
```
The code above prints this:
```shell
[my_file.exs:2: (file)]
feature #=> %{inspiration: "Rust", name: :dbg}
[my_file.exs:3: (file)]
Map.put(feature, :in_version, "1.14.0") #=> %{in_version: "1.14.0", inspiration: "Rust", name: :dbg}
```
When talking about `IO.inspect/2`, we mentioned its usefulness when placed between steps of `|>` pipelines. `dbg` does it better: it understands Elixir code, so it will print values at *every step of the pipeline*.
```elixir
# In dbg_pipes.exs
__ENV__.file
|> String.split("/", trim: true)
|> List.last()
|> File.exists?()
|> dbg()
```
This code prints:
```shell
[dbg_pipes.exs:5: (file)]
__ENV__.file #=> "/home/myuser/dbg_pipes.exs"
|> String.split("/", trim: true) #=> ["home", "myuser", "dbg_pipes.exs"]
|> List.last() #=> "dbg_pipes.exs"
|> File.exists?() #=> true
```
While `dbg` provides conveniences around Elixir constructs, you will need `IEx` if you want to execute code and set breakpoints while debugging.
## Breakpoints
When using `IEx`, you may pass `--dbg pry` as an option to "stop" the code execution where the `dbg` call is:
```console
$ iex --dbg pry
```
Now a call to `dbg` will ask if you want to pry the existing code. If you accept, you'll be able to access all variables, as well as imports and aliases from the code, directly from IEx. This is called "prying". While the pry session is running, the code execution stops, until `continue` or `next` are called. Remember you can always run `iex` in the context of a project with `iex -S mix TASK`.
<script id="asciicast-509509" src="https://asciinema.org/a/509509.js" async></script>
`dbg` calls require us to change the code we intend to debug and has limited stepping functionality. Luckily IEx also provides a `IEx.break!/2` function which allows you to set and manage breakpoints on any Elixir code without modifying its source:
<script type="text/javascript" src="https://asciinema.org/a/0h3po0AmTcBAorc5GBNU97nrs.js" id="asciicast-0h3po0AmTcBAorc5GBNU97nrs" async></script><noscript><p><a href="https://asciinema.org/a/0h3po0AmTcBAorc5GBNU97nrs">See the example in asciinema</a></p></noscript>
Similar to `dbg`, once a breakpoint is reached code execution stops until `continue` or `next` are invoked. However, `break!/2` does not have access to aliases and imports from the debugged code as it works on the compiled artifact rather than on source code.
## Observer
For debugging complex systems, jumping at the code is not enough. It is necessary to have an understanding of the whole virtual machine, processes, applications, as well as set up tracing mechanisms. Luckily this can be achieved in Erlang with `:observer`. In your application:
```elixir
$ iex
iex> :observer.start()
```
> #### Missing dependencies {: .warning}
>
> When running `iex` inside a project with `iex -S mix`, `observer` won't be available as a dependency. To do so, you will need to call the following functions before:
>
> ```elixir
> iex> Mix.ensure_application!(:wx)
> iex> Mix.ensure_application!(:runtime_tools)
> iex> Mix.ensure_application!(:observer)
> iex> :observer.start()
> ```
>
> If any of the calls above fail, here is what may have happened: some package managers default to installing a minimized Erlang without WX bindings for GUI support. In some package managers, you may be able to replace the headless Erlang with a more complete package (look for packages named `erlang` vs `erlang-nox` on Debian/Ubuntu/Arch). In others managers, you may need to install a separate `erlang-wx` (or similarly named) package.
>
> There are conversations to improve this experience in future releases.
The above will open another Graphical User Interface that provides many panes to fully understand and navigate the runtime and your project.
We explore the Observer in the context of an actual project [in the Dynamic Supervisor chapter of the Mix & OTP guide](../mix-and-otp/dynamic-supervisor.md). This is one of the debugging techniques [the Phoenix framework used to achieve 2 million connections on a single machine](https://phoenixframework.org/blog/the-road-to-2-million-websocket-connections).
If you are using the Phoenix web framework, it ships with the [Phoenix LiveDashboard](https://github.com/phoenixframework/phoenix_live_dashboard), a web dashboard for production nodes which provides similar features to Observer.
Finally, remember you can also get a mini-overview of the runtime info by calling `runtime_info/0` directly in IEx.
## Other tools and community
We have just scratched the surface of what the Erlang VM has to offer, for example:
* Alongside the observer application, Erlang also includes a [`:crashdump_viewer`](https://www.erlang.org/doc/man/crashdump_viewer.html) to view crash dumps
* Integration with OS level tracers, such as [Linux Trace Toolkit,](http://www.erlang.org/doc/apps/runtime_tools/LTTng.html) [DTRACE,](http://www.erlang.org/doc/apps/runtime_tools/DTRACE.html) and [SystemTap](http://www.erlang.org/doc/apps/runtime_tools/SYSTEMTAP.html)
* [Microstate accounting](http://www.erlang.org/doc/man/msacc.html) measures how much time the runtime spends in several low-level tasks in a short time interval
* Mix ships with many tasks under the `profile` namespace, such as `cprof` and `fprof`
* For more advanced use cases, we recommend the excellent [Erlang in Anger](https://www.erlang-in-anger.com/), which is available as a free ebook
Happy debugging!
@@ -0,0 +1,124 @@
# Enumerables and Streams
While Elixir allows us to write recursive code, most operations we perform on collections is done with the help of the `Enum` and `Stream` modules. Let's learn how.
## Enumerables
Elixir provides the concept of enumerables and the `Enum` module to work with them. We have already learned two enumerables: lists and maps.
```elixir
iex> Enum.map([1, 2, 3], fn x -> x * 2 end)
[2, 4, 6]
iex> Enum.map(%{1 => 2, 3 => 4}, fn {k, v} -> k * v end)
[2, 12]
```
The `Enum` module provides a huge range of functions to transform, sort, group, filter and retrieve items from enumerables. It is one of the modules developers use frequently in their Elixir code. For a general overview of all functions in the `Enum` module, see [the `Enum` cheatsheet](enum-cheat.cheatmd).
Elixir also provides ranges (see `Range`), which are also enumerable:
```elixir
iex> Enum.map(1..3, fn x -> x * 2 end)
[2, 4, 6]
iex> Enum.reduce(1..3, 0, &+/2)
6
```
The functions in the `Enum` module are limited to, as the name says, enumerating values in data structures. For specific operations, like inserting and updating particular elements, you may need to reach for modules specific to the data type. For example, if you want to insert an element at a given position in a list, you should use the `List.insert_at/3` function, as it would make little sense to insert a value into, for example, a range.
We say the functions in the `Enum` module are polymorphic because they can work with diverse data types. In particular, the functions in the `Enum` module can work with any data type that implements the `Enumerable` protocol. We are going to discuss Protocols in a later chapter, for now we are going to move on to a specific kind of enumerable called a stream.
## Eager vs Lazy
All the functions in the `Enum` module are eager. Many functions expect an enumerable and return a list back:
```elixir
iex> odd? = fn x -> rem(x, 2) != 0 end
#Function<6.80484245/1 in :erl_eval.expr/5>
iex> Enum.filter(1..3, odd?)
[1, 3]
```
This means that when performing multiple operations with `Enum`, each operation is going to generate an intermediate list until we reach the result:
```elixir
iex> 1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum()
7500000000
```
The example above has a pipeline of operations. We start with a range and then multiply each element in the range by 3. This first operation will now create and return a list with `100_000` items. Then we keep all odd elements from the list, generating a new list, now with `50_000` items, and then we sum all entries.
## The pipe operator
The `|>` symbol used in the snippet above is the **pipe operator**: it takes the output from the expression on its left side and passes it as the first argument to the function call on its right side. Its purpose is to highlight the data being transformed by a series of functions. To see how it can make the code cleaner, have a look at the example above rewritten without using the `|>` operator:
```elixir
iex> Enum.sum(Enum.filter(Enum.map(1..100_000, &(&1 * 3)), odd?))
7500000000
```
Find more about the pipe operator [by reading its documentation](`|>/2`).
## Streams
As an alternative to `Enum`, Elixir provides the `Stream` module which supports lazy operations:
```elixir
iex> 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?) |> Enum.sum()
7500000000
```
Streams are lazy, composable enumerables.
In the example above, `1..100_000 |> Stream.map(&(&1 * 3))` returns a data type, an actual stream, that represents the `map` computation over the range `1..100_000`:
```elixir
iex> 1..100_000 |> Stream.map(&(&1 * 3))
#Stream<[enum: 1..100000, funs: [#Function<34.16982430/1 in Stream.map/2>]]>
```
Furthermore, they are composable because we can pipe many stream operations:
```elixir
iex> 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?)
#Stream<[enum: 1..100000, funs: [...]]>
```
Instead of generating intermediate lists, streams build a series of computations that are invoked only when we pass the underlying stream to the `Enum` module. Streams are useful when working with large, *possibly infinite*, collections.
Many functions in the `Stream` module accept any enumerable as an argument and return a stream as a result. It also provides functions for creating streams. For example, `Stream.cycle/1` can be used to create a stream that cycles a given enumerable infinitely. Be careful to not call a function like `Enum.map/2` on such streams, as they would cycle forever:
```elixir
iex> stream = Stream.cycle([1, 2, 3])
#Function<15.16982430/2 in Stream.unfold/2>
iex> Enum.take(stream, 10)
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1]
```
On the other hand, `Stream.unfold/2` can be used to generate values from a given initial value:
```elixir
iex> stream = Stream.unfold("hełło", &String.next_codepoint/1)
#Function<39.75994740/2 in Stream.unfold/2>
iex> Enum.take(stream, 3)
["h", "e", "ł"]
```
Another interesting function is `Stream.resource/3` which can be used to wrap around resources, guaranteeing they are opened right before enumeration and closed afterwards, even in the case of failures. For example, `File.stream!/1` builds on top of `Stream.resource/3` to stream files:
```elixir
iex> stream = File.stream!("path/to/file")
%File.Stream{
line_or_bytes: :line,
modes: [:raw, :read_ahead, :binary],
path: "path/to/file",
raw: true
}
iex> Enum.take(stream, 10)
```
The example above will fetch the first 10 lines of the file you have selected. This means streams can be very useful for handling large files or even slow resources like network resources.
The `Enum` and `Stream` modules provide a wide range of functions, but you don't have to know all of them by heart. Familiarize yourself with `Enum.map/2`, `Enum.reduce/3` and other functions with either `map` or `reduce` in their names, and you will naturally build an intuition around the most important use cases. You may also focus on the `Enum` module first and only move to `Stream` for the particular scenarios where laziness is required, to either deal with slow resources or large, possibly infinite, collections.
Next, we'll look at a feature central to Elixir, Processes, which allows us to write concurrent, parallel and distributed programs in an easy and understandable way.
@@ -0,0 +1,188 @@
# Erlang libraries
Elixir provides excellent interoperability with Erlang libraries. In fact, Elixir discourages simply wrapping Erlang libraries in favor of directly interfacing with Erlang code. In this section, we will present some of the most common and useful Erlang functionality that is not found in Elixir.
Erlang modules have a different naming convention than in Elixir and start in lowercase. In both cases, module names are atoms and we invoke functions by dispatching to the module name:
```elixir
iex> is_atom(String)
true
iex> String.first("hello")
"h"
iex> is_atom(:binary)
true
iex> :binary.first("hello")
104
```
As you grow more proficient in Elixir, you may want to explore the Erlang [STDLIB Reference Manual](http://www.erlang.org/doc/apps/stdlib/index.html) in more detail.
## The binary module
The built-in Elixir String module handles binaries that are UTF-8 encoded. [The `:binary` module](`:binary`) is useful when you are dealing with binary data that is not necessarily UTF-8 encoded.
```elixir
iex> String.to_charlist("Ø")
[216]
iex> :binary.bin_to_list("Ø")
[195, 152]
```
The above example shows the difference; the `String` module returns Unicode codepoints, while `:binary` deals with raw data bytes.
## Formatted text output
Elixir does not contain a function similar to `printf` found in C and other languages. Luckily, the Erlang standard library functions `:io.format/2` and `:io_lib.format/2` may be used. The first formats to terminal output, while the second formats to an iolist. The format specifiers differ from `printf`, [refer to the Erlang documentation for details](`:io.format/2`).
```elixir
iex> :io.format("Pi is approximately given by:~10.3f~n", [:math.pi])
Pi is approximately given by: 3.142
:ok
iex> to_string(:io_lib.format("Pi is approximately given by:~10.3f~n", [:math.pi]))
"Pi is approximately given by: 3.142\n"
```
## The crypto module
[The `:crypto` module](`:crypto`) contains hashing functions, digital signatures, encryption and more:
```elixir
iex> Base.encode16(:crypto.hash(:sha256, "Elixir"))
"3315715A7A3AD57428298676C5AE465DADA38D951BDFAC9348A8A31E9C7401CB"
```
The `:crypto` module is part of the `:crypto` application that ships with Erlang. This means you must list the `:crypto` application as an additional application in your project configuration. To do this, edit your `mix.exs` file to include:
```elixir
def application do
[extra_applications: [:crypto]]
end
```
Any module that is not part of the `:kernel` or `:stdlib` Erlang applications must have their application explicitly listed in your `mix.exs`. You can find the application name of any Erlang module in the Erlang documentation, immediately below the Erlang logo in the sidebar.
## The digraph module
The [`:digraph`](`:digraph`) and [`:digraph_utils`](`:digraph_utils`) modules contain functions for dealing with directed graphs built of vertices and edges. After constructing the graph, the algorithms in there will help find, for instance, the shortest path between two vertices, or loops in the graph.
Given three vertices, find the shortest path from the first to the last.
```elixir
iex> digraph = :digraph.new()
iex> coords = [{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}]
iex> [v0, v1, v2] = (for c <- coords, do: :digraph.add_vertex(digraph, c))
iex> :digraph.add_edge(digraph, v0, v1)
iex> :digraph.add_edge(digraph, v1, v2)
iex> :digraph.get_short_path(digraph, v0, v2)
[{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}]
```
Note that the functions in `:digraph` alter the graph structure in-place, this
is possible because they are implemented as ETS tables, explained next.
## Erlang Term Storage
The modules [`:ets`](`:ets`) and [`:dets`](`:dets`) handle storage of large data structures in memory or on disk respectively.
ETS lets you create a table containing tuples. By default, ETS tables are protected, which means only the owner process may write to the table but any other process can read. ETS has some functionality to allow a table to be used as a simple database, a key-value store or as a cache mechanism.
The functions in the `ets` module will modify the state of the table as a side-effect.
```elixir
iex> table = :ets.new(:ets_test, [])
# Store as tuples with {name, population}
iex> :ets.insert(table, {"China", 1_374_000_000})
iex> :ets.insert(table, {"India", 1_284_000_000})
iex> :ets.insert(table, {"USA", 322_000_000})
iex> :ets.i(table)
<1 > {<<"India">>,1284000000}
<2 > {<<"USA">>,322000000}
<3 > {<<"China">>,1374000000}
```
## The math module
The [`:math`](`:math`) module contains common mathematical operations covering trigonometry, exponential, and logarithmic functions.
```elixir
iex> angle_45_deg = :math.pi() * 45.0 / 180.0
iex> :math.sin(angle_45_deg)
0.7071067811865475
iex> :math.exp(55.0)
7.694785265142018e23
iex> :math.log(7.694785265142018e23)
55.0
```
## The queue module
The [`:queue`](`:queue`) module provides a data structure that implements (double-ended) FIFO (first-in first-out) queues efficiently:
```elixir
iex> q = :queue.new
iex> q = :queue.in("A", q)
iex> q = :queue.in("B", q)
iex> {value, q} = :queue.out(q)
iex> value
{:value, "A"}
iex> {value, q} = :queue.out(q)
iex> value
{:value, "B"}
iex> {value, q} = :queue.out(q)
iex> value
:empty
```
## The rand module
The [`:rand`](`:rand`) has functions for returning random values and setting the random seed.
```elixir
iex> :rand.uniform()
0.8175669086010815
iex> _ = :rand.seed(:exs1024, {123, 123534, 345345})
iex> :rand.uniform()
0.5820506340260994
iex> :rand.uniform(6)
6
```
## The zip and zlib modules
The [`:zip`](`:zip`) module lets you read and write ZIP files to and from disk or memory, as well as extracting file information.
This code counts the number of files in a ZIP file:
```elixir
iex> :zip.foldl(fn _, _, _, acc -> acc + 1 end, 0, :binary.bin_to_list("file.zip"))
{:ok, 633}
```
The [`:zlib`](`:zlib`) module deals with data compression in zlib format, as found in the `gzip` command line utility found in Unix systems.
```elixir
iex> song = "
...> Mary had a little lamb,
...> His fleece was white as snow,
...> And everywhere that Mary went,
...> The lamb was sure to go."
iex> compressed = :zlib.compress(song)
iex> byte_size(song)
110
iex> byte_size(compressed)
99
iex> :zlib.uncompress(compressed)
"\nMary had a little lamb,\nHis fleece was white as snow,\nAnd everywhere that Mary went,\nThe lamb was sure to go."
```
## Learning Erlang
If you want to get deeper into Erlang, here's a list of online resources that cover Erlang's fundamentals and its more advanced features:
* This [Erlang Syntax: A Crash Course](https://elixir-lang.org/crash-course.html) provides a concise intro to Erlang's syntax. Each code snippet is accompanied by equivalent code in Elixir. This is an opportunity for you to not only get some exposure to Erlang's syntax but also review what you learned about Elixir.
* Erlang's official website has a short [tutorial](https://www.erlang.org/course). There is a chapter with pictures briefly describing Erlang's primitives for [concurrent programming](https://www.erlang.org/course/concurrent_programming.html).
* [Learn You Some Erlang for Great Good!](http://learnyousomeerlang.com/) is an excellent introduction to Erlang, its design principles, standard library, best practices, and much more. Once you have read through the crash course mentioned above, you'll be able to safely skip the first couple of chapters in the book that mostly deal with the syntax. When you reach [The Hitchhiker's Guide to Concurrency](http://learnyousomeerlang.com/the-hitchhikers-guide-to-concurrency) chapter, that's where the real fun starts.
Our last step is to take a look at existing Elixir (and Erlang) libraries you might use while debugging.
@@ -0,0 +1,57 @@
# Introduction
Welcome!
This guide will teach you about Elixir fundamentals - the language syntax, how to define modules, the common data structures in the language, and more. This chapter will focus on ensuring that Elixir is installed and that you can successfully run Elixir's Interactive Shell, called IEx.
Let's get started.
## Installation
If you haven't yet installed Elixir, visit our [installation page](https://elixir-lang.org/install.html). Once you are done, you can run `elixir --version` to get the current Elixir version. The requirements for this guide are:
* Elixir 1.15.0 onwards
* Erlang/OTP 26 onwards
If you are looking for other resources for learning Elixir, you can also consult the [learning page](https://elixir-lang.org/learning.html) of the official website.
## Interactive mode
When you install Elixir, you will have three new command line executables: `iex`, `elixir` and `elixirc`.
For now, let's start by running `iex` (or `iex.bat` if you are on Windows PowerShell, where `iex` is a PowerShell command) which stands for Interactive Elixir. In interactive mode, we can type any Elixir expression and get its result. Let's warm up with some basic expressions.
Open up `iex` and type the following expressions:
```elixir
Erlang/OTP 26 [64-bit] [smp:2:2] [...]
Interactive Elixir - press Ctrl+C to exit
iex(1)> 40 + 2
42
iex(2)> "hello" <> " world"
"hello world"
```
Please note that some details like version numbers may differ a bit in your session, that's not important. By executing the code above, you should evaluate expressions and see their results. To exit `iex` press `Ctrl+C` twice.
It seems we are ready to go! We will use the interactive shell quite a lot in the next chapters to get a bit more familiar with the language constructs and basic types, starting in the next chapter.
> Note: if you are on Windows and running on an Erlang/OTP version earlier than 26, you can also try `iex --werl` (`iex.bat --werl` on PowerShell) which may provide a better experience depending on which console you are using.
## Running scripts
After getting familiar with the basics of the language you may want to try writing simple programs. This can be accomplished by putting the following Elixir code into a file:
```elixir
IO.puts("Hello world from Elixir")
```
Save it as `simple.exs` and execute it with `elixir`:
```console
$ elixir simple.exs
Hello world from Elixir
```
Later on we will learn [how to compile Elixir code](modules-and-functions.md) and how to create and work within Elixir projects using the Mix build tool. For now, let's move on to learn the basic data types in the language.
@@ -0,0 +1,217 @@
# IO and the file system
This chapter introduces the input/output mechanisms, file-system-related tasks, and related modules such as `IO`, `File`, and `Path`. The IO system provides a great opportunity to shed some light on some philosophies and curiosities of Elixir and the Erlang VM.
## The `IO` module
The `IO` module is the main mechanism in Elixir for reading and writing to standard input/output (`:stdio`), standard error (`:stderr`), files, and other IO devices. Usage of the module is pretty straightforward:
```elixir
iex> IO.puts("hello world")
hello world
:ok
iex> IO.gets("yes or no? ")
yes or no? yes
"yes\n"
```
By default, functions in the `IO` module read from the standard input and write to the standard output. We can change that by passing, for example, `:stderr` as an argument (in order to write to the standard error device):
```elixir
iex> IO.puts(:stderr, "hello world")
hello world
:ok
```
## The `File` module
The `File` module contains functions that allow us to open files as IO devices. By default, files are opened in binary mode, which requires developers to use the specific `IO.binread/2` and `IO.binwrite/2` functions from the `IO` module:
```elixir
iex> {:ok, file} = File.open("path/to/file/hello", [:write])
{:ok, #PID<0.47.0>}
iex> IO.binwrite(file, "world")
:ok
iex> File.close(file)
:ok
iex> File.read("path/to/file/hello")
{:ok, "world"}
```
A file can also be opened with `:utf8` encoding, which tells the `File` module to interpret the bytes read from the file as UTF-8-encoded bytes.
Besides functions for opening, reading and writing files, the `File` module has many functions to work with the file system. Those functions are named after their UNIX equivalents. For example, `File.rm/1` can be used to remove files, `File.mkdir/1` to create directories, `File.mkdir_p/1` to create directories and all their parent chain. There are even `File.cp_r/2` and `File.rm_rf/1` to respectively copy and remove files and directories recursively (i.e., copying and removing the contents of the directories too).
You will also notice that functions in the `File` module have two variants: one "regular" variant and another variant with a trailing bang (`!`). For example, when we read the `"hello"` file in the example above, we use `File.read/1`. Alternatively, we can use `File.read!/1`:
```elixir
iex> File.read("path/to/file/hello")
{:ok, "world"}
iex> File.read!("path/to/file/hello")
"world"
iex> File.read("path/to/file/unknown")
{:error, :enoent}
iex> File.read!("path/to/file/unknown")
** (File.Error) could not read file "path/to/file/unknown": no such file or directory
```
Notice that the version with `!` returns the contents of the file instead of a tuple, and if anything goes wrong the function raises an error.
The version without `!` is preferred when you want to handle different outcomes using pattern matching:
```elixir
case File.read("path/to/file/hello") do
{:ok, body} -> # do something with the `body`
{:error, reason} -> # handle the error caused by `reason`
end
```
However, if you expect the file to be there, the bang variation is more useful as it raises a meaningful error message. Avoid writing:
```elixir
{:ok, body} = File.read("path/to/file/unknown")
```
as, in case of an error, `File.read/1` will return `{:error, reason}` and the pattern matching will fail. You will still get the desired result (a raised error), but the message will be about the pattern which doesn't match (thus being cryptic in respect to what the error actually is about).
Therefore, if you don't want to handle the error outcomes, prefer to use the functions ending with an exclamation mark, such as `File.read!/1`.
## The `Path` module
The majority of the functions in the `File` module expect paths as arguments. Most commonly, those paths will be regular binaries. The `Path` module provides facilities for working with such paths:
```elixir
iex> Path.join("foo", "bar")
"foo/bar"
iex> Path.expand("~/hello")
"/Users/jose/hello"
```
Using functions from the `Path` module as opposed to directly manipulating strings is preferred since the `Path` module takes care of different operating systems transparently. Finally, keep in mind that Elixir will automatically convert slashes (`/`) into backslashes (`\`) on Windows when performing file operations.
With this, we have covered the main modules that Elixir provides for dealing with IO and interacting with the file system. In the next section, we will peek a bit under the covers and learn how the IO system is implemented in the VM.
## Processes
You may have noticed that `File.open/2` returns a tuple like `{:ok, pid}`:
```elixir
iex> {:ok, file} = File.open("hello", [:write])
{:ok, #PID<0.47.0>}
```
This happens because the `IO` module actually works with processes (see [the previous chapter](processes.md)). Given a file is a process, when you write to a file that has been closed, you are actually sending a message to a process which has been terminated:
```elixir
iex> File.close(file)
:ok
iex> IO.write(file, "is anybody out there")
** (ErlangError) Erlang error: :terminated:
* 1st argument: the device has terminated
(stdlib 5.0) io.erl:94: :io.put_chars(#PID<0.114.0>, "is anybody out there")
iex:4: (file)
```
Let's see in more detail what happens when you request `IO.write(pid, binary)`. The `IO` module sends a message to the process identified by `pid` with the desired operation. A small ad-hoc process can help us see it:
```elixir
iex> pid = spawn(fn ->
...> receive do: (msg -> IO.inspect(msg))
...> end)
#PID<0.57.0>
iex> IO.write(pid, "hello")
{:io_request, #PID<0.41.0>, #Reference<0.0.8.91>,
{:put_chars, :unicode, "hello"}}
** (ErlangError) erlang error: :terminated
```
After `IO.write/2`, we can see the request sent by the `IO` module printed out (a four-elements tuple). Soon after that, we see that it fails since the `IO` module expected some kind of result, which we did not supply.
By modeling IO devices with processes, the Erlang VM allows us to even read and write to files across nodes. Neat!
## `iodata` and `chardata`
In all of the examples above, we used binaries when writing to files. However, most of the IO functions in Elixir also accept either "iodata" or "chardata".
One of the main reasons for using "iodata" and "chardata" is for performance. For example,
imagine you need to greet someone in your application:
```elixir
name = "Mary"
IO.puts("Hello " <> name <> "!")
```
Given strings in Elixir are immutable, as most data structures, the example above will copy the string "Mary" into the new "Hello Mary!" string. While this is unlikely to matter for the short string as above, copying can be quite expensive for large strings! For this reason, the IO functions in Elixir allow you to pass instead a list of strings:
```elixir
name = "Mary"
IO.puts(["Hello ", name, "!"])
```
In the example above, there is no copying. Instead we create a list that contains the original name. We call such lists either "iodata" or "chardata" and we will learn the precise difference between them soon.
Those lists are very useful because it can actually simplify the processing strings in several scenarios. For example, imagine you have a list of values, such as `["apple", "banana", "lemon"]` that you want to write to disk separated by commas. How can you achieve this?
One option is to use `Enum.join/2` and convert the values to a string:
```elixir
iex> Enum.join(["apple", "banana", "lemon"], ",")
"apple,banana,lemon"
```
The above returns a new string by copying each value into the new string. However, with the knowledge in this section, we know that we can pass a list of strings to the IO/File functions. So instead we can do:
```elixir
iex> Enum.intersperse(["apple", "banana", "lemon"], ",")
["apple", ",", "banana", ",", "lemon"]
```
"iodata" and "chardata" do not only contain strings, but they may contain arbitrary nested lists of strings too:
```elixir
iex> IO.puts(["apple", [",", "banana", [",", "lemon"]]])
```
"iodata" and "chardata" may also contain integers. For example, we could print our comma separated list of values by using `?,` as separator, which is the integer representing a comma (`44`):
```elixir
iex> IO.puts(["apple", ?,, "banana", ?,, "lemon"])
```
The difference between "iodata" and "chardata" is precisely what said integer represents. For iodata, the integers represent bytes. For chardata, the integers represent Unicode codepoints. For ASCII characters, the byte representation is the same as the codepoint representation, so it fits both classifications. However, the default IO device works with chardata, which means we can do:
```elixir
iex> IO.puts([?O, ?l, ?á, ?\s, "Mary", ?!])
```
Overall, integers in a list may represent either a bunch of bytes or a bunch of characters and which one to use depends on the encoding of the IO device. If the file is opened without encoding, the file is expected to be in raw mode, and the functions in the `IO` module starting with `bin*` must be used. Those functions expect an `iodata` as an argument, where integers in the list would represent bytes.
On the other hand, the default IO device (`:stdio`) and files opened with `:utf8` encoding work with the remaining functions in the `IO` module. Those functions expect a `chardata` as an argument, where integers represent codepoints.
Although this is a subtle difference, you only need to worry about these details if you intend to pass lists containing integers to those functions. If you pass binaries, or list of binaries, then there is no ambiguity.
Finally, there is one last construct called charlist, which [we discussed in earlier chapters](binaries-strings-and-charlists.md). Charlists are a special case of chardata where all values are integers representing Unicode codepoints. They can be created with the `~c` sigil:
```elixir
iex> ~c"hello"
~c"hello"
```
Charlists mostly show up when interfacing with Erlang, as some Erlang APIs use charlist as their representation for strings. For this reason, any list containing printable ASCII codepoints will be printed as a charlist:
```elixir
iex> [?a, ?b, ?c]
~c"abc"
```
We packed a lot into this small section, so let's break it down:
* iodata and chardata are lists of binaries and integers. Those binaries and integers can be arbitrarily nested inside lists. Their goal is to give flexibility and performance when working with IO devices and files;
* the choice between iodata and chardata depends on the encoding of the IO device. If the file is opened without encoding, the file expects iodata, and the functions in the `IO` module starting with `bin*` must be used. The default IO device (`:stdio`) and files opened with `:utf8` encoding expect chardata and work with the remaining functions in the `IO` module;
* charlists are a special case of chardata, where it exclusively uses a list of integers Unicode codepoints. They can be created with the `~c` sigil. Lists of integers are automatically printed using the `~c` sigil if all integers in a list represent printable ASCII codepoints.
This finishes our tour of IO devices and IO related functionality. We have learned about three Elixir modules - `IO`, `File`, and `Path` - as well as how the VM uses processes for the underlying IO mechanisms and how to use `chardata` and `iodata` for IO operations.
@@ -0,0 +1,276 @@
# Keyword lists and maps
Now let's talk about associative data structures. Associative data structures are able to associate a key to a certain value. Different languages call these different names like dictionaries, hashes, associative arrays, etc.
In Elixir, we have two main associative data structures: keyword lists and maps.
## Keyword lists
Keyword lists are a data-structure used to pass options to functions. Imagine you want to split a string of numbers. We can use `String.split/2`:
```elixir
iex> String.split("1 2 3", " ")
["1", "2", "3"]
```
However, what happens if there is an additional space between the numbers:
```elixir
iex> String.split("1 2 3", " ")
["1", "", "2", "", "3"]
```
As you can see, there are now empty strings in our results. Luckily, the `String.split/3` function allows the `trim` option to be set to true:
```elixir
iex> String.split("1 2 3", " ", [trim: true])
["1", "2", "3"]
```
`[trim: true]` is a keyword list. Furthermore, when a keyword list is the last argument of a function, we can skip the brackets and write:
```elixir
iex> String.split("1 2 3", " ", trim: true)
["1", "2", "3"]
```
As shown in the example above, keyword lists are mostly used as optional arguments to functions.
As the name implies, keyword lists are simply lists. In particular, they are lists consisting of 2-item tuples where the first element (the key) is an atom and the second element can be any value. Both representations are the same:
```elixir
iex> [{:trim, true}] == [trim: true]
true
```
Since keyword lists are lists, we can use all operations available to lists. For example, we can use `++` to add new values to a keyword list:
```elixir
iex> list = [a: 1, b: 2]
[a: 1, b: 2]
iex> list ++ [c: 3]
[a: 1, b: 2, c: 3]
iex> [a: 0] ++ list
[a: 0, a: 1, b: 2]
```
You can read the value of a keyword list using the brackets syntax. This is also known as the access syntax, as it is defined by the `Access` module:
```elixir
iex> list[:a]
1
iex> list[:b]
2
```
In case of duplicate keys, values added to the front are the ones fetched:
```elixir
iex> new_list = [a: 0] ++ list
[a: 0, a: 1, b: 2]
iex> new_list[:a]
0
```
Keyword lists are important because they have three special characteristics:
* Keys must be atoms.
* Keys are ordered, as specified by the developer.
* Keys can be given more than once.
For example, [the Ecto library](https://github.com/elixir-lang/ecto) makes use of these features to provide an elegant DSL for writing database queries:
```elixir
query =
from w in Weather,
where: w.prcp > 0,
where: w.temp < 20,
select: w
```
Although we can pattern match on keyword lists, it is not done in practice since pattern matching on lists requires the number of items and their order to match:
```elixir
iex> [a: a] = [a: 1]
[a: 1]
iex> a
1
iex> [a: a] = [a: 1, b: 2]
** (MatchError) no match of right hand side value: [a: 1, b: 2]
iex> [b: b, a: a] = [a: 1, b: 2]
** (MatchError) no match of right hand side value: [a: 1, b: 2]
```
Furthermore, given keyword lists are often used as optional arguments, they are used in situations where not all keys may be present, which would make it impossible to match on them. In a nutshell, do not pattern match on keyword lists.
In order to manipulate keyword lists, Elixir provides the `Keyword` module. Remember, though, keyword lists are simply lists, and as such they provide the same linear performance characteristics as them: the longer the list, the longer it will take to find a key, to count the number of items, and so on. If you need to store a large amount of keys in a key-value data structure, Elixir offers maps, which we will soon learn.
### `do`-blocks and keywords
As we have seen, keywords are mostly used in the language to pass optional values. In fact, we have used keywords before in this guide. For example, we have seen:
```elixir
iex> if true do
...> "This will be seen"
...> else
...> "This won't"
...> end
"This will be seen"
```
It happens that `do` blocks are nothing more than a syntax convenience on top of keywords. We can rewrite the above to:
```elixir
iex> if true, do: "This will be seen", else: "This won't"
"This will be seen"
```
Pay close attention to both syntaxes. In the keyword list format, we separate each key-value pair with commas, and each key is followed by `:`. In the `do`-blocks, we get rid of the colons, the commas, and separate each keyword by a newline. They are useful exactly because they remove the verbosity when writing blocks of code. Most of the time, you will use the block syntax, but it is good to know they are equivalent.
This plays an important role in the language as it allows Elixir syntax to stay small but still expressive. We only need few data structures to represent the language, a topic we will come back to when talking about [optional syntax](optional-syntax.md) and go in-depth when discussing [meta-programming](../meta-programming/quote-and-unquote.md).
With this out of the way, let's talk about maps.
## Maps as key-value pairs
Whenever you need to store key-value pairs, maps are the "go to" data structure in Elixir. A map is created using the `%{}` syntax:
```elixir
iex> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> map[:a]
1
iex> map[2]
:b
iex> map[:c]
nil
```
Compared to keyword lists, we can already see two differences:
* Maps allow any value as a key.
* Maps' keys do not follow any ordering.
In contrast to keyword lists, maps are very useful with pattern matching. When a map is used in a pattern, it will always match on a subset of the given value:
```elixir
iex> %{} = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> %{:a => a} = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> a
1
iex> %{:c => c} = %{:a => 1, 2 => :b}
** (MatchError) no match of right hand side value: %{2 => :b, :a => 1}
```
As shown above, a map matches as long as the keys in the pattern exist in the given map. Therefore, an empty map matches all maps.
The `Map` module provides a very similar API to the `Keyword` module with convenience functions to add, remove, and update maps keys:
```elixir
iex> Map.get(%{:a => 1, 2 => :b}, :a)
1
iex> Map.put(%{:a => 1, 2 => :b}, :c, 3)
%{2 => :b, :a => 1, :c => 3}
iex> Map.to_list(%{:a => 1, 2 => :b})
[{2, :b}, {:a, 1}]
```
## Maps of predefined keys
In the previous section, we have used maps as a key-value data structure where keys can be added or removed at any time. However, it is also common to create maps with a pre-defined set of keys. Their values may be updated, but new keys are never added nor removed. This is useful when we know the shape of the data we are working with and, if we get a different key, it likely means a mistake was done elsewhere.
We define such maps using the same syntax as in the previous section, except that all keys must be atoms:
```elixir
iex> map = %{:name => "John", :age => 23}
%{name: "John", age: 23}
```
As you can see from the printed result above, Elixir also allows you to write maps of atom keys using the same `key: value` syntax as keyword lists.
When the keys are atoms, in particular when working with maps of predefined keys, we can also access them using the `map.key` syntax:
```elixir
iex> map = %{name: "John", age: 23}
%{name: "John", age: 23}
iex> map.name
"John"
iex> map.agee
** (KeyError) key :agee not found in: %{name: "John", age: 23}
```
There is also syntax for updating keys, which also raises if the key has not yet been defined:
```elixir
iex> %{map | name: "Mary"}
%{name: "Mary", age: 23}
iex> %{map | agee: 27}
** (KeyError) key :agee not found in: %{name: "John", age: 23}
```
These operations have one large benefit in that they raise if the key does not exist in the map and the compiler may even detect and warn when possible. This makes them useful to get quick feedback and spot bugs and typos early on. This is also the syntax used to power another Elixir feature called "Structs", which we will learn later on.
Elixir developers typically prefer to use the `map.key` syntax and pattern matching instead of the functions in the `Map` module when working with maps because they lead to an assertive style of programming. [This blog post by José Valim](https://dashbit.co/blog/writing-assertive-code-with-elixir) provides insight and examples on how you get more concise and faster software by writing assertive code in Elixir.
## Nested data structures
Often we will have maps inside maps, or even keywords lists inside maps, and so forth. Elixir provides conveniences for manipulating nested data structures via the `put_in/2`, `update_in/2` and other macros giving the same conveniences you would find in imperative languages while keeping the immutable properties of the language.
Imagine you have the following structure:
```elixir
iex> users = [
john: %{name: "John", age: 27, languages: ["Erlang", "Ruby", "Elixir"]},
mary: %{name: "Mary", age: 29, languages: ["Elixir", "F#", "Clojure"]}
]
[
john: %{age: 27, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}
]
```
We have a keyword list of users where each value is a map containing the name, age and a list of programming languages each user likes. If we wanted to access the age for john, we could write:
```elixir
iex> users[:john].age
27
```
It happens we can also use this same syntax for updating the value:
```elixir
iex> users = put_in users[:john].age, 31
[
john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}
]
```
The `update_in/2` macro is similar but allows us to pass a function that controls how the value changes. For example, let's remove "Clojure" from Mary's list of languages:
```elixir
iex> users = update_in users[:mary].languages, fn languages -> List.delete(languages, "Clojure") end
[
john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
mary: %{age: 29, languages: ["Elixir", "F#"], name: "Mary"}
]
```
There is more to learn about `put_in/2` and `update_in/2`, including the `get_and_update_in/2` that allows us to extract a value and update the data structure at once. There are also `put_in/3`, `update_in/3` and `get_and_update_in/3` which allow dynamic access into the data structure.
## Summary
There are two different data structures for working with key-value stores in Elixir. Alongside the `Access` module and pattern matching, they provide a rich set of tools for manipulating complex, potentially nested, data structures.
As we conclude this chapter, the important to keep in mind is that you should:
* Use keyword lists for passing optional values to functions
* Use maps for general key-value data structures
* Use maps when working with data that has a predefined set of keys
Now let's talk about modules and functions.

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