Compare commits

..
739 Commits
Author SHA1 Message Date
José Valim d33fd8e413 Release v1.19.5 2026-01-09 11:57:56 +01:00
José Valim 02603b147c Do not check the position in Regex errors, closes #15033 2026-01-01 10:54:31 +01:00
José Valim e126dab368 Optimize protocol consolidation
* Allow directory listings to be pre-computed
* Do not require struct modules to be loaded

The current implementation would require us to
recompute protocols whenever a struct changed,
something that is not currently available.
Furthermore, the fact we had to load modules
naturally made consolidation more expensive.
Therefore we need a lazy type resolution,
and until we have such feature, we will only
track struct names.
2025-12-19 18:42:49 +01:00
Łukasz Samson 38547285f1 Fix parser crash on missing parentheses on expression following operator \"not in\" warning (#15011) 2025-12-12 12:14:39 +01:00
Jechol Lee 4f07670d08 Fix unnecessary recompilation when dbg_callback is modified at runtime (#15007)
When :elixir app starts, store the dbg_callback value in dbg_callback_initial.
Mix compiler now compares against dbg_callback_initial instead of dbg_callback.

This prevents unnecessary recompilation when tools like Kino modify
dbg_callback at runtime. Previously, such runtime modifications would trigger
a full recompilation of all files using dbg/2, even though the config hadn't
actually changed.

The key insight is that dbg/2 is a compile-time macro, so runtime modifications
to dbg_callback don't affect already-compiled code. Only actual config changes
(reflected in dbg_callback_initial) should trigger recompilation.

This is a more general solution than detecting specific wrapping patterns,
as it works with any tool that modifies dbg_callback at runtime.
2025-12-10 09:21:49 +01:00
Andrea Leopardi dcff0ffaea Fix spec for t:ExUnit.Callbacks.child_spec_overrides/2 (#15006) 2025-12-09 08:14:48 -05:00
José Valim 97407cde90 Do not crash on invalid metadata in deprecated backends, closes #14995 2025-12-04 11:58:18 +01:00
Jean Klingler 579fc97b97 Revert Stream transform simplification causing regression (#14993)
* Revert "Simplify transform as suspend is guaranteed to return one entry"

This reverts commit a7c008f2cd.

* Add regression test case
2025-12-04 18:06:12 +09:00
José Valim 003ef335e7 Fix #iex:break as part of multi-line prompts, closes #14992 2025-12-04 00:10:07 +01:00
José Valim 028cdbb91e Support fetching abstract code for modules compiled with Elixir v1.14 and earlier, closes #14987 2025-12-03 20:00:37 +01:00
José Valim 794441a255 Release v1.19.4 2025-11-27 16:50:26 +01:00
Eksperimental 727106beb4 Fix typos (#14951) 2025-11-27 16:49:21 +01:00
Wojtek Mach 5e45871b5e mix help app:APP: compile current Mix project (if any) (#14958)
Prior to this patch, it was impossible to get help for the current app:

    nimble_csv$ mix help app:nimble_csv
    Application nimble_csv does not exist or is not loaded

It also helps with projects with depenendencies when they haven't been
compiled yet:

    req$ mix deps.get && mix help app:finch # mix deps.compile haven't been run yet
    Application finch does not exist or is not loaded

Now it works:

    nimble_csv$ mix help app:nimble_csv
    Compiling 1 file (.ex)
    Generated nimble_csv app
    # NimbleCSV

    NimbleCSV is a small and fast parsing and dumping library.

    # NimbleCSV.RFC4180

    A CSV parser that uses comma as separator and double-quotes as escape according
    to RFC4180.

    # NimbleCSV.Spreadsheet

    A parser with spreadsheet friendly settings.
2025-11-27 15:36:13 +01:00
José Valim 0db308cbff Clarify @on_load will be called early on releases, closes #14960 2025-11-27 15:26:18 +01:00
José Valim 35a7e3aa1f Fix typo 2025-11-26 19:53:12 +01:00
José Valim 7340597020 Clarify struct and record fields, closes #14963 2025-11-26 19:52:43 +01:00
José Valim 3b6acc33d2 Fix task supervisor name 2025-11-24 18:27:17 +01:00
José Valim 2ed048acc6 Include hex in the list of escript.build special cases (#14953) 2025-11-24 15:28:33 +01:00
José Valim bf0c203d52 Skip warning on :re.import/1 2025-11-21 14:04:19 -08:00
Łukasz Samson b074e6b159 Fix ex_unit formatter crash when find_diff returns nil (#14939) 2025-11-21 05:34:16 -08:00
Jean Klingler bfe7315fbb File.cp_r reports non-existing dest dir properly (#14929) 2025-11-20 21:43:53 +09:00
Jean Klingler 28f4fdc21b Fix dialyzer warning in Code.format_string!/2 (#14928)
* Fix dialyzer warning in Code.format_string!/2

Tentative fix for https://github.com/elixir-lang/elixir/issues/14927

* Add regression test for IO.inspect/2 opts
2025-11-20 19:41:01 +09:00
José Valim ee1dc0729f Ensure parallel matches in assert propagate type information, closes #14921 2025-11-18 15:20:17 -08:00
José Valim 32e724c87c Ensure logger formatter works with chardata, closes #14921 2025-11-18 14:50:50 -08:00
José Valim 7758084211 Add --min-cycle-label to mix xref graph 2025-11-15 06:36:31 -08:00
Jean Klingler 57d3098c2a Fix since in doc for E regex modifier (#14909) 2025-11-14 07:48:38 +09:00
José Valim 427d21e410 Release v1.19.3 2025-11-13 17:33:43 +01:00
José Valim 24a188a016 Share consulting logic between compile.app and release 2025-11-13 16:11:15 +01:00
Jean Klingler 457d52c89b Add 'E' modifier to Regex for :export option (#14907) 2025-11-13 16:11:15 +01:00
José Valim 6684dbd3f2 Simplify format stacktrace handling 2025-11-13 13:10:59 +01:00
Łukasz Samson ccb2a742ad Fix crash when format_kind_reason is executed with TestModule (#14901)
Closes #14900.
2025-11-13 13:10:58 +01:00
Jean Klingler 92094c9ef1 Write ascii printable charlists as such in .app file (#14906) 2025-11-13 18:42:54 +09:00
Wojtek Mach d4d5da2ccf mix help app:APP: Always try loading apps and support Erlang apps (#14894) 2025-11-08 12:23:04 +01:00
José Valim 666c082105 &Mod.fun/arity are valid app terms, closes #14891 2025-11-08 12:23:03 +01:00
José Valim d2c9d39efe Allow forcing specific compilers 2025-11-08 11:47:24 +01:00
Jean Klingler 97d16038fe Fix how UTF8 env is stored in .app file (#14890) 2025-11-04 16:21:08 +09:00
José Valim 87cbf06ff1 Release v1.19.2 2025-11-02 11:50:21 +01:00
José Valim caf39255a4 Ensure relative MIX_BUILD_PATH is discarded on deps
Closes #14875.
2025-11-02 10:17:18 +01:00
José Valim 6440fb90a3 Ensure mix escript.install works with deps.partition, closes #14885 2025-11-02 09:50:18 +01:00
José Valim 8793c68822 Remove line breaks from deprecations table 2025-10-30 21:59:52 +01:00
Gonzalo R 49ac038d67 docs(logger): :process_label is not really ever included in the metadata as of now (#14880) 2025-10-30 17:48:20 +01:00
Leslieandwolfgang 3827b73d2d Fix struct update example in guide (#14877)
Co-authored-by: wolfgang <wolfgang.yin@gmail.com>
2025-10-30 22:53:07 +09:00
José Valim dc8a19028f Commit test changes to MIX_BUILD_PATH 2025-10-30 08:27:25 +01:00
José Valim 357c346a9f Ensure MIX_BUILD_PATH is expanded 2025-10-30 08:13:48 +01:00
Jean Klingler 6df9eb338c Document since for --no-check-cwd + fix typo (#14874) 2025-10-30 07:39:03 +09:00
José Valim fa85b95250 Pass the scm when touching compilation manifest for deps 2025-10-29 23:28:40 +01:00
José Valim a89655c434 Ensure latest version of applications are loaded after compilation 2025-10-29 14:35:23 +01:00
Jean Klingler 730fd0f824 Warn on deprecated ~~~ unary op in tokenizer (#14870) 2025-10-29 19:27:48 +09:00
José Valim 29d3b0cf3a Rely on term_to_binary/1 format to protect from incomplete file writes 2025-10-28 20:27:22 +01:00
José Valim ae94e6eb25 Add checksum to manifest files
Closes #14866.
2025-10-28 12:01:03 +01:00
José Valim 661e537cda Add --no-check-pwd to mix compile 2025-10-28 12:01:02 +01:00
José Valim 4a5c076b7f Warn if elixirc_paths is not a list of string paths, closes #14862 2025-10-28 08:11:04 +01:00
Jean Klingler 297bc8302b Fix dialyzer warning on IO.inspect :label (#14865) 2025-10-28 16:06:40 +09:00
José Valim f6413152ff Ensure flags are passed down to umbrella children
Closes #14852.
Closes #14860.
2025-10-26 13:26:45 +01:00
José Valim c2edbaf173 Raise on invalid .app files (#14861)
Since regular expressions now have regexes,
they can end-up in .app files, and then apps
can no longer boot.

This changes makes it so they fail loudly.
2025-10-24 23:56:06 +02:00
José Valim ac9a659bc6 Reset ansi escapes before newlines in Logger
Closes #14855
Closes #14841
2025-10-24 12:19:22 +02:00
José Valim cc5837caa7 Pass signatures on the side instead of module map 2025-10-23 00:18:29 +02:00
José Valim 1738ed453f Do not include signatures twice in beam files 2025-10-23 00:01:32 +02:00
José Valim 3e18aafa0a Measure and optimize writing of .beam files in the compiler
This should provide meaningful benefits when working with
containers and network mounted drives.
2025-10-22 10:47:03 +02:00
José Valim bcb73a468b Clarify CHANGELOG, closes #14844 2025-10-21 14:26:48 +02:00
José Valim 613f631487 Simplify always true and always false bdd nodes 2025-10-21 12:51:11 +02:00
José Valim f08a84af10 Make bdd functions public to aid debugging 2025-10-21 12:40:29 +02:00
José Valim b1a7afd061 Release v1.19.1 2025-10-20 17:30:38 +02:00
Travis Vander Hoop e9bf5df25a Update documentation for formatter's :excludes option (#14842) 2025-10-18 20:35:08 +02:00
José Valim 2e7bb9b47f Do not spawn partitions when all dependencies are local and ok, closes #14843 2025-10-18 19:07:28 +02:00
José Valim 83c68cecc1 Simplify computation of mismatched parts for error messages
Before we were doing:

    common = intersection(actual, expected)
    uncommon = difference(actual, common)

But the second clause is:

    actual and not (actual and expected)

Which is literally the same as:

    actual and not expected

But much faster as it avoids the large nesting of BDDs.

Closes #14836.
2025-10-18 18:54:39 +02:00
José Valim 184b724483 More optimizations for differences
* when a1 < a2
* when a1 == a2 and c2 == bottom and d2 == bottom
* when a1 == a2 and u2 == bottom
2025-10-18 18:33:18 +02:00
José Valim 47fea9580a Perform expensive operation once 2025-10-18 18:32:44 +02:00
José Valim 6a6678f337 Do not attempt to touch deleted files 2025-10-18 18:32:32 +02:00
José Valim ec87c6b111 Improve protocol violation warnings 2025-10-18 18:32:28 +02:00
José Valim 0c595489b6 Improve protocol type error to list possible root causes 2025-10-18 18:32:22 +02:00
Eric Meadows-Jönsson cdbfa09edb Fix hex upload of Elixir build without -otp- suffix (#14840)
OTP 25 is no longer supported so we didn't upload a generic build.
Instead find the oldest version instead of hardcoding it.
2025-10-17 22:21:14 +02:00
José Valim baa25991fa Do not escape dbg options, closes #14839 2025-10-17 22:15:22 +02:00
Daniil Kulchenko a5c9121a7c Fix EEx.compile_string passing invalid options to tokenize (#14835)
EEx.compile_string/2 was passing all options to tokenize/2, but
tokenize/2 only accepts tokenize_opt (:file, :line, :column,
:indentation, :trim). This caused dialyzer to correctly flag calls
with :engine or :parser_options as type errors in Elixir 1.19+.

The fix filters options before passing to tokenize/2, keeping only
the valid tokenize_opt keys, while still passing the full options
list to EEx.Compiler.compile/3 which needs :engine, :parser_options,
and custom engine options.

Fixes #14834
2025-10-16 23:49:53 +02:00
José Valim 177dc8ab59 Release v1.19.0 2025-10-16 08:52:04 +02:00
José Valim bba8835ecc Fix IEx parser fetching on mix release 2025-10-16 08:46:48 +02:00
José Valim dea8456022 Escape meta within existing quote extensions (#14832)
Closes #14829
Closes #14830
2025-10-14 13:05:44 +02:00
José Valim 544a10ccfa Update CHANGELOG 2025-10-09 10:08:12 +02:00
José Valim ee14357bcd Add --shell to mix cmd (#14827) 2025-10-09 10:07:18 +02:00
José Valim 2fa136e60a Deal with relative paths in mix cmd, closes #14787 (#14788) 2025-10-09 09:27:16 +02:00
Eksperimental f382affce3 Fix typos in v1.19 (#14826) 2025-10-09 08:48:14 +02:00
José Valim 36190985d2 Include a hint for defimpl type checking 2025-10-08 18:27:39 +02:00
José Valim 957f84b7cd Release v1.19.0-rc.2 2025-10-07 18:10:32 +02:00
José Valim 74fab3caf4 Update parallel compiler docs, closes #14821 2025-10-07 17:54:41 +02:00
Eksperimental f1a6a4e019 Improve Kernel.ParallelCompiler warning (#14820) 2025-10-07 17:54:41 +02:00
Eksperimental c7884ca393 Elixir v1.19 introduces a warning related to structs, (#14818)
hint: given pattern matching is enough to catch typing errors, you may optionally convert the struct update into a map update. For example, instead of:

         user = some_fun()
         %User{user | name: "John Doe"}

     it is enough to write:

         %User{} = user = some_fun()
         %{user | name: "John Doe"}

Since this could be seen by new-comers to the language, offering a better user experience by avoiding abbreviations. Favoring the usage of "some_function" instead of "some_fun"
2025-10-07 17:54:41 +02:00
José Valim 6f3fb272af Add mix help app:APP, closes #14782 2025-10-07 17:47:31 +02:00
José Valim 8c14d3a818 Add newline after inspection, closes #14819 2025-10-07 17:15:24 +02:00
José Valim 9e10ea876f Update CHANGELOG 2025-10-07 15:58:51 +02:00
José Valim 64f69cfee8 Improve error message when escaping default values with custom rules in structs, closes #14817 2025-10-07 14:26:11 +02:00
José Valim 7844c98fe7 Ensure escaping works within struct fields, closes #14817 2025-10-07 13:07:42 +02:00
José Valim 519eff5e29 Do not crash on empty test unit groups, closes #14754 2025-10-07 11:19:23 +02:00
Rafał Studnicki e29c40a7e9 Add key-based partitioning to duplicate registries (#14654) 2025-10-06 19:02:01 +02:00
José Valim 9fc3708980 Fix optimizations for closed map checking (#14813) 2025-10-06 17:25:10 +02:00
Jonatan Männchen c06b6a929e Update ORT Scanner (#14594) 2025-10-06 08:39:20 +02:00
Wojtek Mach cfa3bb7429 Fix preloading modules in mix test --slowest-modules=N (#14811) 2025-10-05 21:46:49 +02:00
Art Kay 195a4cf9f7 Optimize Access.filter to eliminate intermediate list creation (#14749) 2025-10-05 15:38:42 +02:00
José Valim 8daa2a6f67 Checkpoint before verification to avoid ignore modules warnings 2025-10-05 15:33:41 +02:00
José Valim d38f331db6 Use a map to track Mix compiler state 2025-10-05 15:29:54 +02:00
José Valim f642b30e42 Release v1.19.0-rc.1 2025-10-05 12:13:11 +02:00
José Valim 23f5ade25c Release v1.19.0-rc.1 2025-10-05 12:07:50 +02:00
José Valim b0c1c35933 Fix regression on direct raise of ExUnit.AssertionError 2025-10-04 21:40:42 +02:00
José Valim 734d76a51b Store bdd leaves in a unified and compact format (#14807) 2025-10-04 13:12:38 +02:00
José Valim b2bf886884 Improve docs for test patterns 2025-10-04 13:10:41 +02:00
Jean Klingler d703e784f4 Fix inaccurate hint for disabling :test_ignore_filters (#14808) 2025-10-04 13:10:36 +02:00
José Valim 44fd817935 Use lazy bdd for all types (#14806)
We use Lazy BDDS: ternary trees (instead of binary) where the additional node
encodes a lazy union, as in "COVARIANCE AND CONTRAVARIANCE:
A FRESH LOOK AT AN OLD ISSUE", with some additional optimisations
for intersections and differences to avoid materialising unions.
2025-10-04 13:10:31 +02:00
José Valim f8dcd941ff Fix :file.pwrite return type 2025-10-01 14:44:40 +02:00
José Valim ce403643cd Include previous clause line on default errors/warnings, closes #14804 2025-10-01 08:45:01 +02:00
José Valim 668c9a8f5d Revert "Do not allow protocols to define structs nor exceptions, closes #14158"
Projects rely on this feature, therefore we have to revert
to avoid breaking changes.

This reverts commit 01c82022f2.

Closes #14803.
2025-10-01 08:21:28 +02:00
Eksperimental 3175901d89 Align Regex dotall modifier to PCRE2 (#14792) 2025-09-30 16:48:03 +02:00
José Valim a2baac915a Address regressions on 'not in' operator
Closes #14783.
2025-09-30 16:47:11 +02:00
Jonatan Kłosko 5a5e3855aa Improve user detection in lock and pubsub implementation (#14801) 2025-09-30 16:46:31 +02:00
Jonatan Kłosko cd580f679c Fix File.rename/2 race condition in lock implementation on windows (#14800) 2025-09-30 16:46:31 +02:00
José Valim 33347f107e Update CHANGELOG 2025-09-29 09:36:03 +02:00
José Valim cb15a3dd4c Convert line break error into a warning for v1.19 2025-09-29 09:26:15 +02:00
José Valim a4f98b1177 Optimize DNFs to avoid negations when possible 2025-09-28 20:01:26 +02:00
Guillaume Duboc 5b2f9c9201 Rewrite maps, tuples, lists as BDDs and improve performance (#14693) 2025-09-28 19:05:30 +02:00
Zach Daniel b969aead71 Make errors when piping expressions in IEx safer (#14795) 2025-09-28 16:18:23 +02:00
Jean Klingler bfbd7658b3 Assert scope is not match/guard when using escaped regexes (#14780) 2025-09-27 23:02:05 +09:00
José Valim 1327c0c5c3 Improve docs for shift vs add 2025-09-22 10:42:54 +02:00
Jean Klingler 64c61df2bf Warn on boot for OTP28.0 (#14732) 2025-09-18 19:10:12 +09:00
Jean Klingler 4363dffaf5 Fix Macro.escape/1 bug when :quote tuples is in the tail of a list (#14775)
Close https://github.com/elixir-lang/elixir/issues/14771
2025-09-17 07:15:52 +09:00
Jean Klingler 7be008c1b0 Bugfix: Macro.escape/1 properly escapes meta in :quote tuples (#14773)
Close #14771

Also internally renames the `op` field inside `elixir_quote`: `none -> escape`, `prune_metadata` -> `escape_and_prune`, `add_context -> quote`.
2025-09-16 18:37:16 +09:00
Jonatan Männchen 499a9c55d3 Correct builds.hex.pm Publish Condition in CI (#14772) 2025-09-15 23:14:27 +02:00
José Valim b18cdb0f7a Do not persist temporary compilation warnings, closes #14768 2025-09-14 10:45:01 +02:00
José Valim 62949b0728 Revert "Convert verification errors into diagnostics, closes #14768"
This reverts commit 2dc3d2713c.

This solution will still leave a corrupted state if
verification is manually aborted.
2025-09-14 10:44:58 +02:00
José Valim cb108d5ce5 Unify mix recompile and external resource handling
Imagine the following scenario:

1. the user changes an external resource
2. the user calls mix compile and it fails
3. the user reverts the changes to the external resource

We had already fixed this bug for `__mix_recompile__?`.
Therefore, this pull request unifies how both are handled
by touching the source file after we detect it is stale
(which is also what we do on every subsequent compilation
cycle).

Note that before we stored the result of `__mix_recompile__?`
in the checkpoint file. However, that's not needed. The checkpoint
is useful to track changes to sources that may not define any module.
But since the compilation check and external resources always
require a module, touching the file is enough.

Closes https://github.com/phoenixframework/phoenix/issues/6476.
2025-09-13 21:32:04 +02:00
José Valim 1f7ddd7acd Convert verification errors into diagnostics, closes #14768 2025-09-13 20:07:44 +02:00
José Valim 7477c9326e Expand on why we supervise, not how (#14764)
Closes #14763.
2025-09-10 22:16:01 +02:00
Jean Klingler c0e334df2a Add missing :generated to Macro.escape_opts/0 type (#14761) 2025-09-10 20:14:26 +09:00
José Valim fd9dbf8490 Update Unicode to version 17.0.0 (#14760)
This is an automated commit created by the Maintenance project
https://github.com/eksperimental/maintenance

Please read the release notes by visiting
<http://www.unicode.org/versions/Unicode17.0.0/>.
2025-09-10 09:10:32 +02:00
Jean Klingler 7f45befed6 Have mix test fail if warnings and --warnings-as-errors (#14756) 2025-09-10 07:46:45 +09:00
Nathan Long bbcd7f3094 ExUnit sets a process label for each test (#14758) 2025-09-09 21:53:24 +02:00
José Valim 51ac9bc324 Accept any enumerable in Logger.metadata/1 2025-09-09 15:31:19 +02:00
Jean Klingler c8f8d02cc0 Fix dialyzer opaqueness warnings on module attrs in OTP28 (#14755)
* Mark module attributes as generated in case they contain opaque terms

* Add and use Macro.escape(ast, generated: true)
2025-09-09 17:51:50 +09:00
Jean Klingler d0634c9188 Fix infinite loop: Enum.take/2 with negative index on empty enum (#14747) 2025-09-05 21:25:51 +09:00
Jonatan Männchen cc797de249 tighten CI secret scope and move AWS config to environment vars (#14627)
* Add `environment: release` to the "publish-to-hex" job so that only
  workflows explicitly targeting the release environment can read
  sensitive values.
* Gate the job behind `if: ${{ vars.HEX_AWS_REGION }}` to avoid noisy
  failures in forks where the variable is not configured.
* Replace `${{ secrets.HEX_AWS_REGION }}` / `${{ secrets.HEX_AWS_S3_BUCKET }}`
  references with `${{ vars.* }}`.  These are not credentials, so
  environment-level *variables* are a better fit and keep them readable
  only by jobs that declare the environment.
* Remove Fastly secrets from the job-wide `env:` block and inject them
  only into the Fastly purge step, following the principle of least
  privilege.  Other steps no longer see these tokens.

Restricting secret visibility to an environment and to the exact step
that needs them reduces the blast radius of a compromised workflow run,
blocks accidental exposure in logs of unrelated steps, and stops forks
from obtaining privileged data.
2025-09-03 22:14:58 +02:00
José Valim f56139aa2c Add closing token metadata to a.{}, closes #14682 2025-08-31 22:10:01 +02:00
José Valim 8ac8230e18 Properly handle column for 'in' in 'not in' operator
Closes #14681.
2025-08-31 22:10:00 +02:00
sabiwara 0fca9beaf7 Remove test for warning disabled on 1.19 2025-08-31 20:10:59 +09:00
Jean Klingler b3e3e8c6aa Do not consider variables from pattern in bitstring modifier (#14738) 2025-08-31 19:21:15 +09:00
José Valim 61603a8725 Update CHANGELOG 2025-08-31 11:20:33 +02:00
José Valim 44c5069573 Update CHANGELOG 2025-08-31 11:11:53 +02:00
Jesse Stimpson a96c04f260 Improve docs on iex remote shell halt behaviour (#14721) 2025-08-31 10:59:33 +02:00
Eksperimental d458fcb9f5 Fix order in ExUnit results when listing pinned variables (#14723)
The pinned variables were returned in a random order (often reversed):

     test/ex_unit_pinned_variables_order_test.exs:23
     match (=) failed
     The following variables were pinned:
       var_d = "four"
       var_c = "three"
       var_b = "two"
       var_a = "one"
     code:  assert %{a: ^var_d, b: ^var_c, c: ^var_b, d: ^var_a} = build(var_a, var_b, var_c, var_d)
     left:  %{a: ^var_d, b: ^var_c, c: ^var_b, d: ^var_a}
     right: %{a: "one", b: "two", c: "three", d: "four"}
     stacktrace:
       test/ex_unit_pinned_variables_order_test.exs:29: (test)

This fix sorts them alphabetically.

This bug was introduced in 884e93391e when the pinned vars were now accumulated in a map (instead
of a list).

A repo replicating the issue can be found here:
- https://github.com/eksperimental-debug/elixir_debug/tree/ex-unit-pinned-variables-order
- https://github.com/eksperimental-debug/elixir_debug/blob/ex-unit-pinned-variables-order/ex_unit_pinned_variables_order/test/ex_unit_pinned_variables_order_test.exs
2025-08-31 10:58:58 +02:00
José Valim d507502ecd Update bidi/line break character checks according to UX#55 2025-08-31 10:57:43 +02:00
José Valim 2bb27ea128 Only break newlines if original char is a newline 2025-08-31 10:57:43 +02:00
Lukasz Samson 6fbc6e08a0 Advance line when processing ? followed by <LF> and \<LF>
Closes #14715.
2025-08-31 10:57:43 +02:00
José Valim 71bb017b61 Raise if message in AssertionError is not a binary
Closes #14695.
2025-08-31 10:57:43 +02:00
Łukasz Samson 1fadefe25f Catch-all clause for unbalanced terminators (#14694) 2025-08-31 10:57:43 +02:00
Jean Klingler e1f34d09af Shallow-validate the return of __escape__ (#14736) 2025-08-31 15:51:46 +09:00
Jean Klingler ff21a9d601 Add __escape__/1 and use it to fix Regex escaping in OTP28.1+ (#14720)
Leverages newly added :re.import/1.
https://github.com/erlang/otp/pull/9976
2025-08-30 18:29:56 +09:00
Jean Klingler 1778bf211c Inspect ill-formed structs as maps (#14718) 2025-08-23 17:16:20 +09:00
José Valim 99f2c8f2ea Fix docs for Macro.compile_apply/4 2025-08-17 09:53:59 +02:00
José Valim 54369ba3e5 Improve docs on DynamicSupervisor blocking operations 2025-08-15 19:39:20 +02:00
Steve Cohen ba0f3935d3 Fix filtering documentation (#14705)
The filtering documentation implied that the msg attribute of the
logger event map could be a binary, but according to the erlang types
(https://www.erlang.org/doc/apps/kernel/logger.html#t:log_event/0) it
can't be a binary.

This change updates the docs with an example that comports with the
actual typing.
2025-08-08 08:43:50 +02:00
Eksperimental b566edc2ab ExUnit: Raise explaining what failed on invalid tags (#14707) 2025-08-08 08:43:49 +02:00
Chris Hicks 297f1f3edf Add options to mix format to allow excluding of files (#14702) 2025-08-06 10:05:21 +02:00
Łukasz Samson 764235f1da Fix expand crash on invalid multialias root (#14698) 2025-08-06 10:05:20 +02:00
ice_cap 8d216b87cd Update structs.md (#14683) 2025-08-04 08:59:33 +02:00
José Valim 2c7fa47ad6 Remove general catch on sigil token 2025-07-28 08:28:58 +02:00
Łukasz Samson 71e1ddc64e Return error on invalid unicode sequences (#14666) 2025-07-27 19:04:52 +02:00
José Valim f50f35fd1c Add --name-pattern option to mix test and regex support to OptionParser (#14674) 2025-07-26 19:56:05 +02:00
José Valim 5bc81aaa0a Enhance OptionParser.ParseError with available options display (#14673)
Example output:

  Expected one of:
    --count INTEGER (alias: -c)
    --debug, --no-debug (alias: -d)
    --files STRING (alias: -f) (may be given more than once)
    --verbose, --no-verbose (alias: -v)

Prompt
======

When we raise ParseError, include all of the options we could
potentially accept, alongside their types and aliases. For example,
the switches `[foo: :string, bar: :integer]` and `aliases: [b: :bar]`,
the error message should say:

    Expected one of:
      --foo STRING
      --bar INTEGER (alias: -b)

Furthermore, for types that are :keep (which default to string), you should
add:

    --bar INTEGER (alias: -b) (may be given more than once)

And boolean ones accept no arguments, so they should be written as:

    --baz, --no-baz

Sort all of them alphabetically.
2025-07-26 19:56:05 +02:00
José Valim 99cef0686c Improve ExUnit docs 2025-07-26 19:56:05 +02:00
Paul Gideon Dann 2308be941e Validate type of :deps_paths option for formatter_for_file/2 (#14669) 2025-07-26 19:56:05 +02:00
Jean Klingler 79005e370e Fix opaqueness violation in Task.Supervisor (#14656) 2025-07-18 06:43:08 +09:00
José Valim 1f75ade71f Avoid adding lists that match negations 2025-07-16 22:09:44 +02:00
José Valim a719cb3b21 Update checker to v2 as representation has changed 2025-07-16 16:39:29 +02:00
Guillaume Duboc ac901d9d27 Remove duplicate for map_difference 2025-07-16 16:20:38 +02:00
Benjamin Milde f9966230ee Docs updates und restructuring (#14636) 2025-07-12 22:08:19 +02:00
Vasilis Spilka 9b729ca4c1 Add printable_limit and limit to IO.inspect doc examples (#14646) 2025-07-12 22:08:17 +02:00
Michał Łępicki 8cba20cb3e Clean up unreachable clause of Types.Descr.atom_only? helper (#14647)
it's being always called with a map
2025-07-12 22:08:10 +02:00
Jean Klingler b7e0d657d1 Drop :app_properties when rendering dependency in mix (#14645) 2025-07-12 21:11:21 +09:00
José Valim 2c3edbe1c7 Check for type equality 2025-07-11 16:15:53 +02:00
José Valim fec9899ead Revamp Mix & OTP guides (#14637) 2025-07-11 15:53:07 +02:00
Gary Rennie f54b192823 Add ETS to the Erlang Term Storage section of erlang libs (#14644)
This will ensure that it appears in the short search results on ExDoc
instead of having to navigate through to the search results.
2025-07-11 15:52:06 +02:00
Guillaume Duboc 855df4fc77 Domain keys in map (#14478)
- Introduced tests for union, intersection, and difference operations involving domain key types.
- Validated subtype relationships and intersection results for maps with domain keys.
- Enhanced map fetch and delete functionalities to handle domain key types.
- Ensured correct behavior of dynamic types with domain keys in various scenarios.
2025-07-11 15:52:06 +02:00
José Valim f87fbc2833 Clarify function types 2025-07-11 15:52:06 +02:00
José Valim 2b8ee38660 Tag / as an operator in fragments, closes #14643 2025-07-11 11:12:32 +02:00
José Valim 8d2775051d Update ... to an operator in Code.Fragment 2025-07-11 11:12:31 +02:00
Łukasz Samson 26855eda9f Update allow_local option spec (#14642) 2025-07-10 20:48:43 +02:00
José Valim ea77a68daa Add tests for allow_locals option 2025-07-10 17:32:08 +02:00
Łukasz Samson cb49bfb6e4 Add local_for_callback option to Macro.Env.expand_import (#14620) 2025-07-10 17:32:07 +02:00
Eksperimental c3ec400607 Use thin space (U+2009) as a separator instead of _ and in regular English language (#14635) 2025-07-10 10:22:11 +02:00
Eksperimental c03caa18cf Use backticks around literals in documentation (#14633) 2025-07-10 10:22:10 +02:00
José Valim bd68532c27 Remove explicit mentions to elixirc, as it isn't used in practice 2025-07-10 10:21:55 +02:00
Eksperimental a244323815 Correct grammar in structural sorting order section (#14639)
It is not required by the Elixir developer, but it is not required for them to know this by heart.

The former indicates that it is the Elixir developers who are not requiring this, the latter expresses
that they do not need to know this by heart.
2025-07-10 09:31:15 +02:00
Eksperimental fff2cf0d81 Standardize "Examples" heading section levels in docs (#14638)
* Convert "Examples" 3rd level headings to 2nd level when not under a 2nd level

* Convert 1st level "Examples" heading to 2nd level
2025-07-10 09:31:13 +02:00
José Valim b878f37577 Improve error message for protocols with no implementation, closes #14364 2025-07-10 09:30:42 +02:00
Jean Klingler 9619116cde Prevent mix test from overriding :failures_manifest_path option (#14632)
Introduced in 99be673
2025-07-08 16:08:16 +09:00
José Valim 248a71e2fc Fix logger docs
Closes #14628.
Closes #14629.
2025-07-06 09:46:35 +02:00
José Valim aa6964547d Fix return type of phi, closes #14621 2025-07-04 09:34:39 +02:00
José Valim 5c7687bf6f Apply further fn optimizations and fixes (#14619)
Closes #14598
2025-07-03 16:00:56 +02:00
Guillaume Duboc e73dd0c17d Perf optimizations and inferred intersections (#14605) 2025-07-03 16:00:48 +02:00
Guillaume Duboc f4037a31ba Simplified tuple definitions by removing negations (#14596) 2025-07-03 16:00:40 +02:00
José Valim a5b9c5b5db Add required field back to struct info
Closes #14616.
Closes #14617.
Closes #14500.
2025-07-03 12:22:25 +02:00
Łukasz Samson fcea4b4755 Handle filesystem errors in iex helpers (#14618)
`File.cd` and `File.ls` can return any posix error code
2025-07-03 10:29:41 +02:00
Michał Łępicki 24e63ccc47 Fix parallel option type in Mix.Compilers.Erlang.compile/6 spec (#14615)
compile.yecc and compile.leex tasks call it with parallel: true
2025-07-01 22:34:58 +02:00
Łukasz Samson d8bb1c4009 Add missing erlang compiler options (#14614)
Document options on leex and yecc compilers
2025-07-01 22:34:57 +02:00
José Valim fa2c96ffc3 Remove specs which are pass through and from private modules 2025-07-01 10:44:15 +02:00
Łukasz Samson db203167e2 Replace keyword with concrete keyword lists in specs (#14611) 2025-06-30 17:24:37 +02:00
José Valim a0ca37cb8f Fix warnings on Erlang/OTP 28 2025-06-30 11:57:26 +02:00
José Valim 592f44e2a8 Remove Regex warning until Erlang/OTP 28.1 2025-06-30 10:55:27 +02:00
José Valim 5ebe19e333 Make sure we log all output when partition fails 2025-06-27 15:30:41 +02:00
José Valim 9e3e817e42 Deal with undefined on :shell.whereis/0 2025-06-26 13:49:01 +02:00
José Valim a450d12912 Do not send quoted expressions to Macro.dbg 2025-06-26 13:38:17 +02:00
José Valim 65dbdef7dc Fix pry on Erlang/OTP 28 2025-06-26 13:32:57 +02:00
Jonatan Männchen 4f5746369b Use Workload Identity Federation for Windows Trusted Signing (#14604) 2025-06-25 19:31:00 +02:00
Steffen Deusch 274bc56f53 Add compilers option to Mix.install/2 (#14577) 2025-06-22 11:54:27 -07:00
José Valim 7d3047981c Distinguish source_anno from doc_anno, see #14595 2025-06-21 03:54:49 -07:00
José Valim 4f2868e632 Ensure block_keyword_or_binary_operator is handled in surround context, closes #14590 2025-06-21 03:25:44 -07:00
Łukasz Samson 41151190e5 Handle error result from unescape_tokens in tokenizer (#14587) 2025-06-19 12:02:26 -07:00
Łukasz Samson 711008a456 Consistently raise UnicodeConversionError in tokenizer (#14589) 2025-06-19 12:02:25 -07:00
Łukasz Samson 7a28203ff5 Fix invalid warning on no parens call on true (#14593) 2025-06-19 05:57:39 -07:00
José Valim f4bbf76d0b Optimize empty_difference_subtype? for dynamic parts 2025-06-14 12:13:19 -07:00
Jean Klingler 9fdb835f26 Mark inlined function call result as generated (#14581) 2025-06-14 19:07:36 +09:00
José Valim 95f982e004 Document bug fix on defstruct/defexception inside protocol, closes #14574 2025-06-12 11:36:47 +02:00
José Valim 7e08594ca0 Add tests for nested struct updates too 2025-06-11 17:05:24 +02:00
José Valim 2096f4156d Convert only the pattern matching suggestion into a hint 2025-06-11 14:08:07 +02:00
José Valim 9d845cf031 Update CHANGELOG 2025-06-11 13:59:26 +02:00
José Valim d74dff2bcf Transform the struct update syntax into a type assertion
This transforms the struct update into a type assertion,
requiring the type system to be sure the expression has
precisely the given struct type.

The struct update syntax may still be deprecated in the
future but this will provide a safer migration path and
allow us to engage in more conversations with the community.
2025-06-11 13:39:14 +02:00
José Valim 59a1ad9138 Allow captures to be reconstructed on type system pretty printing 2025-06-11 13:30:51 +02:00
José Valim 342c724863 Do no start listeners if --no-deps-check is given 2025-06-11 12:08:48 +02:00
José Valim 35818f1cdb Warn when invalid fun typespec is used 2025-06-11 10:25:24 +02:00
Joe Yates 502207c9f7 Fix use of prefer with '-ing' (#14568) 2025-06-10 11:17:13 +02:00
José Valim c3bc849550 Point out module must be required before macro usage in match/guard 2025-06-10 10:14:56 +02:00
José Valim f8de42a053 Filter @compile debug_info when explicitly set to true
Closes #14567.
2025-06-10 09:59:05 +02:00
José Valim ce33663780 Relax return type of impl_for with nil to avoid false positives 2025-06-10 09:49:55 +02:00
Tomasz Marek Sulima 752ca8864d Sort by call on tprof memory tests (Erlang/OTP 28) (#14565) 2025-06-09 22:27:08 +02:00
Theodor-Alexandru Irimia 8588a8d473 Clarify why and how to start second session for tests (#14566) 2025-06-09 22:27:08 +02:00
José Valim 2a9a4f2cab Release v1.19.0-rc.0 2025-06-09 12:27:46 +02:00
Michał Łępicki 0847e4b41a Fix mistake in "Untracked compile-time dependencies" anti-pattern (#14563) 2025-06-09 12:07:28 +02:00
José Valim 7aafd6ca77 Also download rebar3 automatically when compiling 2025-06-08 19:13:12 +02:00
José Valim 5ff421ca27 Disable inference as part of v1.19 release 2025-06-07 21:23:03 +02:00
José Valim 4425d684a0 Update CHANGELOG 2025-06-07 20:21:55 +02:00
José Valim e3f538cd25 Branch out v1.19 2025-06-07 20:18:21 +02:00
José Valim 577c8033ba Optimize subtype? by aborting early (#14561)
In the same way we introduced non_disjoint_intersection
to optimize compatibility, we introduce empty_difference
to optimize subtyping.
2025-06-07 15:35:41 +02:00
Jean Klingler 84d806603d More precise error message when escaping a regex with a ref (#14560)
* More precise error message when escaping a regex with a ref

* Refactor as do_quote_map_value

* Yet another refactoring proposal
2025-06-07 21:56:10 +09:00
José Valim b1053b75c5 Use binary appending now that we require Erlang/OTP 26
Prompt
======

There is a format/2 function in inspect/algebra.ex that
calls format/3 recursively that returns a list of binaries.
Instead of doing so, I want you to pass a binary as accumulator,
<<>>, and then instead of `[head | format(...)]`, on every
operation you will do `format(<<acc::binary, result::binary>>, ...)`.

Notes
=====

The assistant was able to detect an issue with `collapse/4`
but it was unable to fix it. That and the changes to
mix/tasks/format.ex were addressed manually.
2025-06-07 12:46:47 +02:00
José Valim b82d2dfa02 Reduce allocation on Inspect.Algebra by using cons cells 2025-06-07 09:57:36 +02:00
José Valim 443d96a396 Introduce non_term_type_to_quoted to avoid additional term_type? checks 2025-06-05 17:45:07 +02:00
José Valim 56e7de39c0 Also pretty print structs with no fields in types 2025-06-05 17:39:49 +02:00
José Valim 063a673901 Check for term_type?(dynamic) before computing difference 2025-06-05 17:33:30 +02:00
José Valim c6ed523914 Fix bugs and add completeness to function pretty printing 2025-06-05 09:18:32 +02:00
José Valim 7307f3c68d More tests for function application 2025-06-05 09:18:32 +02:00
Anthony Accomazzo 2f9be83702 Grammar tweaks in v1.19 CHANGELOG (#14557) 2025-06-04 22:25:50 +02:00
José Valim b0da8c15ec Add acknowledgements 2025-06-04 13:58:35 +02:00
José Valim f295a89cc7 Update CHANGELOG 2025-06-03 11:08:34 +02:00
José Valim f445cb98c3 Add CHANGELOG for function application and improve pretty printing 2025-06-03 10:53:06 +02:00
José Valim 4b6fdb40e8 Add more tests around compatibility check 2025-06-02 17:05:48 +02:00
José Valim 7819a73311 Simplify compatibility check 2025-06-02 16:53:06 +02:00
José Valim f6595985a5 Remove compile-time warnings 2025-06-02 16:17:15 +02:00
José Valim 1f07dff853 Also version rebar dependencies according to otp-elixir-scm, closes #14548 2025-06-02 16:06:53 +02:00
José Valim e87cc66041 Allow updated options to be returned from Inspect protocol (#14554) 2025-06-02 15:55:36 +02:00
José Valim 85e068e144 Do not read ~/.iex.exs in tests (#14553)
Closes #14514.
2025-06-02 10:11:38 +02:00
José Valim 21ed59c578 Add more docs and examples around function_exported?/3 and macro_exported?/3 2025-06-01 10:00:58 +02:00
Isaac Yonemoto 2013e93db4 Add warnings on function exported and optional callback (#14552) 2025-06-01 09:49:48 +02:00
Michał Łępicki 8442f697f5 Do not pass invalid option case: :lower to url_encode64 (#14550) 2025-05-31 11:13:26 +02:00
José Valim 41d1a721ad Do not skip dynamic for indivisible types inside functions 2025-05-30 11:31:21 +02:00
José Valim 65e084e8f8 Infer types for fn 2025-05-30 11:31:21 +02:00
José Valim 0445e9d606 Add denormalization when pretty printing functions 2025-05-30 11:31:21 +02:00
José Valim a0d649cce9 Prove hints for empty domains from unions 2025-05-30 11:31:21 +02:00
Frank Hunleth 590a827046 Add ANSI codes to turn off conceal and crossed_out (#14545) 2025-05-29 18:57:42 +02:00
José Valim 13f0eef2ca Reduce filesystem names on Mix.install, see #14543 2025-05-29 17:16:56 +02:00
Dmitry Kleymenov 340e63b9fe Add doctests to kernel guards (#14544) 2025-05-29 09:46:14 +02:00
José Valim c39286ef9c Improve error report and checks on function application (#14541) 2025-05-28 15:25:37 +02:00
José Valim 7f1fe47f6d Add Code.Fragment.lines/1 (#14493) 2025-05-28 11:04:30 +02:00
Łukasz Samson 56fb419d5e Improve documentation on Exception.message and Exception.blame (#14542) 2025-05-28 11:04:14 +02:00
José Valim 0a77a352ab More type checking of function applications (#14508) 2025-05-27 10:39:54 +02:00
José Valim fb19dec15c Include lines in == Compilation error in file ... == slogans (#14538)
Closes #14535.
2025-05-27 10:31:17 +02:00
dependabot[bot] 1a8d13c12a Bump erlef/setup-beam from 1.18.2 to 1.19.0 (#14537)
Bumps [erlef/setup-beam](https://github.com/erlef/setup-beam) from 1.18.2 to 1.19.0.
- [Release notes](https://github.com/erlef/setup-beam/releases)
- [Commits](https://github.com/erlef/setup-beam/compare/5304e04ea2b355f03681464e683d92e3b2f18451...8aa8a857c6be0daae6e97272bb299d5b942675a4)

---
updated-dependencies:
- dependency-name: erlef/setup-beam
  dependency-version: 1.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-27 09:02:41 +02:00
José Valim 99145c7ed4 Add Mix.Task.Compiler.run/2 2025-05-27 08:45:22 +02:00
Guillaume Duboc 17d51d11fb Add tuple elimination of negations (#14531) 2025-05-26 19:28:25 +02:00
José Valim 336627d474 Clarify and link to config modules, closes #14533 2025-05-26 10:13:39 +02:00
Łukasz Samson 22467ab6c1 Do not use Access protocol on values that may be structs (#14532) 2025-05-26 08:25:14 +02:00
José Valim 360c23965d Allow early precompiled Elixir to run on Erlang/OTP 28 2025-05-25 16:52:05 +02:00
silverdr 9310ffc9c4 Explicit mention of PID (#14529) 2025-05-24 15:09:56 +02:00
Jean Klingler aa8672c4ee Improve test coverage of the Time module (#14525)
* Add tests for Time.to_iso8601/2

* Add tests for Time.compare/2 with different calendars

* Add tests for Time.diff/3

* Test Time.shift/2 with invalid values
2025-05-24 18:19:40 +09:00
Dmitry Kleymenov 5c270037fc Add examples to File (#14528) 2025-05-24 10:31:03 +02:00
Rudolf Manusadzhian 9938105fb4 Fix tiny typo in Logger docs (#14527) 2025-05-24 15:29:17 +09:00
José Valim a7703a9ee2 Use Keyword.get_lazy/3 2025-05-23 17:52:12 +02:00
Jean Klingler 8a13ed49e5 Improve test coverage of the Date module (#14524)
* Add tests for Date.utc_today/1

* Add test for non-integer duration shift

* Add test for Date.convert!/2 with incompatible calendars

* Add test for Date.diff/2 with incompatible calendars

* Add test for Date.compare/2 with incompatible calendars
2025-05-23 17:26:25 +02:00
José Valim c9ddb575af Multi-line prompts in IEx (#14522) 2025-05-23 12:34:35 +02:00
Dmitry Kleymenov b4bcc3b32e Put error doctests to run pipeline (#14520) 2025-05-23 11:07:27 +02:00
José Valim 719ffce5ea Clarify use of environment variables 2025-05-22 15:36:53 +02:00
José Valim 050f9e28b4 Run Erlang/OTP 28 on Windows CI (#14513) 2025-05-22 10:21:24 +02:00
Dmitry Kleymenov d636a0a5cc Add clarity to docs and doctests in Enum (#14511) 2025-05-21 16:50:48 +02:00
José Valim 51e160e6f8 Add OTP 28 to CI (#14510) 2025-05-21 13:48:13 +02:00
Dmitry Kleymenov 945c057bf9 Enum scan improvements (#14486) 2025-05-20 15:19:07 +02:00
João Paulo Abreu 30d1968bd5 Enhance non-assertive map access anti-pattern documentation (#14501) 2025-05-20 14:36:12 +02:00
José Valim 81f885da6c Add anti-pattern on untracked compile-time dependencies, closes #14465 2025-05-20 11:07:29 +02:00
José Valim 8de98a8a86 Raise nice error message when escaping maps/structs with references, closes #14497 2025-05-20 10:35:17 +02:00
José Valim 1f9433feaa not_a_map.key should raise BadMapError for consistency 2025-05-20 09:58:15 +02:00
José Valim f400ac9555 Mention patterns are not allowed in function heads
Closes #14496.
2025-05-20 09:00:33 +02:00
dependabot[bot] ca069f7ab6 Bump DavidAnson/markdownlint-cli2-action from 19.1.0 to 20.0.0 (#14507)
Bumps [DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action) from 19.1.0 to 20.0.0.
- [Release notes](https://github.com/davidanson/markdownlint-cli2-action/releases)
- [Commits](https://github.com/davidanson/markdownlint-cli2-action/compare/05f32210e84442804257b2a6f20b273450ec8265...992badcdf24e3b8eb7e87ff9287fe931bcb00c6e)

---
updated-dependencies:
- dependency-name: DavidAnson/markdownlint-cli2-action
  dependency-version: 20.0.0
  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>
2025-05-19 23:52:01 +02:00
Damir Vandic a3ec2c6d74 Fix some typos (#14505) 2025-05-19 16:34:37 +02:00
Ahmed Elawad 638ec9d0ac docs: Debugging page typo (#14502) 2025-05-18 09:00:58 +02:00
Wojtek Mach 8b79681fa7 mix release: Fix RELEASE_SYS_CONFIG for Windows 11 (#14495)
wmic is no longer available on Windows 11.

As a reminder, this codepath is only hit when setting:

    reboot_system_after_config: true

So I'd guess few users were affected.
2025-05-15 09:42:54 +02:00
Matt Enlow 088e770c0b Module docs: make section references into links (#14494) 2025-05-14 11:25:59 +02:00
José Valim 36888adc07 Rely on more consistent return types for fun descr 2025-05-14 09:36:07 +02:00
José Valim 0d828d86df Use fun(arity) in more places during type checking 2025-05-14 09:36:07 +02:00
Jean Klingler e215843dd3 Optimize Enum.count_until/2-3 (#14491) 2025-05-14 08:58:45 +02:00
Matt Enlow c3d33410ec Parameterize violates the "never run concurrently" async docs (#14492) 2025-05-13 12:37:26 +02:00
José Valim 99b6e435ee Add tests for function to quoted 2025-05-12 18:14:21 +02:00
Guillaume Duboc df007f7a14 Add set-theoretic function types (#14369) 2025-05-12 17:13:45 +02:00
Jean Klingler dadbfade76 Rename function with typo: truthness -> truthiness (#14489) 2025-05-12 16:57:42 +09:00
Jean Klingler 465292fcf0 Fix CI failing coverage 2025-05-12 16:56:28 +09:00
Jean Klingler e9c85a4e47 Allow using protocol list implementation on improper lists (#14366)
* Allow using protocol list implementation on improper lists

* Regroup regression integration tests
2025-05-11 19:41:58 +09:00
João Paulo Abreu 84c64807bc Doc: Fix markdown link formatting in debugging section (#14487) 2025-05-10 17:13:00 +02:00
Eksperimental 9d7c7c1226 Improve arg name in recently deprecated Node.start/2-3 (#14482) 2025-05-10 11:59:30 +02:00
Eksperimental eef6c3cb2a CI: build does using --warnings-as-error ExDoc flag (#14485) 2025-05-10 11:30:41 +02:00
Eksperimental 88a5bec6af Introduce DOCS_OPTIONS env variable to Makefile (#14484)
Usage:

    DOCS_OPTIONS="--warnings-as-errors" make docs

    DOCS_OPTIONS="--warnings-as-errors --formatter html" make docs
2025-05-10 10:33:13 +02:00
Jean Klingler e96ccaaebc Fix small typos in changelog and Duration doc (#14483) 2025-05-10 16:54:21 +09:00
Eksperimental 4fa2240997 Fix warnings due to incorrect links in docs detected by ExDoc (#14480) 2025-05-09 20:44:04 +02:00
Eksperimental 5eacb11ff0 Fix grammar: published on Hex.pm (#14481) 2025-05-09 20:43:02 +02:00
José Valim d1cc9900b8 Document hex options 2025-05-09 19:30:58 +02:00
Travis Vander Hoop 7657f7e65a Update CHANGELOG.md (#14479) 2025-05-09 17:24:07 +02:00
Wojtek Mach 5fbf793e04 Install Hex and Rebar per OTP release (#14477) 2025-05-09 15:44:11 +02:00
José Valim 670cc0954f Update CI 2025-05-09 13:25:10 +02:00
José Valim 1202396b57 Update CHANGELOG 2025-05-09 10:27:12 +02:00
José Valim 9076a521ab Add --no-listeners to mix compile and mix run 2025-05-08 10:18:39 +02:00
xinz a4df56de75 Fix doc of macros section of meta-programming (#14476) 2025-05-08 09:09:45 +02:00
Jean Klingler af29eeff77 Increase test coverage for Enum (#14473)
* Add test for Enum.slice with start > 0 and step > 0

* Cover more branches of sort internals
2025-05-07 16:20:47 +09:00
Jechol Lee 3c3637cc4a Make --min-cycle-size in mix xref graph inclusive (#14475)
Updates cycle filtering logic to include cycles matching the minimum size.
Improves test cases to ensure accurate handling of `min_cycle_size` scenarios.

Closes #14474
2025-05-07 09:03:48 +02:00
José Valim eb119dfee0 Add a section on comparing durations, closes #14472 2025-05-07 08:50:56 +02:00
Jean Klingler 170b797b51 More test coverage for Enum for non-list inputs (#14471)
* Add tests for Enum.max/2 with empty fallback

* Add tests for Enum.min/2 with empty fallback

* Add tests for Enum.random/1 with streams or empty range

* Add tests for Enum.reduce/3 for streams & maps
2025-05-06 22:07:14 +09:00
Jean Klingler 36ac4fc843 Improve test coverage for Enum for non-list inputs (#14470)
* Add tests for Enum.count_until/2-3 with streams

* Add tests for Enum.empty?/1 with streams

* Add tests for Enum.flat_map/2 with streams

* Add tests for Enum.intersperse/2 with ranges
2025-05-06 20:57:44 +09:00
Stepan Yurtsiv bec08cf5f2 adjust examples in String.codepoints/1 (#14469) 2025-05-06 11:22:11 +02:00
Jean Klingler 5c928c9153 Fix Regex.split/2 edge case with empty chunks (#14468) 2025-05-04 22:26:32 +09:00
José Valim 7c6e95f89a Simply touch if file does not yet exist 2025-05-04 11:51:50 +02:00
José Valim 2b15408a66 Ensure compile.lock timestamp is bumped, closes #14464 2025-05-04 11:28:17 +02:00
José Valim 41c093106f Increase timeout on compiler intensive tests 2025-05-04 10:59:32 +02:00
Dmitry Kleymenov c8223c52fd Add two missed assertions to application tests (#14460) 2025-05-01 15:51:17 +02:00
bolandross 06a42bd87d Fix typo in doc of string.ex (#14459) 2025-05-01 13:49:42 +02:00
João Paulo Abreu 2a30332f25 Docs: fix inverted markdown link format in Mix.Tasks.Xref (#14458) 2025-04-30 21:23:23 +09:00
dependabot[bot] ef11da961d Bump actions/download-artifact from 4.2.1 to 4.3.0 (#14457)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.2.1 to 4.3.0.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/95815c38cf2ff2164869cbab79da8d1f422bc89e...d3f86a106a0bac45b974a628896c90dbdf5c8093)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: 4.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-29 22:33:35 +02:00
José Valim 060f262d17 Add more context around @after_compile 2025-04-28 20:27:01 +02:00
Dmitry Kleymenov dfb2f1ce95 Improve docs for Code module (#14456) 2025-04-28 19:51:49 +02:00
José Valim b9129130f5 Allow verification to be disabled with MIX_DEBUG=1 2025-04-28 18:30:29 +02:00
José Valim 9913a77e7f Do not assume uniqueness on the checker 2025-04-28 18:30:13 +02:00
José Valim 77ab395d05 Clarify calendar microseconds example 2025-04-28 16:45:17 +02:00
Andrea Leopardi 72ff75cfb5 Use t:Calendar.microsecond/0 in Duration docs (#14455) 2025-04-28 16:13:05 +02:00
José Valim 041d59b4f8 Add each_long_verification_threshold, closes #14336 2025-04-28 13:56:06 +02:00
José Valim f7f82b2c21 Support debugging the PID in slow compilation 2025-04-28 13:18:27 +02:00
José Valim 64a387da82 Load modules lazily and only if needed (#14453) 2025-04-28 12:45:25 +02:00
Jean Klingler 5e050f22e0 Rework if/2 docs and mention variable scope (#14452) 2025-04-26 16:32:23 +09:00
José Valim 7e33cee28f Avoid purging non-loaded modules 2025-04-26 09:01:07 +02:00
José Valim f1ad48181e Trace on definition callbacks, closes #14427 2025-04-26 08:42:56 +02:00
José Valim c20222ba0b Trace on load functions, see #14427 2025-04-26 08:42:56 +02:00
Dmitry Kleymenov 9c5612150a Add doctests to Kernel module (#14450) 2025-04-26 08:54:55 +09:00
Steffen Deusch 0fb90f1c1d Add String.count/2 (#14448) 2025-04-25 15:38:29 +02:00
José Valim 87dd05e5fd Preserve files with no line filter on mix test
Before this patch, if you invoked:

    mix test test/foo_test.exs:13 test/bar_test.exs

We would include a location filter only for foo_test.exs
and no tests in bar_test.exs would run. We now ensure
that the full location of files with line filters are
included as tags, as long as any file is included.

Closes #14445.
2025-04-25 15:33:43 +02:00
Dmitry Kleymenov 075b8cae4a Rewrite SpecialForms doc examples as doctests (#14435) 2025-04-25 22:22:31 +09:00
José Valim 641ac05b5c Ensure git checkout works with untracked files (#14428)
Closes #14425.
2025-04-25 14:45:55 +02:00
Markus Krogemann e967d58273 Fix typo (#14447) 2025-04-23 08:36:48 +02:00
Rudolf Manusadzhian 7b8bcb98be Add missing parameters field to ExUnit.Test struct type (#14446) 2025-04-22 14:18:23 +02:00
José Valim 920a6a0a42 Consistenly apply timeouts across suites (#14444)
Closes #14443.
2025-04-18 12:11:12 +02:00
Rudolf Manusadzhian 7e6e7008f9 Add group to the test context (#14440) 2025-04-17 09:39:44 +02:00
Yves Orton 82ccb94953 File.LinkError - swap order of new and existing in error message (#14441) 2025-04-17 09:39:28 +02:00
Eksperimental ea2ae22cc0 Sort entries alphabetically in .gitignore generated by mix new task (#14439) 2025-04-16 08:06:49 +02:00
Eksperimental b1cbe7adc9 Send warnings to stderr in Erlang compiler (#14437)
Aligns compiler to these changes: https://github.com/erlang/otp/pull/9363

Related PR: https://github.com/elixir-lang/elixir/pull/14018
2025-04-15 08:23:30 +02:00
Jean Klingler 97ee6b05b7 Optimize valid32? and hex_valid32? (#14436) 2025-04-13 16:58:02 +09:00
Jean Klingler 7c9e350f89 Optimize valid64? and url_valid64? (#14434) 2025-04-13 15:59:29 +09:00
Jean Klingler d4f3445477 Inline function in Base.valid16? implementation (#14433) 2025-04-13 15:59:15 +09:00
Dmitry Kleymenov 769737ed67 Change first doctest for case/2 (#14432) 2025-04-13 08:43:13 +09:00
Jean Klingler fecd6e3650 Optimize Base.valid16?/2 (#14429) 2025-04-12 15:51:28 +09:00
Jean Klingler 30711a7243 Optimize Base.decode* (#14430) 2025-04-12 15:49:01 +09:00
Dmitry Kleymenov dfb7dbef4c Make doctests from code texts for case/2 (#14423) 2025-04-12 06:12:50 +02:00
Andrea Leopardi a47dc197f2 Improve docs for t:Calendar.microsecond/0 (#14426) 2025-04-11 20:00:26 +02:00
Andrea Leopardi fdbc664334 Add Base.valid{n}?/2 functions (#14417) 2025-04-11 08:59:46 +02:00
Steffen Deusch ec4cc25da7 IEx.Helpers.process_info/1 (#14418) 2025-04-10 19:49:05 +02:00
José Valim 7ca67e93b3 Clarify unquote(kv)() docs, closes #14422 2025-04-10 19:48:49 +02:00
Dmitry Kleymenov c00e6fc838 Add clarification for stacktrace doc (#14421) 2025-04-10 14:51:42 +02:00
Santiago Tortosa db6c536c30 Doc ref to inspect/2 for :translator_inspect_opts (#14420) 2025-04-10 11:15:40 +02:00
José Valim 32f7646eb4 Add block_keyword_or_binary_operator to Code.Fragment (#14414) 2025-04-08 12:06:24 +02:00
Panagiotis Nezis 9449df83c3 Improve coverage for Keyword.delete_first/2 (#14412) 2025-04-08 11:49:03 +02:00
Andrew Timberlake f5e28b626b Update info message to respect release quiet option (#14413) 2025-04-08 11:10:21 +02:00
José Valim 247fd711cf Emit trace events for super calls, closes #14395 2025-04-08 09:45:11 +02:00
José Valim bb8761a3c7 Properly track imported function calls in tracer
Closes #13878.
2025-04-08 09:25:20 +02:00
José Valim 78eb659e08 Fix typo 2025-04-08 08:40:33 +02:00
Michał Łępicki 12a3552ec4 Fix Inspect.Algebra.next_break_fits/1 spec (#14410) 2025-04-08 08:22:46 +02:00
José Valim 59a53861fa Skip CLI and bootstrap modules from coverage (#14408) 2025-04-08 08:22:26 +02:00
dependabot[bot] 396382cef7 Bump actions/upload-artifact from 4.6.1 to 4.6.2 (#14411)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.1 to 4.6.2.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.6.1...ea165f8d65b6e75b540449e92b4886f43607fa02)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 4.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-08 08:22:05 +02:00
José Valim d581f7ae26 Improve coverage for several protocol implementations (#14409) 2025-04-07 23:27:50 +02:00
José Valim 51fbf997cb Replace IO.ANSI.Sequence by anonymous function 2025-04-07 22:59:43 +02:00
José Valim 10cc7e995e Ensure more deprecated functionality is removed from coverage 2025-04-07 22:54:20 +02:00
Jonatan Männchen f3e8ceb5aa Implement Test Coverage Reporting (#14343) 2025-04-07 22:37:01 +02:00
Alex Naser 32571b45fa Return --exit-status + 1 when warnings as errors and test failures (#14405) 2025-04-07 21:15:47 +02:00
Jonatan Männchen c0ba8a1ee9 Use Test Modules in tests instead of Elixir built-in modules (#14383) 2025-04-07 16:11:37 +02:00
Zach Allaun af179406f5 Update README sections on community interaction and feature proposals (#14407) 2025-04-07 16:10:11 +02:00
José Valim 468228a236 Add :indentation to Code.string_to_quoted/2 (#14406) 2025-04-07 16:02:01 +02:00
José Valim f628d9563b Add optimistic/pessimistic groups to Inspect.Algebra (#14403)
This is a simplified implementation of next_break_fits
with fewer corner cases.
2025-04-07 15:07:38 +02:00
Dmitry Kleymenov 93a4a0f697 Add doctest for unquote_splicing in block context (#14404) 2025-04-07 09:09:15 +02:00
José Valim 207f5aaa16 mix format 2025-04-06 23:17:00 +02:00
José Valim 148135988c Fix flex break at group end 2025-04-06 23:14:30 +02:00
José Valim 10ea380727 Clarify infer signatures as of now 2025-04-06 10:40:10 +02:00
José Valim d403bb9187 Rely on OS certificates for downloading Hex/Rebar manifests 2025-04-06 10:35:57 +02:00
José Valim 1cbc4934fb Improvements to README 2025-04-05 17:44:02 +02:00
Yves Orton ff684076bd Add Regex.to_embed/2 (#14379)
to_embed(regex,strict) returns an embeddable representation of regex.
For instance ~r/foo/i can be represented as ~r/(?i-msx:foo)/.

If the option :strict is true (the default) then it will throw an
ArgumentError if the regex was compiled with an option/modifier which
cannot be represented as an embeddable pattern.  If :strict is false
then any unembeddable options will be silently ignored.  This may be
perfectly reasonable, for intance the wrapped pattern may be compiled
with the same modifiers as the pattern, or reusing the pattern without
the unembeddable modifiers may not change its semantics.
2025-04-05 15:25:27 +02:00
Nathan LongandJosé Valim 1c05790a39 Add quick Registry example to GenServer docs (#14368)
Co-authored-by: José Valim <jose.valim@gmail.com>
2025-04-05 11:10:51 +02:00
Andrea Leopardi 3e22d208f1 Add a couple of types to PartitionSupervisor (#14396) 2025-04-05 10:59:57 +02:00
Jean Klingler be17d71e45 Allow optional: :all when deriving Inspect (#14400)
* Allow optional: :all when deriving Inspect

* Improve error message when passing a non-list
2025-04-05 16:58:13 +09:00
Stefan Schmiedl a93ce0886a Fix typos in IO documentation (#14398)
Closes #14397.
2025-04-05 09:21:30 +02:00
Jean Klingler 068015e0f8 Add more docs about :optional in Inspect (#14399) 2025-04-05 16:18:40 +09:00
José Valim 25f063e085 Do not crash on nested bitstrings 2025-04-03 11:50:12 +02:00
Dmitry Kleymenov 2e3b8127f8 Fix doc for text-base match operator (#14388) 2025-04-02 10:25:06 +02:00
Dmitry Kleymenov c215c42c45 Fix deprecation warnings for regex in module attributes (#14387) 2025-04-01 21:03:37 +02:00
José Valim aca578bb88 Add a note about native code in mix releases, closes #14386 2025-04-01 18:49:54 +02:00
José Valim a87eb7dec4 Avoid collectable deprecation warning 2025-04-01 18:21:58 +02:00
Alex Martsinovich 884de18a7a Preserve logger reports through translation by encapculating translated output in report_cb (#14380) 2025-04-01 18:16:04 +02:00
Jonatan Männchen 2a2765c2e0 Update test coverage summary to produce a markdown-ish table (#14384)
This allows the table to embedded into places that support Markdown rendering.
2025-04-01 15:14:47 +02:00
José Valim 624ae0c9a2 Recompile regexes when escaped from module attributes (#14381) 2025-03-30 16:13:02 +02:00
Jonatan Männchen 33b6e6ddc8 Add OpenSSF Best Practices Badge (#14342) 2025-03-28 11:50:45 +01:00
José Valim 5a6735e9d9 Consider distance to previous comment in comment metadata, closes #14375 2025-03-28 11:18:59 +01:00
Andrea LeopardiandJosé Valim 441cf7d2f1 Add warning to the docs for start_link_supervised!/2 (#14374)
Co-authored-by: José Valim <jose.valim@dashbit.co>
2025-03-28 10:07:21 +01:00
José Valim a555900d87 Update mermaid version 2025-03-28 07:44:35 +01:00
Jean Klingler eda7b5e250 Fix usage of mermaid in ExUnit &co (#14376) 2025-03-28 07:41:58 +01:00
Jean Klingler 699bb9112e Add doc since for Stream.transform/5 (#14371) 2025-03-28 06:59:03 +09:00
Andrea Leopardi 7b71138da2 Add Mermaid graph about ExUnit processes (#14372) 2025-03-27 21:37:28 +01:00
Andrea Leopardi f6f7d30ed6 Mention child order in docs for Supervisor.child_spec/2 (#14373) 2025-03-27 21:21:17 +01:00
José Valim ad1a6b146a Improve named capture docs and examples 2025-03-27 16:23:41 +01:00
José Valim 9456e9355c Update Regex docs to Erlang/OTP 28 2025-03-27 15:31:06 +01:00
José Valim e7b235f0ed Do not run listeners when not checking the deps 2025-03-26 17:04:55 +01:00
Neil Lyons c5c87a661e Modify Time.utc_now/1 to allow truncation and add Time.utc_now/2 (#14367) 2025-03-25 18:03:53 +01:00
José Valim adc9a28b24 Warn on structs even if both sides have numbers 2025-03-25 14:20:32 +01:00
José Valim 53b7e55dd7 Print a project is recompiled when elixir, otp, or scm changed and MIX_DEBUG is set, closes #14325 2025-03-25 13:42:30 +01:00
José Valim bfb6cdba19 Emit struct comparison warning only if the intersection has structs
This avoids the false negatives while still emitting
the warning when trying to compare two datetimes or similar.
2025-03-25 12:47:53 +01:00
José Valim b27b911318 Remove struct comparison specific warning
Now that we have added inference, there are too many false
positives. However, note we still have a disjoint check warning,
which will still warn about several cases of structs.

Closes #13227.
Closes #14359.
2025-03-25 12:19:12 +01:00
José Valim f0fc792ca9 Remove Protocol.impl_for
If one wants to hide the dispatch, they may use `apply/3` instead.
2025-03-25 11:12:14 +01:00
Jean Klingler a6a38e130f Handle non-binary bitstring in struct default values (#14363) 2025-03-25 18:07:07 +09:00
dependabot[bot] 5c63c8d94d Bump actions/download-artifact from 4.1.9 to 4.2.1 (#14361)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.1.9 to 4.2.1.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/cc203385981b70ca67e1cc392babf9cc229d5806...95815c38cf2ff2164869cbab79da8d1f422bc89e)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-25 10:06:25 +01:00
dependabot[bot] 95b63d74dc Bump actions/upload-artifact from 4.6.1 to 4.6.2 (#14360)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.1 to 4.6.2.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1...ea165f8d65b6e75b540449e92b4886f43607fa02)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-25 09:57:06 +01:00
Marcel Otto b096b50728 Fix URI.merge/2 handling of authorityless base (#14358)
* Fix URI.merge/2 handling of base without authority

* Organize URI.merge/2 tests in describe block
2025-03-24 22:42:43 +01:00
José Valim 05855afe2f Pass environment and target forward in mix deps.partition 2025-03-24 22:09:09 +01:00
ruslandoga cf34fccd4a Retry mix httpc with disabled middlebox_comp_mode (#14357)
Closes #14356
2025-03-24 22:07:02 +01:00
José Valim cba3d42791 Update CHANGELOG 2025-03-24 22:06:36 +01:00
José Valim 3dc3f4e3f1 Add MIX_OS_DEPS_COMPILE_PARTITION_COUNT for concurrent deps compilation (#14340) 2025-03-24 15:32:49 +01:00
David Pavlík e0c016ce1b Add Access.values/0 (#14350) 2025-03-24 13:51:46 +01:00
Jonatan Männchen 5b0ed0dd73 Update Guide EEF Link (#14355) 2025-03-23 20:01:44 +01:00
Alex Martsinovich edd2944bea Drop otp log events if handle_otp_reports is set to false (#14354) 2025-03-22 21:46:20 +01:00
guilherme 0fed3000c1 chore: improve Config.config_env/0 docs (#14353) 2025-03-22 17:15:12 +01:00
David Pavlík 36d6c1cf66 Fix mention of README in Makefile (#14351)
The message probably refers to the "Building documentation" section that
has been moved to CONTRIBUTING.md in a2af062.
2025-03-20 13:34:55 +01:00
Steffen Deusch b08eea70e5 Prevent exits from leaking in ExUnit.CaptureServer (#14348)
When a process that traps exits used ExUnit's log capture functionality,
it would receive stray exits from the Tasks started inside it that could
lead to issues if not handled.
2025-03-19 16:51:28 +01:00
Marcel Otto b601b1c2e3 Fix URI.merge/2 trailing slash handling at root path (#14346) 2025-03-19 11:49:21 +01:00
Michał Łępicki ba48980edb Fix Mix.Utils.write_json_tree!/4 spec (#14345) 2025-03-18 23:00:22 +01:00
Marcel Otto 3baf987266 Fix URI.merge/2 for hostless absolute base URIs (#14344) 2025-03-18 20:58:14 +01:00
Jonatan Männchen d83f961ecd Add test policy to CONTRIBUTING.md (#14337)
Fulfills the test_policy and tests_documented_added
OpenSSF Best Practices Badge requirements.
2025-03-18 16:53:21 +01:00
Marcel Otto b9f012fc77 Fix URI.merge/2 handling of base with trailing dots (#14341) 2025-03-18 14:40:59 +01:00
Jonatan Männchen 822aac84c9 Add CVE CHANGELOG disclosure requirement to RELEASE.md (#14338) 2025-03-18 14:29:10 +01:00
Jonatan MännchenandJosé Valim f55498e7e5 Enable warnings_as_errors for Erlang only on latest (#14339)
Co-authored-by: José Valim <jose.valim@dashbit.co>
2025-03-18 14:27:59 +01:00
José Valim 8e49286b05 Fix type on --format option in mix xref 2025-03-17 16:14:10 +01:00
José Valim e8df031514 Ensure defimpl add export definition on protocols 2025-03-17 16:11:51 +01:00
José Valim cb8b73598b Update Erlang/OTP on Windows CI (#14334) 2025-03-17 15:34:50 +01:00
Jonatan Männchen 37faa6761f Limit GH Actions Token Permissions (#14333) 2025-03-17 14:25:44 +01:00
Jonatan Männchen 6cd8082494 Pin GitHub Actions (#14332) 2025-03-17 14:25:22 +01:00
José Valim 0f3a8f09d2 Remove concurrency configuration on mix tests as it did not affect Windows CI 2025-03-17 14:25:10 +01:00
Jonatan Männchen 4db62c8d59 Update ORT Action & Executable (#14331) 2025-03-17 14:23:11 +01:00
José Valim 32529195f8 Revert to previous printing message for compatibility 2025-03-17 12:25:11 +01:00
Yves OrtonandYves Orton dd672b168d mix xref graph - rework cycles output description (#14330)
Include a link to the wikipedia article on strongly connected
components, and use the term in the output for clarity.

Co-authored-by: Yves Orton <yves.orton@remote.com>
2025-03-17 11:15:54 +01:00
Yves Orton 37b6f0bfdf mix xref graph - add json support and related changes (#14327)
This patch adds support for output the xref graph as a two level Map of
Maps which can be trivially loaded by anything that can process JSON
data. The top level Map contains keys which specify source files whose
values are maps contain sink files as keys, and dependency type data as
values. Source files with no dependencies have empty maps as values. For
example a if "lib/foo.ex" has a compile time dependency on "lib/bar.ex"
which had no dependencies at all, then the output would look like this:

   {
     "lib/foo.ex": { "lib/bar.ex": "compile" },
     "lib/bar.ex": {},
   }

At the same time it adds support for renaming existing xref_graph.dot
and xref_graph.json files to have a .bak extension instead of
overwriting them.

This patch also includes logic to fix some misleading verbiage output by
the 'dot' format when the output file is not in the current working
directory. This change is included with the JSON changes because I
refactored the logic for writing to a file so that both the 'json' and
'dot' formats would use the same code.

Closes #14324.
2025-03-17 10:04:17 +01:00
Jean Klingler 5998c0e549 Fix handling of ErlangError when :general key is chardata (#14329) 2025-03-15 19:18:18 +09:00
José Valim 3e89296916 Preserve backwards compatibility in elixir_erl, closes #14323 2025-03-14 20:13:07 +01:00
José Valim 98080d9c69 Filter neighbours when looking for cycle labels 2025-03-14 18:55:13 +01:00
José Valim 228c33294f Print strong connected cycles in mix xref
Prior to this version, Elixir was breaking strong connected cycles
in smaller cycles, but without considering all possible subcycles,
which meant cycles with compile-time deps in them could be missed.

This commit ensures we only print the strong connected cycle,
highlighting vertices with compile deps in them.
2025-03-14 13:53:13 +01:00
Yves OrtonandYves Orton d25bea52fb Improve warning message about defaults to mention private functions (#14326)
The original version of this warning did not mention that the warning
applies to private functions only, this patch fixes that.

Co-authored-by: Yves Orton <yves.orton@remote.com>
2025-03-13 09:51:05 +01:00
José Valim d661840e5e Improve docs on custom handlers 2025-03-11 20:09:34 +01:00
Jason Axelson 3f2d3bf3e7 Document that multi-line strings are heredocs (#14319) 2025-03-09 11:51:00 +01:00
Art Kay 6d9b4042ac Fix two more typos in comments (#14316) 2025-03-07 12:36:16 +09:00
José Valim 1468352e35 Do not discard nil on protocol concat, closes #14311 (#14314) 2025-03-06 10:39:22 +01:00
José Valim 8d5460062b Fix type in Regex
Closes #14310.
2025-03-05 13:27:15 +01:00
José Valim 65ae6232c4 Encode any JSON key to string, closes #14305 (#14309) 2025-03-05 09:47:33 +01:00
Łukasz Samson 88605cfbc5 Fix autocomplete crash when expanding struct with __MODULE__ (#14308) 2025-03-05 09:44:11 +01:00
José Valim 71da1a29aa Do not raise when Stream.cycle is explicitly halted, closes #14307 2025-03-03 12:54:49 +01:00
José Valim ba43db786a Clarify order on uniq/uniq_by, closes #14304 2025-03-01 09:53:15 +01:00
José Valim c2c331d009 Emit a warning if source is part of a cycle 2025-02-28 10:51:43 +01:00
Jean Klingler b42dbd9a4e Mention the type system in quote :generated doc (#14301) 2025-02-27 03:13:17 +09:00
Adam Millerchip 27f0b18738 Add more docs about :already_started for DynamicSupervisor (#14298) 2025-02-25 07:39:49 +01:00
Jonatan Männchen 491e4df132 Split README from CONTRIBUTING (#14297) 2025-02-24 18:28:52 +01:00
Jonatan Männchen a58382fe5b Update ORT Action (#14296) 2025-02-24 15:41:54 +01:00
Jonatan Männchen 66cbf2fac0 Implement Open Source Policy (#14266) 2025-02-24 15:41:34 +01:00
Jason Florentino 901e066083 Fix typo in anonymous-functions.md (#14295) 2025-02-24 09:13:05 +01:00
Jean Klingler b3cc618199 Fix regression when diffing nested improper lists (#14292)
Close https://github.com/elixir-lang/elixir/issues/14291
2025-02-21 23:08:36 +09:00
José Valim 41a0f0c732 Support --no-listeners in loadpaths 2025-02-21 11:01:45 +01:00
Art Kay 91f12dc016 Fix a typo in types.ex (#14290) 2025-02-20 22:27:56 +01:00
José Valim c1e79d189c Support Erlang/OTP 28 2025-02-20 18:10:23 +01:00
José Valim d800e9c725 Parallelize log dispatch on CaptureLog, closes #14288 2025-02-20 13:10:29 +01:00
Jean Klingler 2cd752c469 Add @doc since for get_in/1 (#14287) 2025-02-20 14:14:14 +09:00
José Valim 7d9c531519 Ensure that keyword: (... -> ...) can be encoded back, closes #14267 2025-02-20 06:10:35 +01:00
Art Kay 73893ee5c2 Fix typo in action.yml (#14286)
desctiption -> description
2025-02-20 05:56:53 +01:00
James Baldwin 56d03dabff Fix grammar in callbacks docs (#14285) 2025-02-19 20:50:06 +01:00
Wojtek Mach cd6fcc41a6 Add instructions for updating AZURE_CLIENT_SECRET (#14284) 2025-02-19 11:26:22 +01:00
José Valim b65570e194 Remove --keep-going and reduce concurrency for CI on Windows (#14283) 2025-02-19 11:09:39 +01:00
Daniel Kukula c65f9555aa Fix ExUnit list diff bug (#14275) 2025-02-19 08:32:15 +01:00
Art Kay 5505de89c2 Fix a typo (#14281)
This PR fixes a minor typo in the docs.
2025-02-18 22:54:54 +01:00
Daniel Kukula f1bf1b8602 Use newlines and pretty printing in several builtin exceptions (#14269) 2025-02-18 11:39:58 +01:00
José Valim 09a16215a4 Address code bootstrap 2025-02-18 10:47:52 +01:00
José Valim 20b4e647b8 Print header on first dbg instruction, closes #14279 2025-02-18 10:44:39 +01:00
José Valim a7c008f2cd Simplify transform as suspend is guaranteed to return one entry 2025-02-18 10:21:54 +01:00
José Valim ff90cd157c Ensure inner halt in Stream.transform does not override user intention, closes #14278 2025-02-18 10:21:54 +01:00
Theodor Fiedler 8f6cb5d33d Do not truncate microseconds in DateTime.diff/3 (#14274)
Closes #14273
2025-02-17 23:14:14 +01:00
Adam Roses Wight b393dcc187 Minor doc update: "orphan" processes (#14271)
This patch makes two small changes to the documentation:

First, "zombie" processes are in a terminated state and will appear in
"ps" output in "Z" state.  The problem described in our documentation
seems to actually be about the "orphan" condition, in which a process
continues running after its parent terminates.  Most unix systems will
reparent the process with PID 1 but this can vary, but there is no
convention that the process should stop unless it has been wired using
eg. "prctl" to detect a terminated parent.  The distinction between
zombie and orphan is important because it means the child is still
active.

Second, our suggestion that the child process should detect stdin
closure is a good workaround but is not generally true for
applications that aren't doing their work on stdio.  I've tried to
clean up the text around the example shell script to clarify that
developers are free to use this technique to detect parent
termination, but it's not the rule for polite unix applications.
Hijacking stdin also comes with some drawbacks as is already
mentioned.

I'm not suggesting a different workaround at the moment—there is a
linux-specific technique of using "prctl" which is demonstrated in
https://groups.google.com/g/elixir-lang-core/c/yiepKrcEniU ,
however this isn't portable.

See #7495 and #9171
2025-02-17 09:41:54 +01:00
Taine Fan a6368b7c3b Update doc for String.chunk/2 (#14265) 2025-02-14 09:38:13 +01:00
Jonatan Männchen 4b50edce3e Expose Packages in SBoM (#14264) 2025-02-14 09:20:16 +01:00
José Valim 0bc8a60c9b Turn off refinements on dynamic calls 2025-02-12 11:57:46 +01:00
José Valim 71696e939b Do not change value of infer signatures during tests 2025-02-12 11:25:20 +01:00
José Valim 34997a2783 Do not purge on recompile if IEx is not running, closes #14260 2025-02-12 10:03:01 +01:00
Jonatan Männchen e45a1f2593 Improve SBoM Details (#14258) 2025-02-09 17:35:07 +01:00
Jonatan Männchen 980500551c Inline Licence Info & Copyright for non-test files (#14256) 2025-02-07 21:11:09 +01:00
Jonatan Männchen f7bbe81ea7 Inline licence info & copyright for test files (#14257) 2025-02-07 20:54:14 +01:00
José Valim 2251b1e64a Collapse structs in type warnings by default 2025-02-07 16:38:16 +01:00
José Valim c58755d831 Remove unused variable 2025-02-07 10:44:24 +01:00
José Valim 331170edaf Default to IO.write in Mix.Shell.cmd/2
Erlang defaults to UTF-8 on Windows since Erlang/OTP 26+,
so we no longer need to check conditionally.

Closes #14253.
2025-02-06 12:06:36 +01:00
José Valim a0214e93fc Only make module available after inference to avoid races (#14254) 2025-02-05 13:57:12 +01:00
Jonatan Männchen e1d171e5c7 Add inline License / Copyright to Erlang files (#14255) 2025-02-05 13:50:00 +01:00
Jonatan Männchen d12286c490 Setup OSS Review Toolkit SBoM generation (#14241) 2025-02-05 13:09:56 +01:00
Rudolf Manusadzhian 28b0ef277d Fix typo in callbacks docs (#14252) 2025-02-05 08:45:48 +01:00
José Valim 8a57552bb5 Mark protocol code as generated 2025-02-04 22:38:33 +01:00
José Valim fb7b34550e Remove deprecation warning from Dialyzer suite 2025-02-04 22:38:24 +01:00
José Valim 219bcc90ef Rely on the existing module passed in 2025-02-04 21:47:41 +01:00
José Valim 5f50a1f907 Ensure consolidated version of protocols is loaded 2025-02-04 21:25:30 +01:00
Łukasz Samson 8d645aa291 Fix inconsistent variable naming in record typespec macro (#14251) 2025-02-04 20:34:02 +01:00
José Valim 29e3b78da8 Allow controlling which applications are used during inference 2025-02-03 18:13:18 +01:00
José Valim a5c28126b8 Remove warnings from IEx.Introspection 2025-02-03 17:32:27 +01:00
José Valim e019dfdf22 Have specific type violation messages per construct instead of generic ones 2025-02-03 14:24:23 +01:00
Renan b205181bee Add mix help Mod, mix help :mod, mix help Mod.fun and mix help Mod.fun/arity (#14246) 2025-02-02 16:53:15 +01:00
Kip Cole b118698777 Support 2-arity options for Calendar.strftime/3 (#14247) 2025-02-01 11:30:42 +01:00
José Valim 80aad3585c Ensure logger suite passes when IO.ANSI colors is disabled 2025-02-01 10:53:44 +01:00
José Valim cff55fbc88 Compute ANSI escaping lazily in Logger, closes #14248 2025-02-01 10:33:47 +01:00
Haelwenn Monnier 07afa71d3b Properly handle permissions errors cascading from parent in File.mkdir (#14242) 2025-01-30 13:33:03 +01:00
José Valim 15a941e365 Fix heisentest 2025-01-30 13:31:58 +01:00
José Valim a56886969a Allow <<_::3*8>> in typespecs 2025-01-30 10:23:39 +01:00
Ben Murden 965ee9b894 Improved help on deprecation warning (#14239) 2025-01-29 08:59:17 +01:00
cevado 9c07243d8a Add section to Process.exit/2 with differences to Kernel.exit/1 (#14238) 2025-01-29 08:56:49 +01:00
José Valim bea1c6c58e Remove :erts from the list of applications, closes #14236 2025-01-28 16:43:34 +01:00
Jean Klingler 4087560237 Support ellipsis in doctest exceptions (#14233) 2025-01-28 18:48:47 +09:00
José Valim 1de463f1f2 Remove remaining references to build_embedded 2025-01-27 20:06:15 +01:00
Zach Allaun 944ac0ff48 Use the correct incorrect typespec definition in exception.ex (#14232) 2025-01-27 16:54:01 +01:00
UWABUNKEONYE BENEDICTA 1eb3a20dcb Improve docs and add examples to exception.ex (#14227) 2025-01-27 15:03:37 +01:00
Jean Klingler f94f4d5fb9 Mechanical refactor of Descr, avoid intermediate lists (#14230) 2025-01-27 19:52:34 +09:00
José Valim d9ffe76aea Add support for MIX_PROFILE_FLAGS for more configuration around task profiling 2025-01-27 11:44:57 +01:00
Jean Klingler 16a1472efe Refactor Descr.map_all_but_one (#14231) 2025-01-27 19:06:25 +09:00
Jean Klingler 615751da8a Implement optimization of tuple unions (#14228) 2025-01-27 07:24:50 +09:00
tea☆ a43741fbb0 Fix --no-color not setting :ansi_enabled to false (#14229) 2025-01-26 14:10:03 +01:00
José Valim bdd569ec71 Use guards as helpers instead of hardcoding constants 2025-01-25 17:03:13 +01:00
Jean Klingler adcb7c6c09 Fix String.split/3 example in guide and add note in docs (#14223) 2025-01-25 07:11:26 +09:00
eeff 5354a1fc8d Add :inspect_opts option for doctest (#14225)
Closes #14219
2025-01-24 21:29:21 +01:00
José Valim 4c1e9dac18 Use exdoc:loaded to reload mermaid graphs 2025-01-24 19:58:37 +01:00
Jean Klingler 85d2e161ff Optimize map unions to avoid building long lists (#14215) 2025-01-24 17:12:13 +09:00
José Valim 2ab8a543e5 Improve docs around list handling 2025-01-23 19:57:45 +01:00
Mike Stok fc348d7bd5 Fix typo in code anti-patterns doc (#14221)
The paragraph preceding the change mentions two reasons (atoms aren't
garbage collected, and the Erlang VM limits the number of atoms).
2025-01-23 19:29:11 +01:00
José Valim 5610775078 Improve pretty printing of types and fix list tail 2025-01-23 16:30:03 +01:00
José Valim f21c13cfc6 More docs on types 2025-01-23 13:14:32 +01:00
Jean Klingler d777074904 Print intermediate results of dbg for blocks (#14218) 2025-01-23 19:32:11 +09:00
Jean Klingler fb7a22eda8 Print intermediate results of dbg for boolean operators (#14217) 2025-01-23 17:25:41 +09:00
José Valim 8a5ed11ad7 Improve docs around inference 2025-01-22 19:59:27 +01:00
José Valim f557991310 Make --color/--no-color consistent with mix test 2025-01-22 17:38:51 +01:00
José Valim 9628bf83d9 Show non-expanded struct assocs in struct update deprecation warning 2025-01-22 17:22:01 +01:00
José Valim 40164fc291 Print intermediate results of dbg on if and case
Closes #14075.
Closes #14005.
2025-01-22 10:08:56 +01:00
Jean Klingler acdd846768 Update argument name for documentation (#14207) 2025-01-22 07:12:43 +09:00
José Valim 8896cf1030 Update deps.loadpaths docs 2025-01-21 19:08:25 +01:00
José Valim e3599fc935 Do not precompile regexes on Erlang/OTP 28 2025-01-21 18:53:51 +01:00
José Valim b0779893c0 Use Collectable instead of Inspect to avoid accidental protocol loading 2025-01-21 10:54:59 +01:00
José Valim ba945e918c Provide more AST around cursor fragments, closes #14118 2025-01-21 10:36:09 +01:00
José Valim cb0f8d9791 Fetch all tags before generating docs (#14206) 2025-01-21 08:29:42 +01:00
dependabot[bot] 0df1ff54b1 Bump DavidAnson/markdownlint-cli2-action from 19.0.0 to 19.1.0 (#14204)
Bumps [DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action) from 19.0.0 to 19.1.0.
- [Release notes](https://github.com/davidanson/markdownlint-cli2-action/releases)
- [Commits](https://github.com/davidanson/markdownlint-cli2-action/compare/v19.0.0...v19.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-21 08:20:19 +01:00
José Valim 029699735d Remove unnecessary clause in descr 2025-01-21 08:07:33 +01:00
Jean Klingler a42f2e8ba3 Remove changelog entry after reverting #14172 (#14202) 2025-01-21 06:59:16 +09:00
José Valim b339deb204 Simplify token pruning, closes #14139 2025-01-20 18:51:29 +01:00
José Valim a1c1888f9a Fix checking of closed maps
If a map has unknown keys, because we cannot specify
term() => term() with the necessary precision today,
we need to mark it as dynamic.

We also improve the pretty printing of differences
between composites types.
2025-01-20 13:01:43 +01:00
José Valim b15de1cad6 Revert "Support Duration in Date.range/3 (#14172)"
There is some ambiguity if the duration represents the end
date or the step, so it is best to be explicit about it.

This reverts commit 8deaaf4117.
2025-01-20 11:41:23 +01:00
Marcus Kruse 661319329e Show hints for mix test --no-color (#14198) 2025-01-18 16:44:52 +01:00
José Valim 7e8de3c04e Update CHANGELOG 2025-01-18 10:51:38 +01:00
José Valim c22c907810 Add Protocol.impl_for/1 and Protocol.impl_for!/1 2025-01-18 10:32:40 +01:00
José Valim 83a70d799c Deprecate struct update syntax
The struct update syntax was added early in Elixir to help validate
at compile-time that the update keys were valid. However, for a couple
releases already, Elixir's static analysis can perform such validation
more reliably and find more error if you pattern match on the struct
when the variable is defined instead.

This deprecation simplifies the language and pushes developers to
better alternatives.

Closes #13974.
2025-01-18 10:08:31 +01:00
José Valim 67b6f89152 Do not add duplicates on maps and tuples intersections 2025-01-17 15:50:07 +01:00
José Valim 1637dc7184 Include latest and searchNodes metadata into hosted docs 2025-01-17 14:26:34 +01:00
Jean Klingler 618d03baa8 Prevent crash on zero arity def in protocol (#14191) 2025-01-17 20:47:46 +09:00
Jean Klingler 6e3de5f17d Fix warning in tests and some doctest whitespace (#14194)
* Fix whitespace in iex> docstrings

* Fix type warning in test

* Fix negative step warning in test compilation

* Remove raise assertion
2025-01-17 19:49:13 +09:00
José Valim 2716bb62ff Provide more information on out of bound errors to aid debugging 2025-01-17 10:33:36 +01:00
José Valim 6a9620467c Avoid false warnings on private clauses that raise (return none()) 2025-01-16 21:59:36 +01:00
José Valim 6df3c08fa5 Avoid ambiguous up to date working on deps.get 2025-01-16 21:45:10 +01:00
José Valim e4f7ee448c Perform type inference using reverse arrows on all non-branching constructs (#14145) 2025-01-15 21:15:22 +01:00
Jean Klingler c592252874 Mention the new type system in typespecs doc (#14188) 2025-01-15 20:41:33 +09:00
dependabot[bot] ffbe286320 Bump azure/trusted-signing-action from 0.5.0 to 0.5.1 (#14185)
Bumps [azure/trusted-signing-action](https://github.com/azure/trusted-signing-action) from 0.5.0 to 0.5.1.
- [Release notes](https://github.com/azure/trusted-signing-action/releases)
- [Commits](https://github.com/azure/trusted-signing-action/compare/v0.5.0...v0.5.1)

---
updated-dependencies:
- dependency-name: azure/trusted-signing-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-14 08:20:24 +01:00
Theodor Fiedler 8deaaf4117 Support Duration in Date.range/3 (#14172) 2025-01-13 19:39:42 +01:00
José Valim aea54fc202 Remove powershell scripts
It current leaves the shell broken after quitting Erlang.

Issues have been reported upstream, so we may be able
to bring it back in future Erlang/OTP versions.

Closes #14174.
2025-01-13 11:29:21 +01:00
José Valim 2f8311b7a3 Add --color for easy enabling and disabling of coloring (#14183) 2025-01-13 08:54:07 +01:00
José Valim f978b575ae Preserve argument quoting in mix cmd (#14181) 2025-01-13 08:53:50 +01:00
José Valim e2f0196848 Simplify bat and ps1 files (#14180) 2025-01-13 07:33:06 +01:00
José Valim f16fb5aa81 Keep line information in defstruct 2025-01-12 20:09:42 +01:00
Jean Klingler 08d844a206 Add and simplify links to erlang documentation (#14179)
* Link more erlang modules in docs

* Fix epmd link

* Use `:atom` syntax to link erlang modules
2025-01-12 18:58:38 +09:00
Kip Cole c246400989 Update Calendar.day_of_week callback and Date.day_of_week/2 docs (#14176) 2025-01-11 21:50:32 +01:00
Jonatan Kłosko 04a662ab8a Remove duplicate call to mkdir in concurrency lock (#14175) 2025-01-11 10:53:25 +01:00
Christopher Lane 821ac12b3a Fix typo in docs (#14173) 2025-01-11 07:03:46 +01:00
Steffen Deusch 6657e7f8f7 Track mix_pubsub and mix_lock folders per user (#14171) 2025-01-10 20:02:36 +01:00
Daniel Kukula 758975400f Optimize parsing microseconds and offset in calendar (#14169) 2025-01-10 18:40:51 +01:00
Claudio Ortolina 12b20043c3 Improve clarity of Path.safe_relative/2 argument names (#14167)
Switches from `cwd` to `relative_to`, which is more generic and more consistent with possible use cases of the function.
2025-01-09 22:49:21 +01:00
Zach Allaun 56f745a667 Update task-and-gen-tcp.md (#14166) 2025-01-09 18:10:23 +01:00
José Valim 51fb7242ce Clarify the need for better tools in the TCP guide 2025-01-09 16:37:10 +01:00
felipe stival d7fa519037 JSON: document that calendar types are encoded to ISO 8601 (#14165) 2025-01-09 16:18:25 +01:00
Gilbert Bishop-White 72c5fe3648 Document VC++ required for Windows releases (#14164) 2025-01-09 14:42:35 +01:00
Jean Klingler 059071acdc Fix ExUnit crash when diffing bitstring specifiers (#14161) 2025-01-09 18:26:50 +09:00
Jean Klingler 8cd2915530 Prevent infinite loop in compiler for some invalid type specs (#14155) 2025-01-09 18:25:51 +09:00
José Valim 116eb4e74a Migrate the git repo against the origin branch, closes #14163 2025-01-09 10:03:13 +01:00
Daniel Drexler b18aaa0d71 Improve warning for charlists (#14160)
The current warning for single-quoted character lists is confusing
because it appears to be a stylistic note instead of a deprecation
warning. This commit updates the language to make the reasoning clearer
and to more clearly describe what `mix format --migrate` will change
about the users' code.
2025-01-09 09:22:02 +01:00
José Valim 01c82022f2 Do not allow protocols to define structs nor exceptions, closes #14158 2025-01-09 09:18:54 +01:00
Daniel Kukula 194ecfd8da Optimize Float.parse/1 (#14159) 2025-01-09 09:11:48 +01:00
Guillaume Milan ef1450d38b Fix crashing on autocompleting structs with runtime values (#14150) 2025-01-07 11:39:41 +01:00
Roeland van Batenburg 9256143eee Mention alternatives to compare/2 for comparison in other dates and times (#14151) 2025-01-07 07:51:21 +01:00
José Valim 38f460ecda Improve docs for defoverridable and behaviours 2025-01-06 12:01:01 +01:00
Roeland van Batenburg 13baeb53dc Mention alternatives to DateTime.compare/2 for comparison (#14147) 2025-01-06 11:03:57 +01:00
José Valim dcc27c2763 Optimize Rebar compilation by skipping plugins 2025-01-05 10:55:45 +01:00
Simon McConnell 763d0f1846 Add note about microsecond precision in Calendar.strftime (#14137) 2025-01-04 17:54:53 +01:00
Andrea Leopardi cbc4c8ba13 Clarify metadata behavior in Registry :via docs (#14141) 2025-01-03 14:41:48 +01:00
Andrea Leopardi bde9494ac5 Clarify return value of ExUnit.CaptureLog.with_log/2 2025-01-03 10:10:24 +01:00
Coby Benveniste 9878eaf81b Fix minor typo in lib/mix/lib/mix/task.compiler.ex (#14140) 2025-01-02 20:46:32 +01:00
José Valim d713e47dbe Improve error message for invalid default arguments 2025-01-02 18:35:46 +01:00
Jonatan Kłosko 8e796fcbf5 Print compilation lock waiting message to stderr (#14138) 2025-01-02 09:40:50 +01:00
Daniel Kukula a0fb49f892 Use iodata in calendar modules (#14131) 2024-12-31 18:42:20 +01:00
José Valim 5579253aae mix format 2024-12-31 18:42:03 +01:00
Michał Łępicki 1af384e631 Fix Calendar.ISO.naive_datetime_to_iodata spec (#14135) 2024-12-31 18:25:58 +01:00
Tyler A. Young bae5385bd9 Clarify that mix do early-exits on error (#14134) 2024-12-31 18:25:41 +01:00
Daniel Kukula b53ceff073 Use iodata for calendar types serialization (#14133) 2024-12-31 18:24:42 +01:00
José Valim bf1a2071e9 Type checking of apply/2 and apply/3 2024-12-31 14:11:41 +01:00
José Valim c5cbe7cc7e Infer return types for for+:reduce 2024-12-31 09:26:06 +01:00
José Valim 984620a44c Bring kv-observer image back as it is used in guides 2024-12-31 08:53:54 +01:00
Daniel Kukula 9b95af848e Use iodata for Calendar type formatting (#14130) 2024-12-31 08:49:38 +01:00
José Valim dc69977796 Add a test to ensure literals are not assigned to variables in in/2 2024-12-31 08:48:27 +01:00
José Valim cd68d64fdf Do not wrap literals in variable when expanding in/2 2024-12-31 08:45:44 +01:00
dependabot[bot] 78720e50d8 Bump DavidAnson/markdownlint-cli2-action from 18.0.0 to 19.0.0 (#14132)
Bumps [DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action) from 18.0.0 to 19.0.0.
- [Release notes](https://github.com/davidanson/markdownlint-cli2-action/releases)
- [Commits](https://github.com/davidanson/markdownlint-cli2-action/compare/v18.0.0...v19.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>
2024-12-30 23:43:10 +01:00
Jonatan Kłosko f879356600 Add an environment variable to optionally disable compilation locking (#14129) 2024-12-30 17:00:16 +01:00
José Valim b0abc2b810 Properly track gradual types in for comprehensions :into 2024-12-30 11:52:11 +01:00
José Valim 1f9256a735 Properly handle optional keys in map intersection 2024-12-30 11:39:36 +01:00
José Valim ed2ff6a0a9 Fix expression-tag pairing inside with type checking 2024-12-30 11:07:22 +01:00
José Valim c64a035cd4 Track return type of for-comprehensions using :into 2024-12-30 10:59:02 +01:00
José Valim deca849bc3 Remove duplication in comprehensions integration tests 2024-12-29 19:55:46 +01:00
José Valim e9e4e24ef7 Track __info__(:struct) more precisely
Closes #14127.
2024-12-29 19:55:46 +01:00
José Valim f300c7ee96 Annotate the root level type that does not match a given expression 2024-12-29 19:55:46 +01:00
Harrison Chienjo 8302dbf519 Fix typos (#14128) 2024-12-29 19:42:09 +01:00
Steffen Deusch a4cb71c0cd Add test_load_filters and test_warn_filters to mix test (#14036) 2024-12-29 13:27:15 +01:00
Aleksei Matiushkin 6dc0d6f967 Add Mix.Tasks.Compile.reenable (#13771) 2024-12-29 13:09:34 +01:00
José Valim d9c536ce39 Do not expect protocols to have :signatures, closes #14126 2024-12-29 13:04:38 +01:00
Łukasz Samson 8e4d2cd6d7 Document assoc metadata entry (#14125) 2024-12-29 12:05:16 +01:00
José Valim d3cef1f32c Type checking of protocols in for-comprehensions (#14124) 2024-12-29 11:49:04 +01:00
José Valim 478711666e Pass args as explicit argument to Apply.remote 2024-12-28 11:49:36 +01:00
José Valim 4f0b0d9615 Use ensure_compiled for verifying protocol implementations 2024-12-28 09:38:16 +01:00
José Valim 6c7f6e7e79 Add a test to ensure no compile time deps on defimpl 2024-12-28 09:32:55 +01:00
José Valim bf068de4cd Properly raise for invalid async value 2024-12-28 09:32:43 +01:00
José Valim af4cb1d6a6 Simplify release matrix 2024-12-28 09:21:00 +01:00
José Valim c1c2cf8a65 Type checking of protocol dispatch (#14117) 2024-12-27 19:40:26 +01:00
Michał Łępicki c34627ca3b Fix type specs in the File module (#14123)
They were introduced in #14121.
2024-12-27 17:10:03 +01:00
José Valim 292f8761a2 Update AST metadata documentation (#14120) 2024-12-27 11:37:25 +01:00
Jonatan Kłosko 3ee2ecabff Remove duplicated metadata in nested call AST (#14122) 2024-12-27 11:36:52 +01:00
Gal Tsubery 71b85292c4 Update specs to match OTP definitions (#14121) 2024-12-27 09:39:21 +01:00
José Valim 256793830b Bring conditional violation reports back 2024-12-26 19:37:22 +01:00
José Valim baa7b5ed75 Revert "Remove unnecessary case annotations"
This reverts commit ecb4613c04.
2024-12-26 19:35:11 +01:00
José Valim 30764fe67d Add type checking of protocol implementations (#14115) 2024-12-26 09:00:45 +01:00
José Valim ecb4613c04 Remove unnecessary case annotations 2024-12-25 19:23:11 +01:00
Benjamin Zubača ea02b0c846 Fix indentation in JSON.Encoder module doc (#14114) 2024-12-25 13:35:03 +01:00
José Valim 62f49cd756 Conditionally run steps 2024-12-25 10:22:20 +01:00
José Valim e0eb19a933 Take two 2024-12-25 10:08:58 +01:00
José Valim 1ec5af94fe No need for == 2024-12-25 10:01:15 +01:00
José Valim 45e348b1f5 Run Erlang/OTP 25 release builds
Closes #14113.
2024-12-25 09:57:59 +01:00
Neil Berkman 933b008ab8 Fix grammar (#14112) 2024-12-24 18:25:23 +01:00
José Valim 54c85ed724 Improve grammar in CHANGELOG 2024-12-24 16:31:40 +01:00
José Valim 3c471cccac Update CHANGELOG 2024-12-24 10:39:24 +01:00
José Valim 591e97a49f Merge compile.protocols into compile.elixir (#14109) 2024-12-24 10:05:07 +01:00
José Valim 1cd67c961c Do not warn when comparing literals 2024-12-24 09:57:34 +01:00
José Valim 9df0fe5d8e Use division by zero to show exception
Closes #14111.
2024-12-24 09:42:09 +01:00
José Valim f9f61bbb20 Do not recompile if compilation fails due to --warnings-as-errors 2024-12-23 18:25:42 +01:00
José Valim 4285cea166 Improve container_cursor_to_quoted with trailing fragment, closes #14087 2024-12-23 12:23:58 +01:00
Kieran 92f1e3777f Omit excluded tests from test count in CLI summary (#14078) 2024-12-23 11:33:47 +01:00
Jonatan Kłosko 71ad8faeea Add build lock to deps.loadpaths (#14108) 2024-12-23 10:55:52 +01:00
José Valim 45aec00ff7 Queue async groups until state is loaded 2024-12-23 10:49:54 +01:00
José Valim d78ef17803 Do not run async groups on load and support --repeat-until-failure (#14107) 2024-12-23 10:31:43 +01:00
José Valim 462f614e79 Avoid crash when typing violation is detected on dynamic dispatch 2024-12-22 17:56:03 +01:00
José Valim 4b262d8835 More coverage around source on parallel compiler 2024-12-22 16:04:58 +01:00
José Valim 182e4b5304 Track source for warnings, closes #14093 2024-12-22 11:03:45 +01:00
José Valim fe089dea56 Do not validate clauses of overridable functions, closes #14094 2024-12-22 09:55:05 +01:00
Jean Klingler 5882a52e73 Deprecate complex module expressions in &mod.fun/arity captures (#14095) 2024-12-22 16:22:42 +09:00
José Valim 52e3aa73b8 Verify no warnings are emitted on generated clauses, see #14094 2024-12-21 21:29:04 +01:00
José Valim bca55b9afd Use binwrite only on Windows, closes #14101 2024-12-21 18:25:43 +01:00
Jonatan Männchen 91527e3753 Fix Release Signing Again (#14099) (#14102)
This time, fix the Docs publishing.
2024-12-21 17:59:53 +01:00
Jonatan Männchen 70bd74e0e0 Fix Release Signing (#14099) (#14100) 2024-12-21 16:17:38 +01:00
Jonatan Kłosko 40a73d7b80 Fix mix escript.build when protocol consolidation is disabled (#14098) 2024-12-21 12:22:38 +01:00
Jean Klingler 1b1e5c93b9 Improve doc navigability for child spec options (#14097) 2024-12-21 19:54:10 +09:00
Jean Klingler 853e9ca7b9 Remove no_parens when using capture with arity (#14090) 2024-12-20 22:27:11 +09:00
Jonatan Männchen 7cbc44d6bf Hash release files after signing (#14085) 2024-12-20 11:30:07 +01:00
José Valim 8a89663dfb Keep traces backwards compatible 2024-12-20 10:43:14 +01:00
José Valim 27a825aaf3 Annotate nifs to accumulate 2024-12-20 10:03:44 +01:00
Sebastia Ramon 68a17aea7e Fix typo in alias-require-and-import.md (#14086) 2024-12-20 09:56:09 +01:00
José Valim 6ff6fe45e6 Clarify unit padding/truncation in to_unix, closes #14081 2024-12-19 19:05:59 +01:00
Zach Daniel 74898fb990 Advise library authors on how best to depend on child dependencies (#14080) 2024-12-19 18:57:45 +01:00
José Valim 4689205d60 Improve warning as errors deprecation notice 2024-12-19 17:25:14 +01:00
José Valim 86d375178f Respect struct field order when printing types 2024-12-19 15:38:54 +01:00
José Valim 49e32095c0 Collapse structs for better pretty printing in different scenarios 2024-12-19 15:12:31 +01:00
José Valim ba5717adc6 Include types in comparison warning 2024-12-19 12:13:17 +01:00
José Valim 5532656ed5 Simplify and optimize NaiveDateTime.utc_now 2024-12-19 12:12:49 +01:00
José Valim 73fed09f66 Improvements to descr pretty printing 2024-12-18 09:31:32 +01:00
Guillaume Duboc bcc36c585b Fuse maps and tuples for printing (#14079) 2024-12-18 08:34:17 +01:00
José Valim 53f6a45b82 Update CHANGELOG 2024-12-17 11:46:53 +01:00
José Valim 98229529f6 Do not warn for CaseTemplate options, closes #14077 2024-12-17 10:03:40 +01:00
loving marshmallow a539395708 Correct typo in Process doc 2024-12-17 14:56:57 +09:00
José Valim 6443322382 Emit warnings for duplicate patterns instead of errors 2024-12-16 19:30:42 +01:00
José Valim 0513a038fe Shuffle fields order in JSON test 2024-12-16 17:26:14 +01:00
José Valim c52ee25526 Tag exceptions as dynamic, closes #14074 2024-12-16 16:12:55 +01:00
José Valim 87bba10b00 Add support for :group doc metadata and use it in IEx.Autocomplete 2024-12-16 13:52:46 +01:00
José Valim 7fdfe779b2 Also encode Duration to JSON 2024-12-16 12:23:40 +01:00
José Valim a506ebc257 Avoid crashes on diagnostics with tabs, closes #14073 2024-12-16 12:09:02 +01:00
José Valim a1fe483e27 Do not convert fun() into (... -> any()) and vice-versa in typespecs 2024-12-15 15:17:57 +01:00
Jonatan Kłosko fb7f24e4d5 Make :source_path in docs chunk a charlist (#14071) 2024-12-13 16:57:40 +01:00
José Valim 606cb13abd Fix Range.new deprecation assertion 2024-12-13 10:14:56 +01:00
Michał Łępicki 201cd1ebfd Fix JSON.decode/3 spec (#14068)
The error clause was missing the :error tuple wrapping
2024-12-13 09:52:20 +01:00
Mathias Polligkeit b7020f1d80 Further improve warning message for range with negative step (#14070) 2024-12-13 09:51:58 +01:00
Wojtek Mach fc2d8d94c0 Update Kernel docs (#14066) 2024-12-12 12:00:56 +01:00
Wojtek Mach eaf7123cf6 Do not set docs source_ref to -latest tags (#14065) 2024-12-12 11:51:19 +01:00
Jean Klingler 5f657f90b8 Add :migrate_call_parens_on_pipe formatter option (#14056) 2024-12-12 18:14:14 +09:00
Jean Klingler a1938d4b40 Fix type warnings in pop_in/1 (#14064) 2024-12-12 17:46:24 +09:00
José Valim 8a0cd664cb Implement JSON.Encoder for Calendar types (#14061) 2024-12-12 00:37:04 +01:00
Panagiotis Nezis fb288a4a80 Improve warning in Range.new/2 with negative step (#14062) 2024-12-12 00:36:49 +01:00
José Valim 423369304f Improve JSON and Float docs 2024-12-11 21:08:25 +01:00
ruslandoga b0951f346c fix typo in mix xref example (#14060) 2024-12-11 18:11:24 +01:00
Michał Łępicki 64e57a9b92 Sync docs improvements around auto_reloading from the v1.18 branch (#14059)
The changes were made in da7e04a540
directly on the v1.18 branch
2024-12-11 17:45:22 +01:00
José Valim 3de7658489 Postpone requirement for pin on bitstring 2024-12-11 17:40:53 +01:00
Andrea Leopardi 9f31cecca0 Improve error message on empty "mix do" (#14055) 2024-12-11 12:42:04 +01:00
José Valim 28fe70635b Fix encoding specs 2024-12-11 12:16:26 +01:00
José Valim a8ba82cb9d Remove accidental left-over content 2024-12-11 12:07:35 +01:00
José Valim 098c01a7ee Introduce deprecations scheduled for v1.19 2024-12-11 11:59:36 +01:00
José Valim e3bbef375f Add Node.start/2 with keyword list 2024-12-11 10:24:57 +01:00
José Valim 70ccf57bac Also document min and max on guards guide 2024-12-11 10:04:34 +01:00
Clark Kampfe 13285e90f8 Document min and max are allowed in guards (#14051) 2024-12-11 10:03:05 +01:00
José Valim 593851fabb Touch the compile path when writing .app file for the first time (#14053)
Closes #14049.
2024-12-11 10:00:31 +01:00
sobolevn 1ab6cdc2db Deprecate single quotes in quoted atoms and calls (#14047)
Closes #13958.
2024-12-10 19:31:11 +01:00
José Valim 77ea87ae29 Parse OTP release 2024-12-10 19:30:25 +01:00
José Valim 66c82a2361 Fix error message around single quote keywords 2024-12-10 19:29:32 +01:00
José Valim 13eadf94f9 Avoid mtime races on compile.app_test.exs 2024-12-10 15:41:05 +01:00
José Valim ef7c11814f Require Erlang/OTP 26+ (#14045) 2024-12-10 15:38:02 +01:00
José Valim 80c51cce96 Start v1.19 2024-12-10 15:05:41 +01:00
659 changed files with 34465 additions and 13026 deletions
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
[
inputs: [
"lib/*/{lib,scripts,unicode,test}/**/*.{ex,exs}",
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
lib/elixir/test/elixir/fixtures/*.txt text eol=lf
*.ex diff=elixir
*.exs diff=elixir
+3
View File
@@ -1,3 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
---
blank_issues_enabled: true
+3
View File
@@ -1,3 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
---
name: Report an issue
description:
+3
View File
@@ -1,3 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
version: 2
updates:
- package-ecosystem: "github-actions"
+8 -2
View File
@@ -1,3 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
name: CI for Markdown content
on:
@@ -11,6 +14,9 @@ on:
- "lib/**/*.md"
workflow_dispatch:
permissions:
contents: read
jobs:
lint:
name: Lint Markdown content
@@ -22,12 +28,12 @@ jobs:
steps:
- name: Check out the repository
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 10
- name: Run markdownlint
uses: DavidAnson/markdownlint-cli2-action@v18.0.0
uses: DavidAnson/markdownlint-cli2-action@992badcdf24e3b8eb7e87ff9287fe931bcb00c6e # v20.0.0
with:
globs: |
lib/elixir/pages/**/*.md
+63 -14
View File
@@ -1,3 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
name: CI
on:
@@ -18,29 +21,39 @@ permissions:
jobs:
test_linux:
name: Ubuntu 24.04, Erlang/OTP ${{ matrix.otp_version }}${{ matrix.deterministic && ' (deterministic)' || '' }}
name: Ubuntu 24.04, Erlang/OTP ${{ matrix.otp_version }}${{ matrix.deterministic && ' (deterministic)' || '' }}${{ matrix.coverage && ' (coverage)' || '' }}
strategy:
fail-fast: false
matrix:
include:
- otp_version: "27.1"
- otp_version: "28.1"
deterministic: true
- otp_version: "27.1"
- otp_version: "28.1"
erlc_opts: "warnings_as_errors"
coverage: true
- otp_version: "28.1"
otp_latest: true
erlc_opts: "warnings_as_errors"
- otp_version: "27.3"
erlc_opts: "warnings_as_errors"
- otp_version: "27.0"
erlc_opts: "warnings_as_errors"
- otp_version: "26.0"
- otp_version: "25.3"
- otp_version: "25.0"
- otp_version: master
development: true
- otp_version: maint
development: true
runs-on: ubuntu-24.04
# Earlier Erlang/OTP versions ignored compiler directives
# when using warnings_as_errors. So we only set ERLC_OPTS
# from Erlang/OTP 27+.
env:
ERLC_OPTS: ${{ matrix.erlc_opts || '' }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 50
- uses: erlef/setup-beam@v1
- uses: erlef/setup-beam@8aa8a857c6be0daae6e97272bb299d5b942675a4 # v1.19.0
with:
otp-version: ${{ matrix.otp_version }}
- name: Set ERL_COMPILER_OPTIONS
@@ -60,6 +73,11 @@ jobs:
- name: Elixir test suite
run: make test_elixir
continue-on-error: ${{ matrix.development }}
env:
COVER: "${{ matrix.coverage }}"
- name: "Calculate Coverage"
run: make cover | tee "$GITHUB_STEP_SUMMARY"
if: "${{ matrix.coverage }}"
- name: Build docs (ExDoc main)
if: ${{ matrix.otp_latest }}
run: |
@@ -68,7 +86,8 @@ jobs:
cd ex_doc
../elixir/bin/mix do local.rebar --force + local.hex --force + deps.get + compile
cd ../elixir/
make docs
git fetch --tags
DOCS_OPTIONS="--warnings-as-errors" make docs
- name: Check reproducible builds
if: ${{ matrix.deterministic }}
run: |
@@ -76,20 +95,26 @@ jobs:
# Recompile System without .git
cd lib/elixir && ../../bin/elixirc -o ebin lib/system.ex && cd -
taskset 1 make check_reproducible
- name: "Upload Coverage Artifact"
if: "${{ matrix.coverage }}"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: TestCoverage
path: cover/*
test_windows:
name: Windows Server 2019, Erlang/OTP ${{ matrix.otp_version }}
strategy:
matrix:
otp_version: ["25.3", "26.2", "27.3"]
otp_version: ["26.2", "27.3", "28.1"]
runs-on: windows-2022
steps:
- name: Configure Git
run: git config --global core.autocrlf input
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 50
- uses: erlef/setup-beam@v1
- uses: erlef/setup-beam@8aa8a857c6be0daae6e97272bb299d5b942675a4 # v1.19.0
with:
otp-version: ${{ matrix.otp_version }}
- name: Compile Elixir
@@ -101,17 +126,17 @@ jobs:
- name: Check format
run: make test_formatted && echo "All Elixir source code files are properly formatted."
- name: Erlang test suite
run: make --keep-going test_erlang
run: make test_erlang
- name: Elixir test suite
run: |
Remove-Item 'c:/Windows/System32/drivers/etc/hosts'
make --keep-going test_elixir
make test_elixir
check_posix_compliant:
name: Check POSIX-compliant
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 50
- name: Install Shellcheck
@@ -123,3 +148,27 @@ jobs:
shellcheck -e SC2039,2086 bin/elixir && echo "bin/elixir is POSIX compliant"
shellcheck bin/elixirc && echo "bin/elixirc is POSIX compliant"
shellcheck bin/iex && echo "bin/iex is POSIX compliant"
license_compliance:
name: Check Licence Compliance
runs-on: ubuntu-24.04
steps:
- name: Use HTTPS instead of SSH for Git cloning
id: git-config
shell: bash
run: git config --global url.https://github.com/.insteadOf ssh://git@github.com/
- name: Checkout project
id: checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: "Run OSS Review Toolkit"
id: ort
uses: ./.github/workflows/ort
with:
upload-reports: true
fail-on-violation: true
report-formats: "WebApp"
version: "${{ github.sha }}"
+4
View File
@@ -1,4 +1,8 @@
# #!/usr/bin/env elixir
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
[tag] = System.argv()
Mix.install([
+5 -2
View File
@@ -1,3 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
name: Notify
on:
@@ -13,10 +16,10 @@ jobs:
runs-on: ubuntu-latest
name: Notify
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 50
- uses: erlef/setup-beam@v1
- uses: erlef/setup-beam@8aa8a857c6be0daae6e97272bb299d5b942675a4 # v1.19.0
with:
otp-version: "27.3"
elixir-version: "1.18.3"
+109
View File
@@ -0,0 +1,109 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
name: "Run OSS Review Toolkit"
description: "Runs OSS Review Toolkit & generates SBoMs"
inputs:
report-formats:
description: "ORT Report Formats"
required: true
fail-on-violation:
description: "Whether to fail on violation."
required: false
default: false
upload-reports:
description: "Whether to upload all reports"
required: false
default: false
version:
description: "Elixir Version (Tag / SHA)"
required: true
outputs:
results-path:
description: "See oss-review-toolkit/ort-ci-github-action action"
value: "${{ steps.ort.outputs.results-path }}"
results-sbom-cyclonedx-xml-path:
description: "See oss-review-toolkit/ort-ci-github-action action"
value: "${{ steps.ort.outputs.results-sbom-cyclonedx-xml-path }}"
results-sbom-cyclonedx-json-path:
description: "See oss-review-toolkit/ort-ci-github-action action"
value: "${{ steps.ort.outputs.results-sbom-cyclonedx-json-path }}"
results-sbom-spdx-yml-path:
description: "See oss-review-toolkit/ort-ci-github-action action"
value: "${{ steps.ort.outputs.results-sbom-spdx-yml-path }}"
results-sbom-spdx-json-path:
description: "See oss-review-toolkit/ort-ci-github-action action"
value: "${{ steps.ort.outputs.results-sbom-spdx-json-path }}"
runs:
using: "composite"
steps:
- name: Fetch Default ORT Config
id: fetch-default-ort-config
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
repository: oss-review-toolkit/ort-config
ref: "main"
path: ".ort-config"
- name: Setup ORT Config
id: setup-ort-config
shell: bash
run: |
mkdir -p "/$HOME/.ort/"
# Move Fetched Default Config into Place
mv .ort-config "$HOME/.ort/config"
# Append Global ORT Config
cat .ort/config/config.yml >> "$HOME/.ort/config/config.yml"
# Override Default Evaluator Rules
cp .ort/config/evaluator.rules.kts "$HOME/.ort/config/evaluator.rules.kts"
# Add Package Configurations
mkdir -p "$HOME/.ort/config/package-configurations/SpdxDocumentFile/The Elixir Team"
for FILE in .ort/package-configurations/*.yml; do
COMPONENT="$(basename "$FILE")"
cp "$FILE" "$HOME/.ort/config/package-configurations/SpdxDocumentFile/The Elixir Team/$COMPONENT"
sed -i -E \
"s/(\"SpdxDocumentFile:The Elixir Team:.+:)\"/\1${ELIXIR_VERSION}\"/" \
"$HOME/.ort/config/package-configurations/SpdxDocumentFile/The Elixir Team/$COMPONENT"
done
# Set Version in SPDX & Config
sed -i "s/# elixir-version-insert/versionInfo: '${ELIXIR_VERSION}'/" project.spdx.yml
sed -i -E "s/(\"SpdxDocumentFile:The Elixir Team:.+:)\"/\1${ELIXIR_VERSION}\"/" .ort.yml
sed -i "s|https://github.com/elixir-lang/elixir.git|${ELIXIR_REPO}@${ELIXIR_VERSION}|" project.spdx.yml
env:
ELIXIR_VERSION: "${{ inputs.version }}"
ELIXIR_REPO: "${{ github.server_url }}/${{ github.repository }}.git"
- name: "Cache ScanCode"
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
with:
path: "~/.cache/scancode-tk"
key: ${{ runner.os }}-scancode
- name: Run OSS Review Toolkit
id: ort
uses: oss-review-toolkit/ort-ci-github-action@1805edcf1f4f55f35ae6e4d2d9795ccfb29b6021 # v1.1.0
with:
image: ghcr.io/oss-review-toolkit/ort-minimal:65.0.0
run: >-
labels,
cache-dependencies,
cache-scan-results,
analyzer,
scanner,
advisor,
evaluator,
reporter,
${{ inputs.upload-reports == 'true' && 'upload-results' || '' }}
fail-on: "${{ inputs.fail-on-violation == 'true' && 'violations,issues' || '' }}"
report-formats: "${{ inputs.report-formats }}"
ort-cli-report-args: >-
-O CycloneDX=output.file.formats=json,xml
-O SpdxDocument=outputFileFormats=JSON,YAML
sw-version: "${{ inputs.version }}"
+150 -59
View File
@@ -1,3 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
name: Release
on:
@@ -13,13 +16,13 @@ env:
LANG: C.UTF-8
permissions:
contents: write
id-token: write
attestations: write
contents: read
jobs:
create_draft_release:
runs-on: ubuntu-22.04
permissions:
contents: write
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
@@ -33,7 +36,7 @@ jobs:
--draft \
${{ github.ref_name }}
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
if: github.ref_type == 'branch'
with:
fetch-depth: 50
@@ -55,20 +58,24 @@ jobs:
git push origin $ref_name --force
build:
name: "Build Elixir"
strategy:
fail-fast: true
matrix:
include:
- otp: 25
otp_version: "25.3"
- otp: 26
otp_version: "26.0"
- otp: 27
otp_version: "27.0"
- otp: 28
otp_version: "28.0"
build_docs: build_docs
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 50
@@ -79,49 +86,38 @@ jobs:
otp: ${{ matrix.otp }}
build_docs: ${{ matrix.build_docs }}
- name: "Attest docs provenance"
uses: actions/attest-build-provenance@v2
id: attest-docs-provenance
if: ${{ matrix.build_docs }}
with:
subject-path: "Docs.zip"
- name: "Copy docs provenance"
if: ${{ matrix.build_docs }}
run: cp "$ATTESTATION" Docs.zip.sigstore
env:
ATTESTATION: "${{ steps.attest-docs-provenance.outputs.bundle-path }}"
- name: Create Docs Hashes
if: ${{ matrix.build_docs }}
if: matrix.build_docs
run: |
shasum -a 1 Docs.zip > Docs.zip.sha1sum
shasum -a 256 Docs.zip > Docs.zip.sha256sum
- name: "Upload linux release artifacts"
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: build-linux-elixir-otp-${{ matrix.otp }}
path: elixir-otp-${{ matrix.otp }}.zip
- name: "Upload windows release artifacts"
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: build-windows-elixir-otp-${{ matrix.otp }}
path: elixir-otp-${{ matrix.otp }}.exe
- name: "Upload doc artifacts"
uses: actions/upload-artifact@v4
if: ${{ matrix.build_docs }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
if: matrix.build_docs
with:
name: Docs
path: Docs.zip*
sign:
needs: [build]
environment: release
strategy:
fail-fast: true
matrix:
otp: [25, 26, 27]
otp: [26, 27, 28]
flavor: [windows, linux]
env:
@@ -129,38 +125,36 @@ jobs:
runs-on: ${{ matrix.flavor == 'linux' && 'ubuntu-22.04' || 'windows-2022' }}
permissions:
contents: write
id-token: write
steps:
- uses: actions/download-artifact@v4
- name: "Download build"
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: build-${{ matrix.flavor }}-elixir-otp-${{ matrix.otp }}
- name: Log in to Azure
if: ${{ matrix.flavor == 'windows' && vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: "Sign files with Trusted Signing"
if: github.repository == 'elixir-lang/elixir' && matrix.flavor == 'windows'
uses: azure/trusted-signing-action@v0.5.0
uses: azure/trusted-signing-action@0d74250c661747df006298d0fb49944c10f16e03 # v0.5.1
if: ${{ matrix.flavor == 'windows' && vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
with:
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
endpoint: https://eus.codesigning.azure.net/
trusted-signing-account-name: trusted-signing-elixir
certificate-profile-name: Elixir
trusted-signing-account-name: ${{ vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
certificate-profile-name: ${{ vars.AZURE_CERTIFICATE_PROFILE_NAME }}
files-folder: ${{ github.workspace }}
files-folder-filter: exe
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
- name: "Attest release provenance"
uses: actions/attest-build-provenance@v2
id: attest-provenance
with:
subject-path: ${{ env.RELEASE_FILE }}
- name: "Copy release .zip provenance"
shell: bash
run: cp "$ATTESTATION" "${RELEASE_FILE}.sigstore"
env:
ATTESTATION: "${{ steps.attest-provenance.outputs.bundle-path }}"
- name: Create Release Hashes
if: matrix.flavor == 'windows'
shell: pwsh
@@ -179,20 +173,105 @@ jobs:
shasum -a 256 "$RELEASE_FILE" > "${RELEASE_FILE}.sha256sum"
- name: "Upload linux release artifacts"
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: sign-${{ matrix.flavor }}-elixir-otp-${{ matrix.otp }}
path: ${{ env.RELEASE_FILE }}*
upload-release:
needs: [create_draft_release, build, sign]
runs-on: ubuntu-22.04
sbom:
name: Generate SBoM
needs: [build, sign]
runs-on: ubuntu-24.04
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: actions/download-artifact@v4
- name: Use HTTPS instead of SSH for Git cloning
id: git-config
shell: bash
run: git config --global url.https://github.com/.insteadOf ssh://git@github.com/
- name: Checkout project
id: checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: "Download Build Artifacts"
id: download-build-artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: "{sign-*-elixir-otp-*,Docs}"
merge-multiple: true
path: /tmp/build-artifacts/
- name: "Run OSS Review Toolkit"
id: ort
uses: ./.github/workflows/ort
with:
report-formats: "CycloneDx,SpdxDocument"
version: "${{ github.ref_type == 'tag' && github.ref_name || github.sha }}"
- name: Attest Distribution Assets with SBoM
id: attest-sbom
uses: actions/attest-sbom@115c3be05ff3974bcbd596578934b3f9ce39bf68 # v2.2.0
with:
subject-path: |
/tmp/build-artifacts/{elixir-otp-*.*,Docs.zip}
${{ steps.ort.outputs.results-sbom-cyclonedx-xml-path }}
${{ steps.ort.outputs.results-sbom-cyclonedx-json-path }}
${{ steps.ort.outputs.results-sbom-spdx-yml-path }}
${{ steps.ort.outputs.results-sbom-spdx-json-path }}
sbom-path: "${{ steps.ort.outputs.results-sbom-spdx-json-path }}"
- name: "Copy SBoM provenance"
id: sbom-provenance
shell: bash
run: |
mkdir attestations
for FILE in /tmp/build-artifacts/{elixir-otp-*.*,Docs.zip}; do
cp "$ATTESTATION" "attestations/$(basename "$FILE").sigstore"
done
cp "$ATTESTATION" "attestations/$(basename "${{ steps.ort.outputs.results-sbom-cyclonedx-xml-path }}").sigstore"
cp "$ATTESTATION" "attestations/$(basename "${{ steps.ort.outputs.results-sbom-cyclonedx-json-path }}").sigstore"
cp "$ATTESTATION" "attestations/$(basename "${{ steps.ort.outputs.results-sbom-spdx-yml-path }}").sigstore"
cp "$ATTESTATION" "attestations/$(basename "${{ steps.ort.outputs.results-sbom-spdx-json-path }}").sigstore"
env:
ATTESTATION: "${{ steps.attest-sbom.outputs.bundle-path }}"
- name: "Assemble Release SBoM Artifacts"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: "SBoM"
path: |
${{ steps.ort.outputs.results-sbom-cyclonedx-xml-path }}
${{ steps.ort.outputs.results-sbom-cyclonedx-json-path }}
${{ steps.ort.outputs.results-sbom-spdx-yml-path }}
${{ steps.ort.outputs.results-sbom-spdx-json-path }}
- name: "Assemble Distribution Attestations"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: "Attestations"
path: "attestations/*.sigstore"
upload-release:
needs: [create_draft_release, build, sign, sbom]
runs-on: ubuntu-22.04
permissions:
contents: write
steps:
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: "{sign-*-elixir-otp-*,Docs,SBoM,Attestations}"
merge-multiple: true
- name: Upload Pre-built
shell: bash
@@ -217,23 +296,27 @@ jobs:
elixir-otp-*.exe.sigstore \
Docs.zip \
Docs.zip.sha{1,256}sum \
Docs.zip.sigstore
Docs.zip.sigstore \
bom.*
upload-builds-hex-pm:
needs: [build, sign]
runs-on: ubuntu-22.04
concurrency: builds-hex-pm
environment: release
env:
AWS_ACCESS_KEY_ID: ${{ secrets.HEX_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.HEX_AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.HEX_AWS_REGION }}
AWS_S3_BUCKET: ${{ secrets.HEX_AWS_S3_BUCKET }}
FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }}
FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }}
FASTLY_KEY: ${{ secrets.HEX_FASTLY_KEY }}
OTP_GENERIC_VERSION: "25"
AWS_REGION: ${{ vars.HEX_AWS_REGION }}
AWS_S3_BUCKET: ${{ vars.HEX_AWS_S3_BUCKET }}
steps:
- uses: actions/download-artifact@v4
- name: "Check if variables are set up"
if: "${{ ! vars.HEX_AWS_REGION }}"
run: |
echo "Required variables for uploading to hex.pm are not set up, skipping..."
exit 1
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: "{sign-*-elixir-otp-*,Docs}"
merge-multiple: true
@@ -246,6 +329,8 @@ jobs:
run: |
ref_name=${{ github.ref_name }}
oldest_otp=$(find . -type f -name 'elixir-otp-*.zip' | sed -r 's/^.*elixir-otp-([[:digit:]]+)\.zip$/\1/' | sort -n | head -n 1)
for zip in $(find . -type f -name 'elixir-otp-*.zip' | sed 's/^\.\///'); do
dest=${zip/elixir/${ref_name}}
surrogate_key=${dest/.zip$/}
@@ -255,7 +340,7 @@ jobs:
--metadata "{\"surrogate-key\":\"builds builds/elixir builds/elixir/${surrogate_key}\",\"surrogate-control\":\"public,max-age=604800\"}"
echo "builds/elixir/${surrogate_key}" >> purge_keys.txt
if [ "$zip" == "elixir-otp-${OTP_GENERIC_VERSION}.zip" ]; then
if [ "$zip" == "elixir-otp-${oldest_otp}.zip" ]; then
aws s3 cp "${zip}" "s3://${AWS_S3_BUCKET}/builds/elixir/${ref_name}.zip" \
--cache-control "public,max-age=3600" \
--metadata "{\"surrogate-key\":\"builds builds/elixir builds/elixir/${ref_name}\",\"surrogate-control\":\"public,max-age=604800\"}"
@@ -288,6 +373,8 @@ jobs:
date="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
ref_name=${{ github.ref_name }}
oldest_otp=$(find . -name 'elixir-otp-*.zip.sha256sum' | sed -r 's/^.*elixir-otp-([[:digit:]]+)\.zip\.sha256sum$/\1/' | sort -n | head -n 1)
aws s3 cp "s3://${AWS_S3_BUCKET}/builds/elixir/builds.txt" builds.txt || true
touch builds.txt
@@ -298,7 +385,7 @@ jobs:
sed -i "/^${ref_name}-${otp_version} /d" builds.txt
echo -e "${ref_name}-${otp_version} ${{ github.sha }} ${date} ${build_sha256} \n$(cat builds.txt)" > builds.txt
if [ "${otp_version}" == "otp-${OTP_GENERIC_VERSION}" ]; then
if [ "${otp_version}" == "otp-${oldest_otp}" ]; then
sed -i "/^${ref_name} /d" builds.txt
echo -e "${ref_name} ${{ github.sha }} ${date} ${build_sha256} \n$(cat builds.txt)" > builds.txt
fi
@@ -337,3 +424,7 @@ jobs:
for key in $(cat purge_keys.txt); do
purge "${key}"
done
env:
FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }}
FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }}
FASTLY_KEY: ${{ secrets.HEX_FASTLY_KEY }}
@@ -1,3 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
name: "Release pre built"
description: "Builds elixir release, ExDoc and generates docs"
inputs:
@@ -10,7 +13,7 @@ inputs:
runs:
using: "composite"
steps:
- uses: erlef/setup-beam@v1
- uses: erlef/setup-beam@5304e04ea2b355f03681464e683d92e3b2f18451 # v1.18.2
with:
otp-version: ${{ inputs.otp_version }}
version-type: strict
@@ -42,7 +45,7 @@ runs:
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@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
if: ${{ inputs.build_docs }}
with:
repository: elixir-lang/ex_doc
+5
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
/doc/
/lib/*/ebin/
/lib/*/_build/
@@ -11,3 +15,4 @@
/.eunit
.elixir.plt
erl_crash.dump
/cover/
+3
View File
@@ -1,3 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2021 The Elixir Team
{
// Consecutive header levels (h1 -> h2 -> h3). We don't care about this.
"MD001": false,
+118
View File
@@ -0,0 +1,118 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
excludes:
paths:
- pattern: "man/*"
reason: "DOCUMENTATION_OF"
comment: "Documentation"
- pattern: ".github/**/*"
reason: "BUILD_TOOL_OF"
comment: "Documentation"
- pattern: ".ort/**/*"
reason: "BUILD_TOOL_OF"
comment: "Documentation"
# Unfortunately we'll have to repeat all package level excludes here
# Make sure to keep them in sync with the package configuration in
# .ort/package-configurations
- pattern: "lib/*/pages/**/*"
reason: "DOCUMENTATION_OF"
comment: "Documentation"
- pattern: "lib/*/test/**/*"
reason: "TEST_OF"
comment: "Tests"
- pattern: "lib/*/scripts/**/*"
reason: "BUILD_TOOL_OF"
comment: "Build Tool"
- pattern: "lib/*/examples/**/*"
reason: "EXAMPLE_OF"
comment: "Example"
curations:
license_findings:
# Version File
- path: "VERSION"
reason: "NOT_DETECTED"
comment: "Apply Trademark Policy to VERSION file"
detected_license: "NONE"
concluded_license: "Apache-2.0"
# Wrongly Identified
- path: ".gitignore"
reason: "INCORRECT"
comment: "Ignored by ScanCode"
detected_license: "NONE"
concluded_license: "Apache-2.0"
- path: ".gitattributes"
reason: "INCORRECT"
comment: "Ignored by ScanCode"
detected_license: "NONE"
concluded_license: "Apache-2.0"
- path: "CONTRIBUTING.md"
reason: "INCORRECT"
comment: "Wrongly identified TSL license"
detected_license: "Apache-2.0 OR NOASSERTION OR LicenseRef-scancode-tsl-2020"
concluded_license: "Apache-2.0"
- path: "OPEN_SOURCE_POLICY.md"
reason: "INCORRECT"
comment: "Wrongly identified NOASSERTION"
detected_license: "NOASSERTION"
concluded_license: "Apache-2.0"
# Unfortunately we'll have to repeat all package level license curations here
# Make sure to keep them in sync with the package configuration in
# .ort/package-configurations
# Test Fixtures
- path: "lib/*/test/fixtures/**/*"
reason: "NOT_DETECTED"
comment: "Apply default license to test fixtures"
detected_license: "NONE"
concluded_license: "Apache-2.0"
# Logos
- path: "lib/elixir/pages/images/logo.png"
reason: "NOT_DETECTED"
comment: "Apply Trademark Policy to Elixir Logo"
detected_license: "NONE"
concluded_license: "LicenseRef-elixir-trademark-policy"
- path: "lib/elixir/scripts/windows_installer/assets/Elixir.ico"
reason: "NOT_DETECTED"
comment: "Apply Trademark Policy to Elixir Logo"
detected_license: "NONE"
concluded_license: "LicenseRef-elixir-trademark-policy"
# Documentation Images
- path: "lib/elixir/pages/images/**/*.png"
reason: "NOT_DETECTED"
comment: "Apply default license to all images"
detected_license: "NONE"
concluded_license: "Apache-2.0"
# Test Fixtures
- path: "lib/elixir/test/elixir/fixtures/**/*"
reason: "NOT_DETECTED"
comment: "Apply default license to test fixtures"
detected_license: "NONE"
concluded_license: "Apache-2.0"
# Unicode
- path: "lib/elixir/unicode/*.txt"
reason: "NOT_DETECTED"
comment: "Apply default license to unicode files"
detected_license: "NONE"
concluded_license: "LicenseRef-scancode-unicode"
# Wrongly Identified
- path: "lib/elixir/pages/references/library-guidelines.md"
reason: "INCORRECT"
comment: |
The guide mentions multiple licenses for users to choose from.
It however is not licensed itself by the mentioned licenses.
concluded_license: "Apache-2.0"
- path: "lib/elixir/scripts/windows_installer/.gitignore"
reason: "INCORRECT"
comment: "Ignored by ScanCode"
detected_license: "NONE"
concluded_license: "Apache-2.0"
+21
View File
@@ -0,0 +1,21 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
ort:
enableRepositoryPackageCurations: true
enableRepositoryPackageConfigurations: true
scanner:
skipConcluded: false
includeFilesWithoutFindings: true
analyzer:
allowDynamicVersions: true
enabledPackageManagers: [SpdxDocumentFile]
reporter:
reporters:
SpdxDocument:
options:
creationInfoOrganization: The Elixir Team
documentName: "Elixir Source SPDX Document"
+88
View File
@@ -0,0 +1,88 @@
/*
* Copyright (C) 2019 The ORT Project Authors (see <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>)
* Copyright (c) 2021 The Elixir Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
// Docs: https://oss-review-toolkit.org/ort/docs/configuration/evaluator-rules
val whitelistedLicenses = listOf(
// License for Elixir & Imported Erlang Projects
"Apache-2.0",
// License for the Elixir Logo
"LicenseRef-elixir-trademark-policy",
"LicenseRef-scancode-elixir-trademark-policy",
// License for included Unicode Files
"LicenseRef-scancode-unicode",
// DCO for committers
"LicenseRef-scancode-dco-1.1"
).map { SpdxSingleLicenseExpression.parse(it) }.toSet()
fun PackageRule.howToFixDefault() = """
* Check if this license violation is intended
* Adjust evaluation rules in `.ort/config/evaluator.rules.kts`
""".trimIndent()
fun PackageRule.LicenseRule.isHandled() =
object : RuleMatcher {
override val description = "isHandled($license)"
override fun matches() = license in whitelistedLicenses
}
fun RuleSet.unhandledLicenseRule() = packageRule("UNHANDLED_LICENSE") {
// Do not trigger this rule on packages that have been excluded in the .ort.yml.
require {
-isExcluded()
}
// Define a rule that is executed for each license of the package.
licenseRule("UNHANDLED_LICENSE", LicenseView.CONCLUDED_OR_DECLARED_AND_DETECTED) {
require {
-isExcluded()
-isHandled()
}
// Throw an error message including guidance how to fix the issue.
error(
"The license $license is currently not covered by policy rules. " +
"The license was ${licenseSource.name.lowercase()} in package " +
"${pkg.metadata.id.toCoordinates()}.",
howToFixDefault()
)
}
}
fun RuleSet.unmappedDeclaredLicenseRule() = packageRule("UNMAPPED_DECLARED_LICENSE") {
require {
-isExcluded()
}
resolvedLicenseInfo.licenseInfo.declaredLicenseInfo.processed.unmapped.forEach { unmappedLicense ->
warning(
"The declared license '$unmappedLicense' could not be mapped to a valid license or parsed as an SPDX " +
"expression. The license was found in package ${pkg.metadata.id.toCoordinates()}.",
howToFixDefault()
)
}
}
val ruleSet = ruleSet(ortResult, licenseInfoResolver, resolutionProvider) {
unhandledLicenseRule()
unmappedDeclaredLicenseRule()
}
ruleViolations += ruleSet.violations
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
id: "SpdxDocumentFile:The Elixir Team:eex:"
path_excludes:
- pattern: "lib/eex/test/**/*"
reason: "TEST_OF"
comment: "Tests"
license_finding_curations:
# Test Fixtures
- path: "lib/eex/test/fixtures/**/*"
reason: "NOT_DETECTED"
comment: "Apply default license to test fixtures"
detected_license: "NONE"
concluded_license: "Apache-2.0"
+60
View File
@@ -0,0 +1,60 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
id: "SpdxDocumentFile:The Elixir Team:elixir:"
path_excludes:
- pattern: "lib/elixir/pages/**/*"
reason: "DOCUMENTATION_OF"
comment: "Documentation"
- pattern: "lib/elixir/scripts/**/*"
reason: "BUILD_TOOL_OF"
comment: "Build Tool"
- pattern: "lib/elixir/test/**/*"
reason: "TEST_OF"
comment: "Tests"
license_finding_curations:
# Logos
- path: "lib/elixir/pages/images/logo.png"
reason: "NOT_DETECTED"
comment: "Apply Trademark Policy to Elixir Logo"
detected_license: "NONE"
concluded_license: "LicenseRef-elixir-trademark-policy"
- path: "lib/elixir/scripts/windows_installer/assets/Elixir.ico"
reason: "NOT_DETECTED"
comment: "Apply Trademark Policy to Elixir Logo"
detected_license: "NONE"
concluded_license: "LicenseRef-elixir-trademark-policy"
# Documentation Images
- path: "lib/elixir/pages/images/**/*.png"
reason: "NOT_DETECTED"
comment: "Apply default license to all images"
detected_license: "NONE"
concluded_license: "Apache-2.0"
# Test Fixtures
- path: "lib/elixir/test/elixir/fixtures/**/*"
reason: "NOT_DETECTED"
comment: "Apply default license to test fixtures"
detected_license: "NONE"
concluded_license: "Apache-2.0"
# Unicode
- path: "lib/elixir/unicode/*.txt"
reason: "NOT_DETECTED"
comment: "Apply default license to unicode files"
detected_license: "NONE"
concluded_license: "LicenseRef-scancode-unicode"
# Wrongly Identified
- path: "lib/elixir/pages/references/library-guidelines.md"
reason: "INCORRECT"
comment: |
The guide mentions multiple licenses for users to choose from.
It however is not licensed itself by the mentioned licenses.
concluded_license: "Apache-2.0"
- path: "lib/elixir/scripts/windows_installer/.gitignore"
reason: "INCORRECT"
comment: "Ignored by ScanCode"
detected_license: "NONE"
concluded_license: "Apache-2.0"
+18
View File
@@ -0,0 +1,18 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
id: "SpdxDocumentFile:The Elixir Team:exunit:"
path_excludes:
- pattern: "lib/ex_unit/examples/**/*"
reason: "EXAMPLE_OF"
comment: "Example"
- pattern: "lib/ex_unit/test/**/*"
reason: "TEST_OF"
comment: "Tests"
license_finding_curations:
# Test Fixtures
- path: "lib/ex_unit/test/fixtures/**/*"
reason: "NOT_DETECTED"
comment: "Apply default license to test fixtures"
detected_license: "NONE"
concluded_license: "Apache-2.0"
+8
View File
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
id: "SpdxDocumentFile:The Elixir Team:logger:"
path_excludes:
- pattern: "lib/logger/test/**/*"
reason: "TEST_OF"
comment: "Tests"
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
id: "SpdxDocumentFile:The Elixir Team:mix:"
path_excludes:
- pattern: "lib/mix/test/**/*"
reason: "TEST_OF"
comment: "Tests"
license_finding_curations:
# Test Fixtures
- path: "lib/mix/test/fixtures/**/*"
reason: "NOT_DETECTED"
comment: "Apply default license to test fixtures"
detected_license: "NONE"
concluded_license: "Apache-2.0"
+395 -369
View File
@@ -1,482 +1,508 @@
# Changelog for Elixir v1.18
<!--
SPDX-License-Identifier: Apache-2.0
SPDX-FileCopyrightText: 2021 The Elixir Team
SPDX-FileCopyrightText: 2012 Plataformatec
-->
Elixir v1.18 is an impressive release with improvements across the two main efforts happening within the Elixir ecosystem right now: set-theoretic types and language servers. It also comes with built-in JSON support and adds new capabilities to its unit testing library. Here is a quick break down.
# Changelog for Elixir v1.19
## Type system improvements
The most exciting change in Elixir v1.18 is type checking of function calls, alongside gradual inference of patterns and return types. To understand how this will impact your programs, consider the following code in "lib/user.ex":
### Type checking of protocol dispatch and implementations
This release also adds type checking when dispatching and implementing protocols.
For example, string interpolation in Elixir uses the `String.Chars` protocol. If you pass a value that does not implement said protocol, Elixir will now emit a warning accordingly.
Here is an example passing a range, which cannot be converted into a string, to an interpolation:
```elixir
defmodule User do
defstruct [:age, :car_choice]
def drive(%User{age: age, car_choice: car}, car_choices) when age >= 18 do
if car in car_choices do
{:ok, car}
else
{:error, :no_choice}
end
end
def drive(%User{}, _car_choices) do
{:error, :not_allowed}
defmodule Example do
def my_code(first..last//step = range) do
"hello #{range}"
end
end
```
Elixir's type system will infer that the `drive/2` function expects a `%User{}` struct and returns either `{:ok, dynamic()}`, `{:error, :no_choice}`, or `{:error, :not_allowed}`.
the above emits the following warnings:
Therefore, the following code in a separate module (either in a separate or the same file), should emit a violation, due to an invalid argument:
```
warning: incompatible value given to string interpolation:
data
it has type:
%Range{first: term(), last: term(), step: term()}
but expected a type that implements the String.Chars protocol, it must be one of:
dynamic(
%Date{} or %DateTime{} or %NaiveDateTime{} or %Time{} or %URI{} or %Version{} or
%Version.Requirement{}
) or atom() or binary() or float() or integer() or list(term())
```
Warnings are also emitted if you pass a data type that does not implement the `Enumerable` protocol as a generator to for-comprehensions:
```elixir
User.drive({:ok, %User{}}, car_choices)
defmodule Example do
def my_code(%Date{} = date) do
for(x <- date, do: x)
end
end
```
Here is the warning:
will emit:
```
warning: incompatible types given to User.drive/2:
warning: incompatible value given to for-comprehension:
User.drive({:ok, %User{age: nil, car_choice: nil}}, car_choices)
x <- date
it has type:
%Date{year: term(), month: term(), day: term(), calendar: term()}
but expected a type that implements the Enumerable protocol, it must be one of:
dynamic(
%Date.Range{} or %File.Stream{} or %GenEvent.Stream{} or %HashDict{} or %HashSet{} or
%IO.Stream{} or %MapSet{} or %Range{} or %Stream{}
) or fun() or list(term()) or non_struct_map()
```
### Type checking and inference of anonymous functions
Elixir v1.19 can now type infer and type check anonymous functions. Here is a trivial example:
```elixir
defmodule Example do
def run do
fun = fn %{} -> :map end
fun.("hello")
end
end
```
The example above has an obvious typing violation, as the anonymous function expects a map but a string is given. With Elixir v1.19, the following warning is now printed:
```
warning: incompatible types given on function application:
fun.("hello")
given types:
{:ok, %User{age: nil, car_choice: nil}}, empty_list()
binary()
but expected one of:
but function has type:
dynamic(%User{age: term(), car_choice: term()}), dynamic()
where "car_choices" was given the type:
# type: empty_list()
# from: lib/foo.ex:21:17
car_choices = []
(dynamic(map()) -> :map)
typing violation found at:
│
22 │ User.drive({:ok, %User{}}, car_choices)
│ ~
6 │ fun.("hello")
│ ~
│
└─ lib/foo.ex:22:10: Example.run/0
└─ mod.exs:6:8: Example.run/0
```
> The mismatched arguments are shown in red, if your terminal supports ANSI coloring.
Function captures, such as `&String.to_integer/1`, will also propagate the type as of Elixir v1.19, arising more opportunity for Elixir's type system to catch bugs in our programs.
And the next snippet will warn because the `:error` clause will never match, as that's not a valid return type of the `User.drive/2` call:
### Acknowledgements
The type system was made possible thanks to a partnership between [CNRS](https://www.cnrs.fr/) and [Remote](https://remote.com/). The development work is currently sponsored by [Fresha](https://www.fresha.com/), [Starfish*](https://starfish.team/), and [Dashbit](https://dashbit.co/).
## Faster compile times in large projects
This release includes two compiler improvements that can lead up to 4x faster builds in large codebases.
While Elixir has always compiled the given files in project or a dependency in parallel, the compiler would sometimes be unable to use all of the machine resources efficiently. This release addresses two common limitations, delivering performance improvements that scale with codebase size and available CPU cores.
### Code loading bottlenecks
Prior to this release, Elixir would load modules as soon as they were defined. However, because the Erlang part of code loading happens within a single process (the code server), this would make it a bottleneck, reducing parallelization, especially on large projects.
This release makes it so modules are loaded lazily. This reduces the pressure on the code server and the amount of work during compilation, with reports of more than two times faster compilation for large projects. The benefits depend on the codebase size and the number of CPU cores available.
Implementation wise, [the parallel compiler already acts as a mechanism to resolve modules during compilation](https://elixir-lang.org/blog/2012/04/24/a-peek-inside-elixir-s-parallel-compiler/), so we built on that. By making sure the compiler controls both module compilation and module loading, it can also better guarantee deterministic builds.
There are two potential regressions with this approach. The first one happens if you spawn processes during compilation which invoke other modules defined within the same project. For example:
```elixir
case User.drive(user, car_choices) do
{:ok, car} -> car
:error -> Logger.error("User cannot drive")
defmodule MyLib.SomeModule do
list = [...]
Task.async_stream(list, fn item ->
MyLib.SomeOtherModule.do_something(item)
end)
end
```
And here is the warning:
Because the spawned process is not visible to the compiler, it won't be able to load `MyLib.SomeOtherModule`. You have two options, either use `Kernel.ParallelCompiler.pmap/2` or explicitly call `Code.ensure_compiled!(MyLib.SomeOtherModule)` before spawning the process that uses said module.
```
warning: the following clause will never match:
:error
because it attempts to match on the result of:
User.drive(user, car_choices)
which has type:
dynamic({:ok, term()} or {:error, :no_choice} or {:error, :not_allowed})
typing violation found at:
│
26 │ :error -> Logger.error("User cannot drive")
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
│
└─ lib/foo.ex:26: Example.run/0
```
For more details on typing inference and the trade-offs made by the Elixir team, [see our official documentation](https://hexdocs.pm/elixir/1.18/gradual-set-theoretic-types.html#type-inference).
There are many other improvements to the type system, which we will go in detail within the official release. Meanwhile, here is a list summary of the overall improvements done to the type system:
* Type inference of patterns (typing inference of guards will be part of an upcoming release)
* Type checking of all language constructs, including local and remote calls, except `for`, `with`, and closures
* Type checking of all functions inlined by the compiler found in `Kernel`
* Type checking of all conversion functions inlined by the compiler
* [Support for tuples and lists as composite types](https://elixir-lang.org/blog/2024/08/28/typing-lists-and-tuples/) as well as type checking of their basic operations
* Detection of clauses and patterns that will never match from `case`, `cond`, and `=`
* Detection of unused clauses in private functions
## ExUnit improvements
ExUnit now supports parameterized tests to run the same test module multiple times under different parameters.
For example, Elixir ships a local, decentralized and scalable key-value process storage called `Registry`. The registry can be partitioned and its implementation differs depending if partitioning is enabled or not. Therefore, during tests, we want to ensure both modes are exercised. With Elixir v1.18, we can achieve this by writing:
The second one is related to `@on_load` callbacks (typically used for [NIFs](https://www.erlang.org/doc/system/nif.html)) that invoke other modules defined within the same project. For example:
```elixir
defmodule Registry.Test do
use ExUnit.Case,
async: true,
parameterize: [
%{partitions: 1},
%{partitions: 8}
]
defmodule MyLib.SomeModule do
@on_load :init
# ... the actual tests ...
def init do
MyLib.AnotherModule.do_something()
end
def something_else do
...
end
end
MyLib.SomeModule.something_else()
```
The reason this fails is because `@on_load` callbacks are invoked within the code server and therefore they have limited ability to load additional modules. It is generally advisable to limit invocation of external modules during `@on_load` callbacks but, in case it is strictly necessary, you can set `@compile {:autoload, true}` in the invoked module to address this issue in a forward and backwards compatible manner.
Both snippets above could actually lead to non-deterministic compilation failures in the past, and as a result of these changes, compiling these cases are now deterministic.
### Parallel compilation of dependencies
This release introduces a variable called `MIX_OS_DEPS_COMPILE_PARTITION_COUNT`, which instructs `mix deps.compile` to compile dependencies in parallel.
While fetching dependencies and compiling individual Elixir dependencies already happened in parallel, as outlined in the previous section, there were pathological cases where performance gains would be left on the table, such as when compiling dependencies with native code or dependencies where one or two large files would take most of the compilation time.
By setting `MIX_OS_DEPS_COMPILE_PARTITION_COUNT` to a number greater than 1, Mix will now compile multiple dependencies at the same time, using separate OS processes. Empirical testing shows that setting it to half of the number of cores on your machine is enough to maximize resource usage. The exact speed up will depend on the number of dependencies and the number of machine cores, although some reports mention up to 4x faster compilation times. If you plan to enable it on CI or build servers, keep in mind it will most likely have a direct impact on memory usage too.
## Improved pretty printing algorithm
Elixir v1.19 ships with a new pretty printing implementation that tracks limits as a whole, instead of per depth. Previous versions would track limits per depth. For example, if you had a list of lists of 4 elements and a limit of 5, it would be pretty printed as follows:
```elixir
[
[1, 2, 3],
[1, 2, ...],
[1, ...],
[...],
...
]
```
This allows for more information to be shown at different nesting levels, which is useful for complex data structures. But it led to some pathological cases where the `limit` option had little effect on filtering the amount of data shown. The new implementation decouples the limit handling from depth, decreasing it as it goes. Therefore, the list above with the same limit in Elixir v1.19 is now printed as:
```elixir
[
[1, 2, 3],
...
]
```
The outer list is the first element, the first nested list is the second, followed by three numbers, reaching the limit. This gives developers more precise control over pretty printing.
Given this may reduce the amount of data printed by default, the default limit has also been increased from 50 to 100. We may further increase it in upcoming releases based on community feedback.
## Erlang/OTP 28 support
Elixir v1.19 officially supports Erlang/OTP 28.1+ and later. In order to support the new Erlang/OTP 28 representation for regular expressions, structs can now control how they are escaped into abstract syntax trees by defining a `__escape__/1` callback.
On the other hand, the new representation for regular expressions implies they can no longer be used as default values for struct fields. Instead of this:
```elixir
defmodule Foo do
defstruct regex: ~r/foo/
end
```
ExUnit parameterizes whole test modules. If your modules are configured to run concurrently, as above, so will the parameterized ones.
ExUnit also comes with the ability of specifying test groups. While ExUnit supports running tests concurrently, those tests must not have shared state between them. However, in large applications, it may be common for some tests to depend on some shared state, and other tests to depend on a completely separate state. For example, part of your tests may depend on Cassandra, while others depend on Redis. Prior to Elixir v1.18, these tests could not run concurrently, but in v1.18 they might as long as they are assigned to different groups. Tests modules within the same group do not run concurrently, but across groups, they might.
With features like async tests, suite partitioning, and now grouping, Elixir developers have plenty of flexibility to make the most use of their machine resources, both in development and in CI.
## `mix format --migrate`
The `mix format` command now supports an explicit `--migrate` flag, which will convert constructs that have been deprecated in Elixir to their latest version. Because this flag rewrites the AST, it is not guaranteed the migrated format will always be valid when used in combination with macros that also perform AST rewriting.
As of this release, the following migrations are executed:
* Normalize parens in bitstring modifiers - it removes unnecessary parentheses in known bitstring modifiers, for example `<<foo::binary()>>` becomes `<<foo::binary>>`, or adds parentheses for custom modifiers, where `<<foo::custom_type>>` becomes `<<foo::custom_type()>>`.
* Charlists as sigils - formats charlists as `~c` sigils, for example `'foo'` becomes `~c"foo"`.
* `unless` as negated `if`s - rewrites `unless` expressions using `if` with a negated condition, for example `unless foo do` becomes `if !foo do`.
More migrations may be added in future releases.
## JSON support
This release includes official support for JSON encoding and decoding.
Both encoder and decoder fully conform to [RFC 8259](https://tools.ietf.org/html/rfc8259) and [ECMA 404](https://ecma-international.org/publications-and-standards/standards/ecma-404/) standards.
### Encoding
Encoding can be done via `JSON.encode!/1` and `JSON.encode_to_iodata!/1` functions. The default encoding rules are applied as follows:
| **Elixir** | **JSON** |
|-----------------------------|----------|
| `integer() \| float()` | Number |
| `true \| false ` | Boolean |
| `nil` | Null |
| `binary()` | String |
| `atom()` | String |
| `list()` | Array |
| `%{String.Chars.t() => _}` | Object |
You may also implement the `JSON.Encoder` protocol for custom data structures. Elixir already implements the protocol for all Calendar types.
If you have a struct, you can derive the implementation of the `JSON.Encoder` by specifying which fields should be encoded to JSON:
You must do this:
```elixir
@derive {JSON.Encoder, only: [...]}
defstruct ...
defmodule Foo do
defstruct [:regex]
def new do
%Foo{regex: ~r/foo/}
end
end
```
### Decoding
## OpenChain certification
Decoding can be done via `JSON.decode/2` and `JSON.decode!/2` functions. The default decoding rules are applied as follows:
Elixir v1.19 is also our first release following OpenChain compliance, [as previously announced](https://elixir-lang.org/blog/2025/02/26/elixir-openchain-certification/). In a nutshell:
| **JSON** | **Elixir** |
|----------|------------------------|
| Number | `integer() \| float()` |
| Boolean | `true \| false` |
| Null | `nil` |
| String | `binary()` |
| Object | `%{binary() => _}` |
* Elixir releases now include a Source SBoM in CycloneDX 1.6 or later and SPDX 2.3 or later formats.
* Each release is attested along with the Source SBoM.
## Language server listeners
These additions offer greater transparency into the components and licenses of each release, supporting more rigorous supply chain requirements.
4 months ago, we welcomed [the Official Language Server team](https://elixir-lang.org/blog/2024/08/15/welcome-elixir-language-server-team/), with the goal of unifying the efforts behind code intelligence, tools, and editors in Elixir. Elixir v1.18 brings new features on this front by introducing locks and listeners to its compilation. Let's understand what it means.
This work was performed by [Jonatan Männchen](https://maennchen.dev) and sponsored by the [Erlang Ecosystem Foundation](https://erlef.org).
At the moment, all language server implementations have their own compilation environment. This means that your project and dependencies during development are compiled once, for your own use, and then again for the language server. This duplicate effort could cause the language server experience to lag, when it could be relying on the already compiled artifacts of your project.
This release address by introducing a compiler lock, ensuring that only a single operating system process running Elixir compiles your project at a given moment, and by providing the ability for one operating system process to listen to the compilation results of others. In other words, different Elixir instances can now communicate over the same compilation build, instead of racing each other.
These enhancements do not only improve editor tooling, but they also directly benefit projects like IEx and Phoenix. For example, you can invoke `IEx.configure(auto_reload: true)` and IEx will automatically reload modules changed elsewhere, either by a separate terminal or your IDE.
## Potential incompatibilities
This release no longer supports WERL (a graphical user interface on Windows used by Erlang 25 and earlier). For a better user experience on Windows terminals, use Erlang/OTP 26+ (this is also the last Elixir release to support Erlang/OTP 25).
Furthermore, in order to support inference of patterns, Elixir will raise if it finds recursive variable definitions. This means patterns that never match, such as this one, will no longer compile:
def foo(x = {:ok, y}, x = y)
However, recursion of root variables (where variables directly point to each other), will also fail to compile:
def foo(x = y, y = z, z = x)
While the definition above could succeed (as long as all three arguments are equal), the cycle is not necessary and could be removed, as below:
def foo(x = y, y = z, z)
You may also prefer to write using guards:
def foo(x, y, z) when x == y and y == z
## v1.18.4 (2025-05-21)
This release includes initial support for Erlang/OTP 28, for those who want to try it out. In such cases, you may use Elixir v1.18.4 precompiled for Erlang/OTP 27, as it is binary compatible with Erlang/OTP 28.
### 1. Enhancements
#### IEx
* [IEx.Helpers] Add `IEx.Helpers.process_info/1` which prints process information
#### Mix
* [mix compile] Support the `--no-listeners` option
* [mix local] Retry HTTP requests with disabled middlebox comp mode depending on the failure reason
* [mix local.hex] Install Hex per OTP release
* [mix local.rebar] Install Hex per OTP release
* [mix run] Support the `--no-listeners` option
### 2. Bug fixes
#### Elixir
* [Kernel] Emit trace events for `@on_definition` callbacks
* [Kernel] Emit trace events for `@on_load` callbacks
* [Kernel] Emit trace events for `super` calls
* [Kernel] Emit trace events for imported function calls
* [Kernel] Optimize map unions to avoid building long lists
* [Kernel] Do not crash when type checking nested bitstrings in patterns
* [Kernel] Do not crash when non-binary bitstring is given as struct default value
* [Kernel] Recompile regexes when escaped from module attributes for Erlang/OTP 28 compatibility
* [Kernel] Preserve backwards compatibility in `elixir_erl`
#### Mix
* [mix deps.get] Ensure git checkout works when there are untracked files in the dependency
* [mix loadpaths] Do not run listeners when not checking the deps
## v1.18.3 (2025-03-06)
## v1.19.5 (2026-01-09)
### 1. Enhancements
#### Elixir
* [JSON] Encode any JSON key to string
* [Kernel] Allow `<<_::3*8>>` in typespecs
#### Mix
* [mix loadpaths] Support `--no-listeners` option
* [Protocol] Optimize protocol consolidation to no longer load structs
### 2. Bug fixes
#### Elixir
* [CLI] Fix `--no-color` not setting `:ansi_enabled` to false
* [Protocol] Return correct implementation for an invalid struct pointing to `nil`
* [Stream] Do not raise when `Stream.cycle/1` is explicitly halted
#### ExUnit
* [ExUnit.Diff] Fix regression when diffing nested improper lists
* [Kernel] Fix unnecessary recompilation when `dbg_callback` is modified at runtime
* [Kernel] Fix parser crash on missing parentheses on expression following operator `not in`
* [Kernel] Support fetching abstract code for modules compiled with Elixir v1.14 and earlier
* [Protocol] Ensure protocol consolidation no longer stores outdated struct types. As a consequence, protocols types only track struct names at the moment
* [Stream] Revert optimization which caused nested streams in `Stream.flat_map/2` to crash
#### IEx
* [IEx.Autocomplete] Fix autocomplete crash when expanding struct with `__MODULE__`
* [IEx.Helpers] Do not purge on `recompile` if IEx is not running
* [IEx] Fix usage of `#iex:break` as part of multi-line prompts
## v1.18.2 (2025-01-22)
#### Logger
* [Logger.Backends] Do not crash on invalid metadata
## v1.19.4 (2025-11-27)
### 1. Enhancements
#### Mix
* [mix xref] Add `--min-cycle-label` to help projects adapt to the more precise `mix xref graph` reports in Elixir v1.19. In previous versions, Elixir would break a large compilation cycle into several smaller ones, and therefore developers would check for `--min-cycle-size` on CI. However, the issue is not the size of the cycle (it has no implication in the amount of compiled files), but how many compile-time dependencies (aka compile labels) in a cycle. The new option allows developers to filter on the label parameter
### 2. Bug fixes
#### Elixir
* [File] Ensure `File.cp_r/3` reports non-existing destination properly (instead of source)
#### ExUnit
* [ExUnit] Fix formatter crash when diffing takes too long
* [ExUnit] Ensure parallel matches in `assert` propagate type information
#### Logger
* [Logger] Fix regression where formatter would crash when given chardata (the crash would happen when logging non-ASCII characters)
#### Mix
* [mix help] Ensure `app:APP` works when the project or its dependencies were not yet compiled
* [mix escript.build] Ensure the `hex` application can be included in escripts
## v1.19.3 (2025-11-13)
### 1. Enhancements
#### Elixir
* [CLI] Add `--color`/`--no-color` for enabling and disabling of ANSI colors
* [Code.Fragment] Provide more AST context when invoking `container_cursor_to_quoted` with trailing fragments
* [Regex] Ensure compatibility with Erlang/OTP 28+ new Regex engine
* [Kernel] Support /E modifier for regular expressions in config files
#### Mix
* [mix] Print compilation lock waiting message to stderr
* [mix] Add an environment variable to optionally disable compilation locking
* [mix compile] Allow forcing specific compilers, such as `--force-elixir`, `--force-app`, etc
* [mix help app:APP] Support showing helps for apps in Elixir and Erlang standard libraries
### 2. Bug fixes
#### Elixir
* [CLI] Temporarily remove PowerShell scripts for `elixir`, `elixirc`, and `mix` on Windows, as they leave the shell broken after quitting Erlang
* [IO] Fix dialyzer warning on `IO.inspect :label`
#### ExUnit
* [ExUnit] Fix crash when diffing bitstring specifiers
#### IEx
* [IEx.Autocomplete] Fix crashing when autocompleting structs with runtime values
* [ExUnit.Case] Fix crash when formatting errors caused by a linked/trapped exit during `setup_all`
#### Mix
* [mix] Track compilation locks per user to avoid permission errors
* [mix deps.update] Ensure Git dependencies can be upgraded by doing so against the origin
* [mix compile.app] Ensure functions in the format `&Mod.fun/arity` can be written to .app files
* [mix compile.app] Ensure strings with Unicode characters can be written to .app files
## v1.18.1 (2024-12-24)
### 1. Enhancements
* [Kernel] Do not emit type violation warnings when comparing or matching against literals
* [Kernel] Do not validate clauses of private overridable functions
### 2. Bug fixes
#### Elixir
* [Code.Fragment] Ensure `Code.Fragment.container_cursor_to_quoted/2` with `:trailing_fragment` parses expressions that were supported in previous versions
* [Kernel] Do not crash when typing violation is detected on dynamic dispatch
* [Kernel] Properly annotate the source for warnings emitted by the compiler with the `@file` annotation
* [Kernel] Properly annotate the source for warnings emitted by the type system with the `@file` annotation
* [Kernel] Remove `:no_parens` metadata when using capture with arity on all cases
* [Kernel] Ensure diagnostic traces are kept backwards compatible
#### ExUnit
* [ExUnit.Case] Ensure async groups do not run concurrenly while the test suite is still loading
* [ExUnit.Case] Ensure `--repeat-until-failure` can be combined with groups
#### Mix
* [mix compile.elixir] Store compilation results if compilation fails due to `--warnings-as-errors`
* [mix deps.loadpaths] Add build lock
* [mix escript.build] Ensure build succeeds when protocol consolidation is disabled
* [Mix.Shell] Ensure encoding is properly respected on Windows and Unix systems
## v1.18.0 (2024-12-19)
## v1.19.2 (2025-11-02)
### 1. Enhancements
#### Elixir
* [CLI] Add experimental PowerShell scripts for `elixir`, `elixirc`, and `mix` on Windows. Those provide a safer entry point for running Elixir from other platforms
* [Calendar] Add `Duration.to_string/1`
* [Code] Support several migration options in `Code.format_string!/2`
* [Code] Add parenthesis around `--` and `---` in `Code.format_string!/2` to make precedence clearer
* [Code] Include more metadata in `Code.string_to_quoted/2` when `token_metadata: true` to help compute ranges from the AST
* [Code.Fragment] Have `:capture_arg` as its own entry in `Code.Fragment.surround_context/2`
* [Config] Add `Config.read_config/1`
* [Enumerable] Add `Enum.product_by/2` and `Enum.sum_by/2`
* [Exception] Add `MissingApplicationsError` exception to denote missing applications
* [JSON] Add a new `JSON` module with encoding and decoding functionality
* [JSON] Implement `JSON.Encoder` for all Calendar types
* [Kernel] Update source code parsing to match [UTS #55](https://www.unicode.org/reports/tr55/) latest recommendations. In particular, mixed script is allowed in identifiers as long as they are separate by underscores (`_`), such as `http_сервер`. Previously allowed highly restrictive identifiers, which mixed Latin and other scripts, such as the japanese word for t-shirt, `Tシャツ`, now require the underscore as well
* [Kernel] Warn on bidirectional confusability in identifiers
* [Kernel] Verify the type of the binary generators
* [Kernel] Track the type of tuples in patterns and inside `elem/2`
* [Kernel] Perform validation of root AST nodes in `unquote` and `unquote_splicing` to catch bugs earlier
* [Kernel] Add source, behaviour, and record information to Docs chunk metadata
* [Kernel] Support deterministic builds in tandem with Erlang by setting `ERL_COMPILER_OPTIONS=deterministic`. Keep in mind deterministic builds strip source and other compile time information, which may be relevant for programs
* [Kernel] Allow aliases and imports to be enabled conditionally in module body
* [List] Add `List.ends_with?/2`
* [Macro] Improve `dbg` handling of `if/2`, `with/1` and of code blocks
* [Macro] Add `Macro.struct_info!/2` to return struct information mirroring `mod.__info__(:struct)`
* [Registry] Add `Registry.lock/3` for local locking
* [PartitionSupervisor] Add `PartitionSupervisor.resize!/2` to resize the number of partitions in a supervisor (up to the limit it was started with)
* [Process] Handle arbitrarily high integer values in `Process.sleep/1`
* [Protocol] Add `@undefined_impl_description` to customize error message when an implementation is undefined
* [Protocol] Add `__deriving__/1` as optional macro callback to `Protocol`, no longer requiring empty implementations
* [String] Inspect special whitespace and zero-width characters using their Unicode representation
* [String] Update Unicode to 16.0
#### ExUnit
* [ExUnit] Support parameterized tests on `ExUnit.Case`
* [ExUnit] Support test groups: tests in the same group never run concurrently
* [ExUnit.Case] Add `test_pid` as a tag
#### IEx
* [IEx] Add `IEx.configure(auto_reload: true)` to automatically pick up modules recompiled from other operating system processes
* [IEx] Add `:dot_iex` support to `IEx.configure/1`
* [IEx] Add report for normal/shutdown exits in IEx
* [Kernel] Measure and optimize writing of `.beam` files in the compiler
* [Kernel] Optimize rare scenarios where type checking took too long
#### Mix
* [mix compile] Ensure only a single operating system process can compile at a given time
* [mix deps.get] Ensure only a single operating system process can fetch deps at a given time
* [mix format] Add `mix format --migrate` to migrate from deprecated functionality
* [mix format] Add new options and metadata to improve formatting applying by editors and other environments
* [mix test] Taint failure manifest if requiring or compiling tests fail
* [Mix.Project] Add a `:listeners` configuration to listen to compilation events from the current and other operating system processes
* [Mix.Task.Compiler] Add API for fetching all persisted compiler diagnostics
* [Mix.Task.Compiler] Add API for fetching all compiler tasks
* [mix compile] Add flag `--no-check-cwd` to skip compiler check to aid debugging
### 2. Bug fixes
#### Elixir
* [Code] Fix delimiter metadata for single quoted atoms and remote calls in `Code.string_to_quoted/2`
* [Code.Formatter] Fix formatter adding extra escapes to quoted remote calls
* [Code.Fragment] Properly handle keyword keys as their own entry
* [Inspect.Algebra] Ensure `next_break_fits` respects `line_length`
* [Kernel] Validate AST on `unquote` and `unquote_splicing` to provide better error reports instead of failing too late inside the compiler
* [Kernel] Avoid crashes when emitting diagnostics on code using \t for indentation
* [Module] Include module attribute line and name when tracing its aliases
* [Stream] Do not halt streams twice in `Stream.transform/5`
* [URI] Fix a bug when a schemaless URI is given to `URI.merge/2`
* [IO] Fix dialyzer warning on `IO.inspect :label`
* [Kernel] Ensure we warn on deprecated `~~~` unary operator
#### ExUnit
#### Logger
* [ExUnit.Assertions] Raise if guards are used in `assert/1` with `=`
* [ExUnit.Assertions] Format inserted/deleted maps in list assertions
#### IEx
* [IEx.Helpers] `IEx.Helpers.recompile/0` will reload modules changed by other operating system processes
* [Logger] Reset ansi escapes before newlines in Logger
#### Mix
* [mix compile] Ensure warnings from external resources are emitted with `--all-warnings` when files do not change
* [mix deps.compile] Fix escaping issues when invoking `rebar3` in some cases
* [mix escript] Fix escript layout and support storing `priv` directories
* [mix release] Make `.app` files deterministic in releases
* [Mix.Shell] Fix `Mix.Shell` on Windows when outputting non UTF-8 characters
* [mix compile] Warn if `elixirc_paths` is not a list of string paths
* [mix compile] Address regression where umbrella children were compiled too early and without respecting compilation flags
* [mix deps.compile] Improve reliability of `MIX_OS_DEPS_COMPILE_PARTITION_COUNT` across `mix escript.install`, `mix archive.install`, and others
## v1.19.1 (2025-10-20)
### 1. Bug fixes
#### EEx
* [EEx] Address Dialyzer warnings when invoking `EEx.compile_string`
#### Elixir
* [Kernel] Optimize how types are computed for pretty printing
* [Kernel] Optimize how differences are computed in the type system
* [Macro] Do not escape options given to `dbg/2`
* [Protocol] Improve protocol violation warnings
#### Mix
* [mix compile] Do not attempt to touch deleted files when compilation fails and then resumed with missing files
* [mix deps.compile] Do not spawn partitions when all dependencies are local and already compiled
## v1.19.0 (2025-10-16)
### 1. Enhancements
#### Elixir
* [Access] Add `Access.values/0` for traversing maps and keyword lists values
* [Base] Add functions to verify if an encoding is valid, such as `valid16?`, `valid64?`, and so forth
* [Calendar] Support 2-arity options for `Calendar.strftime/3` which receives the whole data type
* [Code] Add `:migrate_call_parens_on_pipe` formatter option
* [Code] Add `:indentation` option to `Code.string_to_quoted/2`
* [Code.Fragment] Preserve more block content around cursor in `container_cursor_to_quoted`
* [Code.Fragment] Add `:block_keyword_or_binary_operator` to `Code.Fragment` for more precise suggestions after operators and closing terminators
* [Code.Fragment] Add `Code.Fragment.lines/1`
* [Enum] Provide more information on `Enum.OutOfBoundsError`
* [Inspect] Allow `optional: :all` when deriving Inspect
* [Inspect.Algebra] Add optimistic/pessimistic groups as a simplified implementation of `next_break_fits`
* [IO.ANSI] Add ANSI codes to turn off conceal and crossed_out
* [Kernel] Raise when U+2028 and U+2029 characters are present in comments and strings to avoid line spoofing attacks
* [Kernel] Include the line for the previous clause in errors/warnings related to conflicts between defaults on function definitions
* [Kernel] Support `min/2` and `max/2` as guards
* [Kernel.ParallelCompiler] Add `each_long_verification_threshold` which invokes a callback when type checking a module takes too long
* [Kernel.ParallelCompiler] Include lines in `== Compilation error in file ... ==` slogans
* [Macro] Print debugging results from `Macro.dbg/3` as they happen, instead of once at the end
* [Macro] Add `__escape__/1` callback so structs can escape references and other runtime data types in `Macro.escape/1`
* [Module] Do not automatically load modules after their compilation, guaranteeing a more consistent compile time experience and drastically improving compilation times
* [OptionParser] Support the `:regex` type
* [OptionParser] Enhance parsing error to display available options
* [Protocol] Type checking of protocols dispatch and implementations
* [Regex] Add `Regex.to_embed/2` which returns an embeddable representation of regex in another regex
* [Regex] Raise error message when regexes are used as default values in struct fields for compatibility with Erlang/OTP 28
* [Registry] Add key-based partitioning of duplicate registries
* [String] Add `String.count/2` to count occurrences of a pattern
* [String] Update to Unicode 17.0.0
#### ExUnit
* [ExUnit] Set a process label for each test
* [ExUnit.CaptureLog] Parallelize log dispatch when multiple processes are capturing log
* [ExUnit.Case] Add `:test_group` to the test context
* [ExUnit.Doctest] Support ellipsis in doctest exceptions to match the remaining of the exception
* [ExUnit.Doctest] Add `:inspect_opts` option for doctest
#### IEx
* [IEx] Support multi-line prompts (due to this feature, `:continuation_prompt` and `:alive_continuation_prompt` are no longer supported as IEx configuration)
* [IEx.Autocomplete] Functions annotated with `@doc group: "Name"` metadata will appear within their own groups in autocompletion
#### Logger
* [Logger] Accept any enumerable in `Logger.metadata/1`
#### Mix
* [mix] Add support for `MIX_PROFILE_FLAGS` to configure `MIX_PROFILE`
* [mix compile] Debug the compiler and type checker PID when `MIX_DEBUG=1` and compilation/verification thresholds are met
* [mix compile] Add `Mix.Tasks.Compiler.reenable/1`
* [mix deps.compile] Support `MIX_OS_DEPS_COMPILE_PARTITION_COUNT` for compiling deps concurrently across multiple operating system processes
* [mix help] Add `mix help Mod`, `mix help :mod`, `mix help Mod.fun`, `mix help Mod.fun/arity`, and `mix help app:package`
* [mix format] Add options to mix format to allow excluding of files
* [mix test] Add `--name-pattern` option to `mix test`
* [mix test] Allow to distinguish the exit status between warnings as errors and test failures
* [mix xref graph] Add support for `--format json`
* [mix xref graph] Emit a warning if `--source` is part of a cycle
* [Mix] Support the `:compilers` option
* [Mix.Task.Compiler] Add `Mix.Task.Compiler.run/2`
### 2. Bug fixes
#### Elixir
* [Code] Return error on invalid unicode sequences in `Code.string_to_quoted/2` instead of raising
* [Code] Properly handle column annotation for `in` in `not in` expressions
* [DateTime] Do not truncate microseconds regardless of precision in `DateTime.diff/3`
* [Enum] Fix infinite loop on `Enum.take/2` with negative index on empty enumerable
* [File] Properly handle permissions errors cascading from parent in `File.mkdir_p/1`
* [Inspect] Inspect ill-formed structs as maps
* [Kernel] Properly increment metadata newline when `?` is followed by a literal newline character
* [Kernel] `not_a_map.key` now raises `BadMapError` for consistency with other map operations
* [Protocol] `defstruct/1` and `defexception/1` are now disabled inside `defprotocol` as to not allow defining structs/exceptions alongside a protocol
* [Regex] Fix `Regex.split/2` returning too many results when the chunk being split on was empty (which can happen when using features such as `/K`)
* [Stream] Ensure `Stream.transform/5` respects suspend command when its inner stream halts
* [URI] Several fixes to `URI.merge/2` related to trailing slashes, trailing dots, and hostless base URIs
#### ExUnit
* [ExUnit.Assertions] Fix order of pinned variables in failure reports
* [ExUnit.Assertions] Raise if attempting to raise an assertion error with invalid message (not a binary)
* [ExUnit.Case] Do not crash on empty test unit groups
#### IEx
* [IEx] Abort pipelines when there is an error in any step along the way
#### Mix
* [mix cmd] Preserve argument quoting in subcommands by no longer performing shell expansion. To revert to the previous behaviour, pass `--shell` before the command name
* [mix compile] Fix bug where reverting changes to an external resource (such as HEEx template) after a compilation error would make it so the source module would not be compiled
* [mix compile] Avoid failures when locking compilation across different users
* [mix compile] Fix race condition when renaming files used by the compilation lock
* [mix format] Ensure the formatter does not go over the specified limit in certain corner cases
* [mix release] Fix `RELEASE_SYS_CONFIG` for Windows 11
* [mix test] Ensure modules are preloaded in `mix test --slowest-modules=N`
* [mix xref graph] Provide more consistent output by considering strong connected components when computing graphs. This means that, if you were using `mix xref graph --format cycles`, you will now get fewer but larger cycles, as cycle between `A` and `B` and another between `A` and `C` is now considered a single cycle between `A`, `B`, and `C`. Note the cycles themselves are not problematic, unless they have compile-time dependencies in them
### 3. Soft deprecations (no warnings emitted)
#### Elixir
* [Inspect.Algebra] `color/3` is deprecated in favor of `color_doc/3`
* [Inspect.Algebra] `fold_doc/2` is deprecated in favor of `fold/2`
* [Kernel] Deprecate `unless` in favor of `if`. Use `mix format --migrate` to automate the migration
* [Macro] `Macro.struct!/2` is deprecated in favor of `Macro.struct_info!/2`
* [Protocol] Defining `__deriving__/3` inside the `Any` implementation is deprecated, derive it inside the protocol definition itself
### 4. Hard deprecations
#### EEx
* [EEx] `<%#` is deprecated in favor of `<%!--` or `<% #`
* [EEx] `c:EEx.handle_text/2` is deprecated in favor of `c:EEx.handle_text/3`
#### Elixir
* [Code] Setting `:warnings_as_errors` is deprecated via `Code.put_compiler_option/2`. This must not affect developers, as the `:warnings_as_errors` option is managed by Mix tasks, and not directly used via the `Code` module
* [Enumerable] Deprecate returning a two-arity function in `Enumerable.slice/1`
* [List] `List.zip/1` is deprecated in favor of `Enum.zip/1`
* [Module] Deprecate `Module.eval_quoted/3` in favor of `Code.eval_quoted/3`
* [Range] Deprecate inferring negative ranges on `Range.new/2`
* [Tuple] `Tuple.append/2` is deprecated, use `Tuple.insert_at/3` instead
* [Inspect.Algebra] `next_break_fits` is deprecated in favor of `optimistic`/`pessimistic` groups
* [Node] `Node.start/2-3` is deprecated in favor of `Node.start/2` with a keyword list
#### Mix
* [mix cmd] Deprecate `mix cmd --app APP` in favor of `mix do --app APP`
* [mix compile] `:warnings_as_errors` configuration in `:elixirc_options` is deprecated. Instead pass the `--warnings-as-errors` flag to `mix compile`. Alternatively, you might alias the task: `aliases: [compile: "compile --warnings-as-errors"]`
* [mix test] `:warnings_as_errors` configuration in `:test_elixirc_options` is deprecated. Instead pass the `--warnings-as-errors` flag to `mix test`. Alternatively, you might alias the task: `aliases: [test: "test --warnings-as-errors"]`
* [Mix.Tasks.Compile] Deprecate `compilers/0` in favor of `Mix.Task.Compiler.compilers/0`
* [mix compile] `--no-protocol-consolidation` is deprecated in favor of `--no-consolidate-protocols` for consistency with `mix.exs` configuration
* [mix compile.protocols] Protocol consolidation is now part of `compile.elixir` and the task itself has no effect
## v1.17
### 4. Hard deprecations
The CHANGELOG for v1.17 releases can be found [in the v1.17 branch](https://github.com/elixir-lang/elixir/blob/v1.17/CHANGELOG.md).
#### Elixir
* [Code] Warn if line-break characters outside of `\r` and `\r\n` are found in strings according to UX#55. This warning will be fast-tracked into an error for security reasons in Elixir v1.20, following a similar rule to bidirectional control characters. They will already raise if found in comments
* [Code] The `on_undefined_variable: :warn` is deprecated. Relying on undefined variables becoming function calls will not be supported in the future
* [File] Passing a callback as third argument to `File.cp/3` is deprecated, pass it as a `on_conflict: callback` option instead
* [File] Passing a callback as third argument to `File.cp_r/3` is deprecated, pass it as a `on_conflict: callback` option instead
* [Kernel] The struct update syntax, such as `%URI{uri | path: "/foo/bar"}`, now requires the given variable (or expression) to explicitly pattern match on the struct before it can be updated. This is because, thanks to the type system, pattern matching on structs can find more errors, more reliably, and we want to promote its usage. Once pattern matching is added, you may optionally convert the struct update syntax into the map update syntax `%{uri | path: "/foo/bar"}` with no less of typing guarantees
* [Kernel.ParallelCompiler] Passing `return_diagnostics: true` as an option is required on `compile`, `compile_to_path` and `require`
#### Logger
* [Logger] The `:backends` configuration is deprecated, either set the `:default_handler` to false or start backends in your application start callback
#### Mix
* [mix] The `:default_task`, `:preferred_cli_env`, and `:preferred_cli_target` configuration inside `def project` in your `mix.exs` has been deprecated in favor of `:default_task`, `:preferred_envs` and `:preferred_targets` inside the `def cli` function
* [mix do] Using commas as task separator in `mix do` (such as `mix do foo, bar`) is deprecated, use `+` instead (as in `mix do foo + bar`)
## v1.18
The CHANGELOG for v1.18 releases can be found [in the v1.18 branch](https://github.com/elixir-lang/elixir/blob/v1.18/CHANGELOG.md).
+6
View File
@@ -1,3 +1,9 @@
<!--
SPDX-License-Identifier: Apache-2.0
SPDX-FileCopyrightText: 2021 The Elixir Team
SPDX-FileCopyrightText: 2012 Plataformatec
-->
# Code of Conduct
Contact: elixir-lang-conduct@googlegroups.com
+197
View File
@@ -0,0 +1,197 @@
<!--
SPDX-License-Identifier: Apache-2.0
SPDX-FileCopyrightText: 2021 The Elixir Team
SPDX-FileCopyrightText: 2012 Plataformatec
-->
# Contributing to Elixir
We invite contributions to Elixir. To contribute, there are a few
things you need to know about the code. First, Elixir code is divided
by each application inside the `lib` folder:
* `elixir` - Elixir's kernel and standard library
* `eex` - EEx is the template engine that allows you to embed Elixir
* `ex_unit` - ExUnit is a simple test framework that ships with Elixir
* `iex` - IEx stands for Interactive Elixir: Elixir's interactive shell
* `logger` - Logger is the built-in logger
* `mix` - Mix is Elixir's build tool
You can run all tests in the root directory with `make test`. You can
also run tests for a specific framework with `make test_#{APPLICATION}`, for example,
`make test_ex_unit`. If you just changed something in Elixir's standard
library, you can run only that portion through `make test_stdlib`.
If you are only changing one file, you can choose to compile and run tests
for that specific file for faster development cycles. For example, if you
are changing the String module, you can compile it and run its tests as:
```sh
bin/elixirc lib/elixir/lib/string.ex -o lib/elixir/ebin
bin/elixir lib/elixir/test/elixir/string_test.exs
```
Some test files need their `test_helper.exs` to be explicitly required
before, such as:
```sh
bin/elixir -r lib/logger/test/test_helper.exs lib/logger/test/logger_test.exs
```
You can also use the `LINE` env var to run a single test:
```sh
LINE=123 bin/elixir lib/elixir/test/elixir/string_test.exs
````
To recompile all (including Erlang modules):
```sh
make compile
```
After your changes are done, please remember to run `make format` to guarantee
all files are properly formatted, then run the full suite with
`make test`.
If your contribution fails during the bootstrapping of the language,
you can rebuild the language from scratch with:
```sh
make clean_elixir compile
```
Similarly, if you can not get Elixir to compile or the tests to pass after
updating an existing checkout, run `make clean compile`. You can check
[the official build status](https://github.com/elixir-lang/elixir/actions/workflows/ci.yml).
More tasks can be found by reading the [Makefile](Makefile).
We encourage contributors to write tests that capture both existing and newly
introduced behavior, especially for bug fixes and major changes:
* **Bug Fixes:** If you are fixing a bug, please try to include a test that
*fails* before your change and *passes* afterward. This makes it easier to
confirm that the fix addresses the underlying issue and helps prevent
regressions in the future.
* **New Features or Major Changes:** If you are adding a new feature or making
major changes to existing functionality, please add tests that cover the
major parts of that functionality. Aim to have the best code coverage possible.
With tests running and passing, you are ready to contribute to Elixir and
[send a pull request](https://help.github.com/articles/using-pull-requests/).
We have saved some excellent pull requests we have received in the past in
case you are looking for some examples:
* [Implement Enum.member? - Pull request](https://github.com/elixir-lang/elixir/pull/992)
* [Add String.valid? - Pull request](https://github.com/elixir-lang/elixir/pull/1058)
* [Implement capture_io for ExUnit - Pull request](https://github.com/elixir-lang/elixir/pull/1059)
## Reviewing changes
Once a pull request is sent, the Elixir team will review your changes.
We outline our process below to clarify the roles of everyone involved.
All pull requests must be approved by two committers before being merged into
the repository. If changes are necessary, the team will leave appropriate
comments requesting changes to the code. Unfortunately, we cannot guarantee a
pull request will be merged, even when modifications are requested, as the Elixir
team will re-evaluate the contribution as it changes.
Committers may also push style changes directly to your branch. If you would
rather manage all changes yourself, you can disable the "Allow edits from maintainers"
feature when submitting your pull request.
The Elixir team may optionally assign someone to review a pull request.
If someone is assigned, they must explicitly approve the code before
another team member can merge it.
When the review finishes, your pull request will be squashed and merged
into the repository. If you have carefully organized your commits and
believe they should be merged without squashing, please mention it in
a comment.
## Licensing and Compliance Requirements
Please review our [Open Source Policy](OPEN_SOURCE_POLICY.md) for complete
guidelines on licensing and compliance. Below is a summary of the key points
affecting **all external contributors**:
- Accepted Licenses: Any code contributed must be licensed under the
`Apache-2.0` license.
- SPDX License Headers: With the exception of approved test fixture files,
all new or modified files in a pull request must include correct SPDX
headers. If you are creating a new file under the `Apache-2.0` license, for
instance, please use:
```elixir
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
```
- No Executable Binaries: Contributions must **not** include any executable
binary files. If you require an exception (for example, certain test artifacts),
please see the policy on how to request approval and document exceptions.
- Preserving Copyright and License Info: If you copy code from elsewhere,
ensure that **all original copyright and license notices remain intact**. If
they are missing or incomplete, you must add them.
- Failure to Comply: Pull requests that do not meet these licensing and
compliance standards will be rejected or require modifications before merging.
- Developer Certificate of Origin: All contributions are subject to the
Developer Certificate of Origin.
```
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the
best of my knowledge, is covered under an appropriate open
source license and I have the right under that license to
submit that work with modifications, whether created in whole
or in part by me, under the same open source license (unless
I am permitted to submit under a different license), as
Indicated in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including
all personal information I submit with it, including my
sign-off) is maintained indefinitely and may be redistributed
consistent with this project or the open source license(s)
involved.
```
See http://developercertificate.org/ for a copy of the Developer Certificate
of Origin license.
## Building documentation
Building the documentation requires that [ExDoc](https://github.com/elixir-lang/ex_doc)
is 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/elixir ../elixir/bin/mix do deps.get + compile
```
Now go back to Elixir's root directory and run:
```sh
make docs # to generate HTML pages
make docs DOCS_FORMAT=epub # to generate EPUB documents
```
This will produce documentation sets for `elixir`, `eex`, `ex_unit`, `iex`, `logger`,
and `mix` under the `doc` directory. If you are planning to contribute documentation,
[please check our best practices for writing documentation](https://hexdocs.pm/elixir/writing-documentation.html).
+73
View File
@@ -0,0 +1,73 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,98 @@
ELIXIR TEAM TRADEMARKS POLICY
This document outlines the policy for allowed usage of the “Elixir” word and the
Elixir logo by other parties.
“Elixir” and the Elixir logo are registered trademarks of the Elixir Team. The
Elixir Team believes in a decentralized approach to growing the community and
the ecosystem, independent of the Elixir project and the Elixir Team.
Anyone can use the Elixir trademarks if that use of the trademark is nominative.
The trademarks must not be used to disparage the project and its community, nor
be used in any way to imply ownership, endorsement, or association with the
Elixir project and the Elixir Team.
You must not visually combine the Elixir logo with any other images, or change
the logo in any way other than ways required by printing restrictions. If you
want to create your own visual identity in relation to Elixir, you might use the
shape of an unrelated “water drop” as part of your design, as seen in many
community projects and initiatives. You must not combine or modify the Elixir
logo.
The Elixir logo is available in our repository in both vertical and horizontal
versions.
Nominative use
The “nominative use” (or “nominative fair use”) is a legal doctrine that
authorizes everyone (even commercial companies) to use or refer to the trademark
of another if:
The product or service in question must be one not readily identifiable without
use of the trademark.
Only so much of the mark or marks may be used as is reasonably necessary to
identify the product or service.
The organization using the mark must do nothing that would, in conjunction with
the mark, suggest sponsorship or endorsement by the trademark holder.
Our trademarks must be used to refer to the Elixir programming language.
Examples of permitted use
All examples listed next must strictly adhere to the terms outlined in the
previous sections:
Usage of the Elixir logo to say a technology is “powered by Elixir” under
nominative use. Linking back to the Elixir website, if possible, is appreciated.
Usage of the Elixir logo to display it as a supported technology in a service or
platform. For instance, you may say “we support Elixir” and use the Elixir logo,
but you may not refer to yourself as “the Elixir platform” nor imply any form of
endorsement or association with Elixir.
Usage of the Elixir logo in non-commercial community meetups, in presentations,
and in courses when referring to the language and its ecosystem under nominative
use.
Usage of the Elixir logo in non-commercial swag (stickers, t-shirts, mugs, etc)
to promote the Elixir programming language. The Elixir marks must be the only
marks featured in the product. You need permission to make swag that include
Elixir and other third party marks in them.
Inclusion of the Elixir logo in non-commercial icon sets. Use of the Elixir
icons must still adhere to Elixir’s trademark policies.
Usage of the “Elixir” word in book titles, meetups, conferences, and podcasts.
You must not use the word to imply uniqueness or endorsement from the Elixir
team. “The Elixir book” and “The Elixir podcast” are not permitted.
“Elixir in Action”, “Thinking Elixir”, and “Kraków Elixir User Group” are valid
examples already in use today.
Usage of the “Elixir” word in the names of freely distributed software and
hardware products is allowed when referring to use with or suitability for the
Elixir programming language, such as wxElixir, Elixirsense, etc. If the product
includes the Elixir programming language itself, then you must also respect its
license.
Examples of not permitted use
Here is a non-exhaustive list of non permitted uses of the marks:
Usage of the Elixir logo in book covers, conferences, and podcasts.
Usage of the Elixir logo as the mark of third party projects, even in combination
with other marks.
Naming any company or product after Elixir, such as “The Elixir Hosting”,
“The Elixir Consultants”, etc.
Examples that require permission
Here are some examples that may be granted permission upon request:
Selling merchandise (stickers, t-shirts, mugs, etc).
You can request permission by emailing trademarks@elixir-lang.org.
Important note
Nothing in this page shall be interpreted to allow any third party to claim any
association with the Elixir project and the Elixir Team, or to imply any
approval or support by the Elixir project and the Elixir Team for any third
party products, services, or events.
+58
View File
@@ -0,0 +1,58 @@
UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE
Unicode Data Files include all data files under the directories
http://www.unicode.org/Public/, http://www.unicode.org/reports/, and
http://www.unicode.org/cldr/data/ . Unicode Software includes any source
code published in the Unicode Standard or under the directories
http://www.unicode.org/Public/, http://www.unicode.org/reports/, and
http://www.unicode.org/cldr/data/.
NOTICE TO USER: Carefully read the following legal agreement. BY
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S DATA
FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), YOU UNEQUIVOCALLY
ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE TERMS AND CONDITIONS OF THIS
AGREEMENT. IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE
OR USE THE DATA FILES OR SOFTWARE.
COPYRIGHT AND PERMISSION NOTICE
Copyright © Unicode, Inc. All rights reserved. Distributed under
the Terms of Use in http://www.unicode.org/copyright.html.
Permission is hereby granted, free of charge, to any person obtaining a
copy of the Unicode data files and any associated documentation (the
"Data Files") or Unicode software and any associated documentation (the
"Software") to deal in the Data Files or Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, and/or sell copies of the Data Files or Software,
and to permit persons to whom the Data Files or Software are furnished
to do so, provided that
(a) the above copyright notice(s) and this permission notice appear with
all copies of the Data Files or Software,
(b) both the above copyright notice(s) and this permission notice appear
in associated documentation, and
(c) there is clear notice in each modified Data File or in the Software
as well as in the documentation associated with the Data File(s) or
Software that the data or software has been modified.
THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR
ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR SOFTWARE.
Except as contained in this notice, the name of a copyright holder shall
not be used in advertising or otherwise to promote the sale, use or
other dealings in these Data Files or Software without prior written
authorization of the copyright holder.
Unicode and the Unicode logo are trademarks of Unicode, Inc., and may be
registered in some jurisdictions. All other trademarks and registered
trademarks mentioned herein are the property of their respective owners.
+30 -11
View File
@@ -1,9 +1,14 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
PREFIX ?= /usr/local
TEST_FILES ?= "*_test.exs"
SHARE_PREFIX ?= $(PREFIX)/share
MAN_PREFIX ?= $(SHARE_PREFIX)/man
# CANONICAL := main/
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
ELIXIRC_MIN_SIG := $(ELIXIRC) -e 'Code.put_compiler_option :infer_signatures, []'
ERLC := erlc -I lib/elixir/include
ERL_MAKE := erl -make
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
@@ -21,15 +26,15 @@ GIT_TAG = $(strip $(shell head="$(call GIT_REVISION)"; git tag --points-at $$hea
SOURCE_DATE_EPOCH_PATH = lib/elixir/tmp/ebin_reproducible
SOURCE_DATE_EPOCH_FILE = $(SOURCE_DATE_EPOCH_PATH)/SOURCE_DATE_EPOCH
.PHONY: install install_man build_plt clean_plt dialyze test check_reproducible clean clean_elixir clean_man format docs Docs.zip Precompiled.zip zips
.PHONY: cover install install_man build_plt clean_plt dialyze test check_reproducible clean clean_elixir clean_man format docs Docs.zip Precompiled.zip zips
.NOTPARALLEL:
#==> Functions
define CHECK_ERLANG_RELEASE
erl -noshell -eval '{V,_} = string:to_integer(erlang:system_info(otp_release)), io:fwrite("~s", [is_integer(V) and (V >= 25)])' -s erlang halt | grep -q '^true'; \
erl -noshell -eval '{V,_} = string:to_integer(erlang:system_info(otp_release)), io:fwrite("~s", [is_integer(V) and (V >= 26)])' -s erlang halt | grep -q '^true'; \
if [ $$? != 0 ]; then \
echo "At least Erlang/OTP 25.0 is required to build Elixir"; \
echo "At least Erlang/OTP 26.0 is required to build Elixir"; \
exit 1; \
fi
endef
@@ -48,6 +53,10 @@ lib/$(1)/ebin/Elixir.$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(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)";
cover/ex_unit_$(1).coverdata:
$(Q) COVER="1" $(MAKE) test_$(1)
cover/combined.coverdata: cover/ex_unit_$(1).coverdata
endef
define WRITE_SOURCE_DATE_EPOCH
@@ -97,7 +106,7 @@ $(KERNEL): lib/elixir/src/* lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex lib/elixir
"$(MAKE)" unicode; \
fi
@ echo "==> elixir (compile)";
$(Q) cd lib/elixir && ../../$(ELIXIRC) "lib/**/*.ex" -o ebin;
$(Q) cd lib/elixir && ../../$(ELIXIRC_MIN_SIG) "lib/**/*.ex" -o ebin;
$(APP): lib/elixir/src/elixir.app.src lib/elixir/ebin VERSION $(GENERATE_APP)
$(Q) $(GENERATE_APP) $(VERSION)
@@ -105,9 +114,9 @@ $(APP): lib/elixir/src/elixir.app.src lib/elixir/ebin VERSION $(GENERATE_APP)
unicode: $(UNICODE)
$(UNICODE): lib/elixir/unicode/*
@ echo "==> unicode (compile)";
$(Q) $(ELIXIRC) lib/elixir/unicode/unicode.ex -o lib/elixir/ebin;
$(Q) $(ELIXIRC) lib/elixir/unicode/tokenizer.ex -o lib/elixir/ebin;
$(Q) $(ELIXIRC) lib/elixir/unicode/security.ex -o lib/elixir/ebin;
$(Q) $(ELIXIRC_MIN_SIG) lib/elixir/unicode/unicode.ex -o lib/elixir/ebin;
$(Q) $(ELIXIRC_MIN_SIG) lib/elixir/unicode/tokenizer.ex -o lib/elixir/ebin;
$(Q) $(ELIXIRC_MIN_SIG) lib/elixir/unicode/security.ex -o lib/elixir/ebin;
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
$(eval $(call APP_TEMPLATE,logger,Logger))
@@ -170,6 +179,7 @@ clean: clean_man
rm -rf lib/mix/test/fixtures/git_sparse_repo/
rm -rf lib/mix/test/fixtures/archive/ebin/
rm -f erl_crash.dump
rm -rf cover
clean_elixir:
$(Q) rm -f lib/*/ebin/Elixir.*.beam
@@ -177,7 +187,7 @@ clean_elixir:
#==> Documentation tasks
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)" --logo lib/elixir/pages/images/logo.png --output doc/$(2) --canonical "https://hexdocs.pm/$(2)/$(CANONICAL)" --homepage-url "https://elixir-lang.org/docs.html" $(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)" --logo lib/elixir/pages/images/logo.png --output doc/$(2) --canonical "https://hexdocs.pm/$(2)/$(CANONICAL)" --homepage-url "https://elixir-lang.org/docs.html" $(DOCS_OPTIONS) $(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
@@ -219,19 +229,19 @@ docs_logger: compile ../ex_doc/bin/ex_doc
$(call DOCS_CONFIG,logger)
../ex_doc/bin/ex_doc:
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
@ echo "ex_doc is not found in ../ex_doc as expected. See CONTRIBUTING.md for more information."
@ false
#==> Zip tasks
Docs.zip: docs
rm -f Docs.zip
zip -9 -r Docs.zip CHANGELOG.md doc NOTICE LICENSE README.md
zip -9 -r Docs.zip CHANGELOG.md doc LICENSE README.md
@ echo "Docs file created $(CURDIR)/Docs.zip"
Precompiled.zip: build_man compile
rm -f Precompiled.zip
zip -9 -r Precompiled.zip bin CHANGELOG.md lib/*/ebin lib/*/lib LICENSE Makefile man NOTICE README.md VERSION
zip -9 -r Precompiled.zip bin CHANGELOG.md lib/*/ebin lib/*/lib LICENSE Makefile man README.md VERSION
@ echo "Precompiled file created $(CURDIR)/Precompiled.zip"
#==> Test tasks
@@ -282,6 +292,15 @@ test_stdlib: compile
cd lib/elixir && ../../bin/elixir --sname primary -r "test/elixir/test_helper.exs" -pr "test/elixir/**/$(TEST_FILES)"; \
fi
cover/ex_unit_elixir.coverdata:
$(Q) COVER="1" $(MAKE) test_stdlib
cover/combined.coverdata: cover/ex_unit_elixir.coverdata
cover/combined.coverdata:
bin/elixir ./lib/elixir/scripts/cover.exs
cover: cover/combined.coverdata
#==> Dialyzer tasks
DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wunderspecs
-38
View File
@@ -1,38 +0,0 @@
LEGAL NOTICE INFORMATION
------------------------
All the files in this distribution are copyright to the terms below.
== lib/elixir/src/elixir_json.erl
== lib/elixir/src/elixir_parser.erl (generated by build scripts)
Copyright Ericsson AB 1996-2024
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
== All other files
Copyright 2012 Plataformatec
Copyright 2021 The Elixir Team
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+168
View File
@@ -0,0 +1,168 @@
<!--
SPDX-License-Identifier: Apache-2.0
SPDX-FileCopyrightText: 2021 The Elixir Team
-->
# Open Source Policy
## 1. Introduction
This Open Source Policy outlines the licensing, contribution, and compliance
requirements for all code released under the Elixir project. By adhering to
these guidelines, we ensure that our community, maintainers, and contributors
uphold both legal and ethical standards while fostering a collaborative,
transparent environment.
This policy exists to support and protect the Elixir community. It aims to
balance openness, collaboration, and respect for all contributors’ rights,
ensuring that Elixir remains a trusted and innovative open source project.
## 2. Scope
This policy applies to the Elixir Programming language, located at
https://github.com/elixir-lang/elixir. It covers every file, and contribution
made, including documentation and any associated assets.
## 3. Licensing
All code released by the Elixir team is licensed under the
[Apache-2.0](./LICENSES/Apache-2.0.txt) license. Additionally, the following
licenses are recognized as permissible in this project:
- The Unicode license, as documented at
[LicenseRef-scancode-unicode](./LICENSES/LicenseRef-scancode-unicode.txt)
- The Elixir Trademark Policy, as documented at
[LicenseRef-elixir-trademark-policy](./LICENSES/LicenseRef-elixir-trademark-policy.txt)
These licenses are considered acceptable for any files or code that form part of
an Elixir repository. If a contribution requires a different license, it must
either be rejected or prompt an update to this policy.
## 4. Contributing to Elixir Projects
Any code contributed to Elixir repositories must fall under one of the accepted
licenses (Apache-2.0, Unicode, or Elixir Trademark). Contributions under any
other license will be rejected unless this policy is formally revised to include
that license. All files except those specifically exempted (e.g., certain test
fixture files) must contain SPDX license and copyright headers
(`SPDX-License-Identifier` and `SPDX-FileCopyrightText`). If a file qualifies
for an exception, this must be configured in the ORT (Open Source Review Toolkit)
configuration and undergo review.
Contributions must not introduce executable binary files into the codebase.
Every Elixir project within the organization will have an automated GitHub
Action to enforce these rules. This mechanism aids in detecting non-compliant
licenses or files early in the review process.
## 5. Preservation of Copyright and License Information
Any third-party code incorporated into Elixir projects must retain original
copyright and license headers. If no such headers exist in the source, they must
be added. This practice ensures that original authors receive proper credit and
that the licensing lineage is preserved.
## 6. Objectives
The Elixir project aims to promote a culture of responsible open source usage.
Specifically, our objectives include:
### 6.1 Clearly Define and Communicate Licensing & Compliance Policies
We will identify and document all third-party dependencies, ensure that license
information is communicated clearly, and maintain a project-wide license policy
or compliance handbook.
### 6.2 Implement Clear Processes for Reviewing Contributions
We will provide well-defined contribution guidelines. We implement the
Developer Certificate of Origin (DCO) for additional clarity regarding
contributor rights and obligations.
### 6.3 Track and Audit Third-Party Code Usage
All projects will implement a Software Bill of Materials (SBoM) strategy and
regularly verify license compliance for direct and transitive dependencies.
### 6.4 Monitor and Continuously Improve Open Source Compliance
We will conduct periodic internal audits, integrate compliance checks into
continuous integration (CI/CD) pipelines, and regularly review and refine these
objectives to align with best practices.
## 7. Roles and Responsibilities
### 7.1 Core Team Member
Core Team Members are responsible for being familiar with this policy and
ensuring it is consistently enforced. They must demonstrate sufficient
competencies to understand the policy requirements and must reject or request
changes to any pull requests that violate these standards.
### 7.2 Contributor
Contributors are expected to follow this policy when submitting code. If a
contributor submits a pull request that does not comply with the policy
(e.g., introduces a disallowed license), Core Team Members have the authority to
reject it or request changes. No special competencies are required for
contributors beyond awareness and adherence to the policy.
### 7.3 EEF CISO
The CISO designated by the Erlang Ecosystem Foundation (EEF) provides oversight
on queries and guidance regarding open source compliance or legal matters for
Elixir. The CISO is responsible for checking ongoing compliance with the policy,
escalating potential violations to the Core Team, and involving legal counsel if
necessary. This role does not require legal expertise but does involve
initiating legal or community discussions when needed.
## 8. Implications of Failing to Follow the Program Requirements
If a violation of this policy is identified, the Elixir Core Team will undertake
the following actions:
## 8.1 Review the Codebase for Additional Violations
We will investigate the codebase thoroughly to detect any similar instances of
non-compliance.
## 8.2 Review and Update the Process or Policy
In collaboration with the EEF CISO, the Elixir Core Team will assess the policy
and our internal workflows, making any necessary clarifications or amendments to
reduce the likelihood of recurrence.
## 8.3 Notify and Train Core Team Members
We will ensure that all active Core Team Members are informed about any policy
changes and understand how to apply them in everyday development.
## 8.4 Remove or Replace the Offending Code
If required, we will remove or replace the non-compliant code.
## 9. Contact
The project maintains a private mailing list at
[policy@elixir-lang.org](mailto:policy@elixir-lang.org) for handling licensing
and policy-related queries. Email is the preferred communication channel, and
the EEF CISO will be included on this list to provide assistance and ensure
timely responses. While solutions may take longer to implement, the project
commits to acknowledging all queries within five business days.
## 10. External Contributions of Core Team Members
When Core Team Members contribute to repositories outside Elixir, they do so in
a personal capacity or via their employer. They will not act as official
representatives of the Elixir team in those external contexts.
## 11. Policy Review and Amendments
This policy will be revisited annually to address new concerns, accommodate
changes in community standards, or adjust to emerging legal or technical
requirements. Proposed amendments must be reviewed by the Core Team and, if
necessary, by the EEF CISO. Any significant changes will be communicated to
contributors and made publicly available.
*Effective Date: 2025-02-20*
*Last Reviewed: 2025-02-20*
+50 -152
View File
@@ -1,3 +1,9 @@
<!--
SPDX-License-Identifier: Apache-2.0
SPDX-FileCopyrightText: 2021 The Elixir Team
SPDX-FileCopyrightText: 2012 Plataformatec
-->
<h1>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/elixir-lang/elixir-lang.github.com/raw/main/images/logo/logo-dark.png">
@@ -6,6 +12,7 @@
</h1>
[![CI](https://github.com/elixir-lang/elixir/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/elixir-lang/elixir/actions/workflows/ci.yml?query=branch%3Amain)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/10187/badge)](https://www.bestpractices.dev/projects/10187)
Elixir is a dynamic, functional language designed for building scalable
and maintainable applications.
@@ -25,19 +32,18 @@ information, please read our [Security Policy][9].
All interactions in our official communication channels follow our
[Code of Conduct][1].
All contributions are required to conform to our [Open Source Policy][11].
## Bug reports
For reporting bugs, [visit our issue tracker][2] and follow the steps
for reporting a new issue. **Please disclose security vulnerabilities
privately at <elixir-security@googlegroups.com>**.
privately [in our Security page](https://github.com/elixir-lang/elixir/security)**.
## Issues tracker management
All currently open bugs related to the Elixir repository are listed
in the issues tracker. The Elixir team uses the issues tracker to focus
on *actionable items*, including planned enhancements in the short and
medium term. We also do our best to label entries for clarity and to ease
collaboration.
All currently open bugs related to Elixir are listed in the issues tracker.
The Elixir team uses the issues tracker to focus on *actionable items*,
including planned enhancements in the short and medium term. We also do
our best to label entries for clarity and to ease collaboration.
Our *actionable item policy* has some important consequences, such as:
@@ -49,38 +55,42 @@ Our *actionable item policy* has some important consequences, such as:
elsewhere if appropriate).
* We actively close unrelated and non-actionable issues to keep the
issues tracker tidy. We may get things wrong from time to
time and will gladly revisit issues, reopening when necessary.
issues tracker tidy. If you believe we got something wrong, drop a
comment and we can always reopen the issue.
Keep the tone positive and be kind! For more information, see the
[Code of Conduct][1].
By keeping the overall issues tracker tidy and organized, the community
can easily peak at what is coming in new releases and also get involved
by commenting on existing issues and submitting pull requests. Please
remember to keep the tone positive and be kind! For more information,
see the [Code of Conduct][1].
### Proposing new features
For proposing new features, please start a discussion in the
[Elixir Core mailing list][3]. The [language development history and
its focus are described on our website](https://elixir-lang.org/development.html).
Keep in mind that it is your responsibility to argue and explain
why a feature is useful and how it will impact the codebase and
the community. A good proposal includes the problem description
and how the proposed solution compares with existing alternatives
in the Elixir ecosystem (as well as in other languages). To iron
out a proposal before submission, consider using and gathering
feedback from the community spaces [listed on the sidebar of the
Elixir website](https://elixir-lang.org/).
Once a proposal is accepted, it will be added to [the issue tracker][2].
Features and bug fixes that have already been merged and will be included
in the next release are then "closed" and added to the [changelog][7].
### Discussions, support, and help
## Discussions, support, and help
For general discussions, support, and help, please use the community
spaces [listed on the sidebar of the Elixir website](https://elixir-lang.org/),
such as forums, chat platforms, etc, where the wider community will be available
to help you.
## Proposing new features
We encourage you to first propose new features in the community spaces
listed above. These discussions help refine ideas and gather feedback before
submission. Our website also includes [a general outline of the language
history and its current development focus](https://elixir-lang.org/development.html).
Once you are ready, you can submit your proposal to the [Elixir Core
mailing list][3], either through the web interface or by subscribing to
it at <elixir-lang-core+subscribe@googlegroups.com>. Remember to include
a clear problem description, compare the proposed solution to existing
alternatives in the Elixir ecosystem (and in other languages if possible),
and consider the potential impact your changes will have on the codebase and
community.
Once a proposal is accepted, it will be added to [the issue tracker][2].
Features and bug fixes that have already been merged and will be included
in the next release are then "closed" and added to the [changelog][7]
before release.
## Compiling from source
For the many different ways to install Elixir,
@@ -109,125 +119,12 @@ variable `ERL_COMPILER_OPTIONS=deterministic`.
## Contributing
We invite contributions to Elixir. To contribute, there are a few
things you need to know about the code. First, Elixir code is divided
by each application inside the `lib` folder:
* `elixir` - Elixir's kernel and standard library
* `eex` - EEx is the template engine that allows you to embed Elixir
* `ex_unit` - ExUnit is a simple test framework that ships with Elixir
* `iex` - IEx stands for Interactive Elixir: Elixir's interactive shell
* `logger` - Logger is the built-in logger
* `mix` - Mix is Elixir's build tool
You can run all tests in the root directory with `make test`. You can
also run tests for a specific framework with `make test_#{APPLICATION}`, for example,
`make test_ex_unit`. If you just changed something in Elixir's standard
library, you can run only that portion through `make test_stdlib`.
If you are only changing one file, you can choose to compile and run tests
for that specific file for faster development cycles. For example, if you
are changing the String module, you can compile it and run its tests as:
```sh
bin/elixirc lib/elixir/lib/string.ex -o lib/elixir/ebin
bin/elixir lib/elixir/test/elixir/string_test.exs
```
Some test files need their `test_helper.exs` to be explicitly required
before, such as:
```sh
bin/elixir -r lib/logger/test/test_helper.exs lib/logger/test/logger_test.exs
```
You can also use the `LINE` env var to run a single test:
```sh
LINE=123 bin/elixir lib/elixir/test/elixir/string_test.exs
````
To recompile all (including Erlang modules):
```sh
make compile
```
After your changes are done, please remember to run `make format` to guarantee
all files are properly formatted, then run the full suite with
`make test`.
If your contribution fails during the bootstrapping of the language,
you can rebuild the language from scratch with:
```sh
make clean_elixir compile
```
Similarly, if you can not get Elixir to compile or the tests to pass after
updating an existing checkout, run `make clean compile`. You can check
[the official build status](https://github.com/elixir-lang/elixir/actions/workflows/ci.yml).
More tasks can be found by reading the [Makefile](Makefile).
With tests running and passing, you are ready to contribute to Elixir and
[send a pull request](https://help.github.com/articles/using-pull-requests/).
We have saved some excellent pull requests we have received in the past in
case you are looking for some examples:
* [Implement Enum.member? - Pull request](https://github.com/elixir-lang/elixir/pull/992)
* [Add String.valid? - Pull request](https://github.com/elixir-lang/elixir/pull/1058)
* [Implement capture_io for ExUnit - Pull request](https://github.com/elixir-lang/elixir/pull/1059)
### Reviewing changes
Once a pull request is sent, the Elixir team will review your changes.
We outline our process below to clarify the roles of everyone involved.
All pull requests must be approved by two committers before being merged into
the repository. If changes are necessary, the team will leave appropriate
comments requesting changes to the code. Unfortunately, we cannot guarantee a
pull request will be merged, even when modifications are requested, as the Elixir
team will re-evaluate the contribution as it changes.
Committers may also push style changes directly to your branch. If you would
rather manage all changes yourself, you can disable the "Allow edits from maintainers"
feature when submitting your pull request.
The Elixir team may optionally assign someone to review a pull request.
If someone is assigned, they must explicitly approve the code before
another team member can merge it.
When the review finishes, your pull request will be squashed and merged
into the repository. If you have carefully organized your commits and
believe they should be merged without squashing, please mention it in
a comment.
## Building documentation
Building the documentation requires that [ExDoc](https://github.com/elixir-lang/ex_doc)
is 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/elixir ../elixir/bin/mix do deps.get + compile
```
Now go back to Elixir's root directory and run:
```sh
make docs # to generate HTML pages
make docs DOCS_FORMAT=epub # to generate EPUB documents
```
This will produce documentation sets for `elixir`, `eex`, `ex_unit`, `iex`, `logger`,
and `mix` under the `doc` directory. If you are planning to contribute documentation,
[please check our best practices for writing documentation](https://hexdocs.pm/elixir/writing-documentation.html).
Contributions to Elixir are always welcome! Before you get started, please check
out our [CONTRIBUTING.md](CONTRIBUTING.md) file. There you will find detailed
guidelines on how to set up your environment, run the test suite, format your
code, and submit pull requests. We also include information on our review
process, licensing requirements, and helpful tips to ensure a smooth
contribution experience.
## Development links
@@ -250,6 +147,7 @@ and `mix` under the `doc` directory. If you are planning to contribute documenta
[8]: https://groups.google.com/group/elixir-lang-ann
[9]: SECURITY.md
[10]: https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date
[11]: OPEN_SOURCE_POLICY.md
## License
@@ -257,4 +155,4 @@ and `mix` under the `doc` directory. If you are planning to contribute documenta
Elixir source code is released under Apache License 2.0.
Check [NOTICE](NOTICE) and [LICENSE](LICENSE) files for more information.
Check [LICENSE](LICENSE) file for more information.
+8 -2
View File
@@ -1,3 +1,9 @@
<!--
SPDX-License-Identifier: Apache-2.0
SPDX-FileCopyrightText: 2021 The Elixir Team
SPDX-FileCopyrightText: 2012 Plataformatec
-->
# Release process
## Shipping a new version
@@ -22,7 +28,7 @@
### In the new branch
1. Comment out `CANONICAL=` in /Makefile
1. Comment out `CANONICAL := main/` in /Makefile
2. Update tables in /SECURITY.md and "Compatibility and Deprecations"
@@ -44,6 +50,6 @@
2. Update `otp_release` checks in `/Makefile` and `/lib/elixir/src/elixir.erl`
3. Update relevant CI workflows in `/.github/workflows/*.yml`
3. Update relevant CI workflows in `/.github/workflows/*.yml` - for release workflows, outdated/recently added Erlang/OTP versions must run conditionally
4. Remove `otp_release` version checks that are no longer needed
+8 -2
View File
@@ -1,3 +1,9 @@
<!--
SPDX-License-Identifier: Apache-2.0
SPDX-FileCopyrightText: 2021 The Elixir Team
SPDX-FileCopyrightText: 2012 Plataformatec
-->
# Security Policy
## Supported versions
@@ -6,11 +12,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
Elixir version | Support
:------------- | :-----------------------------
1.18 | Bug fixes and security patches
1.19 | Bug fixes and security patches
1.18 | Security patches only
1.17 | Security patches only
1.16 | Security patches only
1.15 | Security patches only
1.14 | Security patches only
## Announcements
+1 -1
View File
@@ -1 +1 @@
1.18.4
1.19.5
+6 -5
View File
@@ -1,7 +1,12 @@
#!/bin/sh
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
set -e
ELIXIR_VERSION=1.18.4
ELIXIR_VERSION=1.19.5
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
cat <<USAGE >&2
@@ -216,10 +221,6 @@ SCRIPT_PATH=$(dirname "$SELF")
if [ "$OSTYPE" = "cygwin" ]; then SCRIPT_PATH=$(cygpath -m "$SCRIPT_PATH"); fi
if [ "$MODE" != "iex" ]; then ERL="-s elixir start_cli $ERL"; fi
if [ "$OS" != "Windows_NT" ] && [ -z "$NO_COLOR" ]; then
if test -t 1 -a -t 2; then ERL="-elixir ansi_enabled true $ERL"; fi
fi
# One MAY change ERTS_BIN= but you MUST NOT change
# ERTS_BIN=$ERTS_BIN as it is handled by Elixir releases.
ERTS_BIN=
+5 -1
View File
@@ -1,6 +1,10 @@
@echo off
set ELIXIR_VERSION=1.18.4
:: SPDX-License-Identifier: Apache-2.0
:: SPDX-FileCopyrightText: 2021 The Elixir Team
:: SPDX-FileCopyrightText: 2012 Plataformatec
set ELIXIR_VERSION=1.19.5
if ""%1""=="""" if ""%2""=="""" goto documentation
if /I ""%1""==""--help"" if ""%2""=="""" goto documentation
+5
View File
@@ -1,4 +1,9 @@
#!/bin/sh
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
set -e
if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
+5
View File
@@ -1,4 +1,9 @@
@echo off
:: SPDX-License-Identifier: Apache-2.0
:: SPDX-FileCopyrightText: 2021 The Elixir Team
:: SPDX-FileCopyrightText: 2012 Plataformatec
setlocal
set argc=0
for %%A in (%*) do (
+5
View File
@@ -1,4 +1,9 @@
#!/bin/sh
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
set -e
if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
+5
View File
@@ -1,4 +1,9 @@
@echo off
:: SPDX-License-Identifier: Apache-2.0
:: SPDX-FileCopyrightText: 2021 The Elixir Team
:: SPDX-FileCopyrightText: 2012 Plataformatec
setlocal
if /I ""%1""==""--help"" goto documentation
if /I ""%1""==""-h"" goto documentation
+5
View File
@@ -1,2 +1,7 @@
#!/usr/bin/env elixir
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
Mix.CLI.main()
+5
View File
@@ -1,2 +1,7 @@
@echo off
:: SPDX-License-Identifier: Apache-2.0
:: SPDX-FileCopyrightText: 2021 The Elixir Team
:: SPDX-FileCopyrightText: 2012 Plataformatec
call "%~dp0\elixir.bat" "%~dp0\mix" %*
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
# Store path to mix.bat as a FileInfo object
$mixBatPath = (Get-ChildItem (((Get-ChildItem $MyInvocation.MyCommand.Path).Directory.FullName) + '\mix.bat'))
$newArgs = @()
+26 -6
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule EEx.SyntaxError do
defexception [:file, :line, :column, :snippet, message: "syntax error"]
@@ -114,6 +118,19 @@ defmodule EEx do
| {:expr | :start_expr | :middle_expr | :end_expr, marker, charlist, metadata}
| {:eof, metadata}
@type tokenize_opt ::
{:file, binary()}
| {:line, line}
| {:column, column}
| {:indentation, non_neg_integer}
| {:trim, boolean()}
@type compile_opt ::
tokenize_opt
| {:engine, module()}
| {:parser_options, Code.parser_opts()}
| {atom(), term()}
@doc """
Generates a function definition from the given string.
@@ -124,6 +141,7 @@ defmodule EEx do
template.
The supported `options` are described [in the module docs](#module-options).
Additional options are passed to the underlying engine.
## Examples
@@ -216,9 +234,11 @@ defmodule EEx do
"3"
"""
@spec compile_string(String.t(), keyword) :: Macro.t()
@spec compile_string(String.t(), [compile_opt]) :: Macro.t()
def compile_string(source, options \\ []) when is_binary(source) and is_list(options) do
case tokenize(source, options) do
tokenize_opts = Keyword.take(options, [:file, :line, :column, :indentation, :trim])
case tokenize(source, tokenize_opts) do
{:ok, tokens} ->
EEx.Compiler.compile(tokens, source, options)
@@ -255,7 +275,7 @@ defmodule EEx do
#=> "3"
"""
@spec compile_file(Path.t(), keyword) :: Macro.t()
@spec compile_file(Path.t(), [compile_opt]) :: Macro.t()
def compile_file(filename, options \\ []) when is_list(options) do
filename = IO.chardata_to_string(filename)
options = Keyword.merge([file: filename, line: 1], options)
@@ -273,7 +293,7 @@ defmodule EEx do
"foo baz"
"""
@spec eval_string(String.t(), keyword, keyword) :: String.t()
@spec eval_string(String.t(), keyword, [compile_opt]) :: String.t()
def eval_string(source, bindings \\ [], options \\ [])
when is_binary(source) and is_list(bindings) and is_list(options) do
compiled = compile_string(source, options)
@@ -295,7 +315,7 @@ defmodule EEx do
#=> "foo baz"
"""
@spec eval_file(Path.t(), keyword, keyword) :: String.t()
@spec eval_file(Path.t(), keyword, [compile_opt]) :: String.t()
def eval_file(filename, bindings \\ [], options \\ [])
when is_list(bindings) and is_list(options) do
filename = IO.chardata_to_string(filename)
@@ -335,7 +355,7 @@ defmodule EEx do
Note new tokens may be added in the future.
"""
@doc since: "1.14.0"
@spec tokenize([char()] | String.t(), opts :: keyword) ::
@spec tokenize([char()] | String.t(), [tokenize_opt]) ::
{:ok, [token()]} | {:error, String.t(), metadata()}
def tokenize(contents, opts \\ []) do
EEx.Compiler.tokenize(contents, opts)
+5 -1
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule EEx.Compiler do
@moduledoc false
@@ -300,7 +304,7 @@ defmodule EEx.Compiler do
source: source,
line: line,
quoted: [],
parser_options: parser_options,
parser_options: [indentation: indentation] ++ parser_options,
indentation: indentation
}
+8
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule EEx.Engine do
@moduledoc ~S"""
Basic EEx engine that ships with Elixir.
@@ -13,6 +17,10 @@ defmodule EEx.Engine do
@doc """
Called at the beginning of every template.
It receives the options during compilation, including the
ones managed by EEx, such as `:line` and `:file`, as well
as custom engine options.
It must return the initial state.
"""
@callback init(opts :: keyword) :: state
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule EEx.SmartEngine do
@moduledoc """
The default engine used by EEx.
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule EEx.MixProject do
use Mix.Project
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
Code.require_file("../test_helper.exs", __DIR__)
defmodule EEx.SmartEngineTest do
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
Code.require_file("../test_helper.exs", __DIR__)
defmodule EEx.TokenizerTest do
+66 -3
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
Code.require_file("test_helper.exs", __DIR__)
require EEx
@@ -498,6 +502,59 @@ defmodule EExTest do
end
end
test "from Elixir parser" do
line = __ENV__.line + 6
message =
assert_raise TokenMissingError, fn ->
EEx.compile_string(
"""
<li>
<strong>Some:</strong>
<%= true && @some[ %>
</li>
""",
file: __ENV__.file,
line: line,
indentation: 12
)
end
assert message |> Exception.message() |> strip_ansi() =~ """
│
514 │ true && @some[\s
│ │ └ missing closing delimiter (expected "]")
│ └ unclosed delimiter
"""
end
test "from Elixir parser with line breaks" do
line = __ENV__.line + 6
message =
assert_raise TokenMissingError, fn ->
EEx.compile_string(
"""
<li>
<strong>Some:</strong>
<%= true &&
@some[ %>
</li>
""",
file: __ENV__.file,
line: line,
indentation: 12
)
end
assert message |> Exception.message() |> strip_ansi() =~ """
│
#{line + 3} │ @some[\s
│ │ └ missing closing delimiter (expected "]")
│ └ unclosed delimiter
"""
end
test "honor line numbers" do
assert_raise EEx.SyntaxError,
"nofile:100:6: expected closing '%>' for EEx expression",
@@ -860,13 +917,13 @@ defmodule EExTest do
file = to_charlist(Path.relative_to_cwd(__ENV__.file))
assert EExTest.Compiled.before_compile() ==
{7, {EExTest.Compiled, :before_compile, 0, [file: file, line: 7]}}
{11, {EExTest.Compiled, :before_compile, 0, [file: file, line: 11]}}
assert EExTest.Compiled.after_compile() ==
{21, {EExTest.Compiled, :after_compile, 0, [file: file, line: 21]}}
{25, {EExTest.Compiled, :after_compile, 0, [file: file, line: 25]}}
assert EExTest.Compiled.unknown() ==
{26, {EExTest.Compiled, :unknown, 0, [file: ~c"unknown", line: 26]}}
{30, {EExTest.Compiled, :unknown, 0, [file: ~c"unknown", line: 30]}}
end
end
@@ -944,6 +1001,12 @@ defmodule EExTest do
end
end
@strip_ansi [IO.ANSI.green(), IO.ANSI.red(), IO.ANSI.reset()]
defp strip_ansi(doc) do
String.replace(doc, @strip_ansi, "")
end
defp assert_eval(expected, actual, binding \\ [], opts \\ []) do
opts = Keyword.merge([file: __ENV__.file, engine: opts[:engine] || EEx.Engine], opts)
result = EEx.eval_string(actual, binding, opts)
+7
View File
@@ -1,6 +1,13 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
{line_exclude, line_include} =
if line = System.get_env("LINE"), do: {[:test], [line: line]}, else: {[], []}
Code.require_file("../../elixir/scripts/cover_record.exs", __DIR__)
CoverageRecorder.maybe_record("eex")
ExUnit.start(
trace: !!System.get_env("TRACE"),
include: line_include,
+4
View File
@@ -1,3 +1,7 @@
%% SPDX-License-Identifier: Apache-2.0
%% SPDX-FileCopyrightText: 2021 The Elixir Team
%% SPDX-FileCopyrightText: 2012 Plataformatec
{'src/*', [
warn_unused_vars,
warn_export_all,
+105 -11
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Access do
@moduledoc """
Key-based access to data structures.
@@ -513,8 +517,8 @@ defmodule Access do
An error is raised if the accessed structure is not a map or a struct:
iex> get_in([], [Access.key(:foo)])
** (BadMapError) expected a map, got: []
** (BadMapError) expected a map, got:
...
"""
@spec key(key, term) :: access_fun(data :: struct | map, current_value :: term)
def key(key, default \\ nil) do
@@ -552,7 +556,8 @@ defmodule Access do
iex> pop_in(map, [Access.key!(:user), Access.key!(:name)])
{"john", %{user: %{}}}
iex> get_in(map, [Access.key!(:user), Access.key!(:unknown)])
** (KeyError) key :unknown not found in: %{name: \"john\"}
** (KeyError) key :unknown not found in:
...
The examples above could be partially written as:
@@ -807,7 +812,7 @@ defmodule Access do
iex> get_in([:a, :b, :c], [Access.at!(2)])
:c
iex> get_in([:a, :b, :c], [Access.at!(3)])
** (Enum.OutOfBoundsError) out of bounds error
** (Enum.OutOfBoundsError) out of bounds error at position 3 when traversing enumerable [:a, :b, :c]
"""
@doc since: "1.11.0"
@@ -819,12 +824,14 @@ defmodule Access do
defp at!(:get, data, index, next) when is_list(data) do
case Enum.fetch(data, index) do
{:ok, value} -> next.(value)
:error -> raise Enum.OutOfBoundsError
:error -> raise Enum.OutOfBoundsError, index: index, enumerable: data
end
end
defp at!(:get_and_update, data, index, next) when is_list(data) do
get_and_update_at(data, index, next, [], fn -> raise Enum.OutOfBoundsError end)
get_and_update_at(data, index, next, [], fn ->
raise Enum.OutOfBoundsError, index: index, enumerable: data
end)
end
defp at!(_op, data, _index, _next) do
@@ -884,7 +891,7 @@ defmodule Access do
end
defp filter(:get, data, func, next) when is_list(data) do
data |> Enum.filter(func) |> Enum.map(next)
for elem <- data, func.(elem), do: next.(elem)
end
defp filter(:get_and_update, data, func, next) when is_list(data) do
@@ -992,6 +999,93 @@ defmodule Access do
raise ArgumentError, "Access.slice/1 expected a list, got: #{inspect(data)}"
end
@doc """
Returns a function that accesses all values in a map or a keyword list.
The returned function is typically passed as an accessor to `Kernel.get_in/2`,
`Kernel.get_and_update_in/3`, and friends.
## Examples
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_in(users, [Access.values(), :age]) |> Enum.sort()
[23, 27]
iex> update_in(users, [Access.values(), :age], fn age -> age + 1 end)
%{"john" => %{age: 28}, "meg" => %{age: 24}}
iex> put_in(users, [Access.values(), :planet], "Earth")
%{"john" => %{age: 27, planet: "Earth"}, "meg" => %{age: 23, planet: "Earth"}}
Values in keyword lists can be accessed as well:
iex> users = [john: %{age: 27}, meg: %{age: 23}]
iex> get_and_update_in(users, [Access.values(), :age], fn age -> {age, age + 1} end)
{[27, 23], [john: %{age: 28}, meg: %{age: 24}]}
By returning `:pop` from an accessor function, you can remove the accessed key and value
from the map or keyword list:
iex> require Integer
iex> numbers = [one: 1, two: 2, three: 3, four: 4]
iex> get_and_update_in(numbers, [Access.values()], fn num ->
...> if Integer.is_even(num), do: :pop, else: {num, to_string(num)}
...> end)
{[1, 2, 3, 4], [one: "1", three: "3"]}
An error is raised if the accessed structure is not a map nor a keyword list:
iex> get_in([1, 2, 3], [Access.values()])
** (RuntimeError) Access.values/0 expected a map or a keyword list, got: [1, 2, 3]
"""
@doc since: "1.19.0"
@spec values() :: Access.access_fun(data :: map() | keyword(), current_value :: list())
def values do
&values/3
end
defp values(:get, data = %{}, next) do
Enum.map(data, fn {_key, value} -> next.(value) end)
end
defp values(:get_and_update, data = %{}, next) do
{reverse_gets, updated_data} =
Enum.reduce(data, {[], %{}}, fn {key, value}, {gets, data_acc} ->
case next.(value) do
{get, update} -> {[get | gets], Map.put(data_acc, key, update)}
:pop -> {[value | gets], data_acc}
end
end)
{Enum.reverse(reverse_gets), updated_data}
end
defp values(op, data = [], next) do
values_keyword(op, data, next)
end
defp values(op, data = [{key, _value} | _tail], next) when is_atom(key) do
values_keyword(op, data, next)
end
defp values(_op, data, _next) do
raise "Access.values/0 expected a map or a keyword list, got: #{inspect(data)}"
end
defp values_keyword(:get, data, next) do
Enum.map(data, fn {key, value} when is_atom(key) -> next.(value) end)
end
defp values_keyword(:get_and_update, data, next) do
{reverse_gets, reverse_updated_data} =
Enum.reduce(data, {[], []}, fn {key, value}, {gets, data_acc} when is_atom(key) ->
case next.(value) do
{get, update} -> {[get | gets], [{key, update} | data_acc]}
:pop -> {[value | gets], data_acc}
end
end)
{Enum.reverse(reverse_gets), Enum.reverse(reverse_updated_data)}
end
defp normalize_range(%Range{first: first, last: last, step: step}, list)
when first < 0 or last < 0 do
count = length(list)
@@ -1038,9 +1132,9 @@ defmodule Access do
iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]
iex> get_in(list, [Access.find(&(&1.salary > 20)), :name])
"francine"
iex> get_and_update_in(list, [Access.find(&(&1.salary <= 40)), :name], fn prev ->
...> {prev, String.upcase(prev)}
...> end)
iex> get_and_update_in(list, [Access.find(&(&1.salary <= 40)), :name], fn prev ->
...> {prev, String.upcase(prev)}
...> end)
{"john", [%{name: "JOHN", salary: 10}, %{name: "francine", salary: 30}]}
`find/1` can also be used to pop the first found element out of a list or
@@ -1067,7 +1161,7 @@ defmodule Access do
An error is raised if the accessed structure is not a list:
iex> get_in(%{}, [Access.find(fn a -> a == 10 end)])
iex> get_in(%{}, [Access.find(fn a -> a == 10 end)])
** (RuntimeError) Access.find/1 expected a list, got: %{}
"""
@doc since: "1.17.0"
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Agent do
@moduledoc """
Agents are a simple abstraction around state.
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Agent.Server do
@moduledoc false
+7 -15
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Application do
@moduledoc """
A module for working with applications and defining application callbacks.
@@ -243,7 +247,7 @@ defmodule Application do
invoked if it hasn't been done yet. Then, it checks if the dependencies listed
in the `applications` key of the resource file are already started. Having at
least one dependency not started is an error condition. Functions like
`ensure_all_started/1` takes care of starting an application and all of its
`ensure_all_started/1` take care of starting an application and all of its
dependencies for you.
If the application does not have a callback module configured, starting is
@@ -908,8 +912,7 @@ defmodule Application do
`:permanent`, or `:transient`. See `t:restart_type/1` for more information.
* `:mode` - (since v1.15.0) if the applications should be started serially
(`:serial`, default) or concurrently (`:concurrent`). This option requires
Erlang/OTP 26+.
(`:serial`, default) or concurrently (`:concurrent`).
"""
@spec ensure_all_started(app | [app], type: restart_type(), mode: :serial | :concurrent) ::
@@ -930,18 +933,7 @@ defmodule Application do
def ensure_all_started(apps, opts) when is_list(apps) and is_list(opts) do
opts = Keyword.validate!(opts, type: :temporary, mode: :serial)
if function_exported?(:application, :ensure_all_started, 3) do
:application.ensure_all_started(apps, opts[:type], opts[:mode])
else
# TODO: Remove this clause when we require Erlang/OTP 26+
Enum.reduce_while(apps, {:ok, []}, fn app, {:ok, acc} ->
case :application.ensure_all_started(app, opts[:type]) do
{:ok, apps} -> {:cont, {:ok, apps ++ acc}}
{:error, e} -> {:halt, {:error, e}}
end
end)
end
:application.ensure_all_started(apps, opts[:type], opts[:mode])
end
@doc """
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Atom do
@moduledoc """
Atoms are constants whose values are their own name.
+583 -150
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Base do
import Bitwise
@@ -315,58 +319,126 @@ defmodule Base do
"Double check your string for unwanted characters or pad it accordingly"
end
@doc """
Checks if a string is a valid base 16 encoded string.
> #### When to use this {: .tip}
>
> Use this function when you just need to *validate* that a string is
> valid base 16 data, without actually producing a decoded output string.
> This function is both more performant and memory efficient than using
> `decode16/2`, checking that the result is `{:ok, ...}`, and then
> discarding the decoded binary.
## Options
Accepts the same options as `decode16/2`.
## Examples
iex> Base.valid16?("666F6F626172")
true
iex> Base.valid16?("666f6f626172", case: :lower)
true
iex> Base.valid16?("666f6F626172", case: :mixed)
true
iex> Base.valid16?("ff", case: :upper)
false
"""
@doc since: "1.19.0"
@spec valid16?(binary, case: decode_case) :: boolean
def valid16?(string, opts \\ [])
def valid16?(string, opts) when is_binary(string) and rem(byte_size(string), 2) == 0 do
case Keyword.get(opts, :case, :upper) do
:upper -> validate16upper?(string)
:lower -> validate16lower?(string)
:mixed -> validate16mixed?(string)
end
end
def valid16?(string, _opts) when is_binary(string) do
false
end
upper = Enum.with_index(b16_alphabet)
for {base, alphabet} <- [upper: upper, lower: to_lower_dec.(upper), mixed: to_mixed_dec.(upper)] do
name = :"decode16#{base}!"
decode_name = :"decode16#{base}!"
validate_name = :"validate16#{base}?"
valid_char_name = :"valid_char16#{base}?"
{min, decoded} = to_decode_list.(alphabet)
defp unquote(name)(char) do
try do
elem({unquote_splicing(decoded)}, char - unquote(min))
rescue
_ -> bad_character!(char)
else
nil -> bad_character!(char)
char -> char
defp unquote(validate_name)(<<>>), do: true
defp unquote(validate_name)(<<c1, c2, rest::binary>>) do
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(validate_name)(rest)
end
defp unquote(validate_name)(<<_char, _rest::binary>>), do: false
@compile {:inline, [{valid_char_name, 1}]}
defp unquote(valid_char_name)(char)
when elem({unquote_splicing(decoded)}, char - unquote(min)) != nil,
do: true
defp unquote(valid_char_name)(_char), do: false
defp unquote(decode_name)(char) do
index = char - unquote(min)
cond do
index not in 0..unquote(length(decoded) - 1) -> bad_character!(char)
new_char = elem({unquote_splicing(decoded)}, index) -> new_char
true -> bad_character!(char)
end
end
defp unquote(name)(<<c1, c2, c3, c4, c5, c6, c7, c8, rest::binary>>, acc) do
unquote(name)(
defp unquote(decode_name)(<<c1, c2, c3, c4, c5, c6, c7, c8, rest::binary>>, acc) do
unquote(decode_name)(
rest,
<<
acc::binary,
unquote(name)(c1)::4,
unquote(name)(c2)::4,
unquote(name)(c3)::4,
unquote(name)(c4)::4,
unquote(name)(c5)::4,
unquote(name)(c6)::4,
unquote(name)(c7)::4,
unquote(name)(c8)::4
unquote(decode_name)(c1)::4,
unquote(decode_name)(c2)::4,
unquote(decode_name)(c3)::4,
unquote(decode_name)(c4)::4,
unquote(decode_name)(c5)::4,
unquote(decode_name)(c6)::4,
unquote(decode_name)(c7)::4,
unquote(decode_name)(c8)::4
>>
)
end
defp unquote(name)(<<c1, c2, c3, c4, rest::binary>>, acc) do
unquote(name)(
defp unquote(decode_name)(<<c1, c2, c3, c4, rest::binary>>, acc) do
unquote(decode_name)(
rest,
<<
acc::binary,
unquote(name)(c1)::4,
unquote(name)(c2)::4,
unquote(name)(c3)::4,
unquote(name)(c4)::4
unquote(decode_name)(c1)::4,
unquote(decode_name)(c2)::4,
unquote(decode_name)(c3)::4,
unquote(decode_name)(c4)::4
>>
)
end
defp unquote(name)(<<c1::8, c2::8, rest::binary>>, acc) do
unquote(name)(rest, <<acc::binary, unquote(name)(c1)::4, unquote(name)(c2)::4>>)
defp unquote(decode_name)(<<c1::8, c2::8, rest::binary>>, acc) do
unquote(decode_name)(
rest,
<<acc::binary, unquote(decode_name)(c1)::4, unquote(decode_name)(c2)::4>>
)
end
defp unquote(name)(<<>>, acc) do
defp unquote(decode_name)(<<>>, acc) do
acc
end
end
@@ -555,6 +627,40 @@ defmodule Base do
string |> remove_ignored(opts[:ignore]) |> decode64base!(pad?)
end
@doc """
Validates a base 64 encoded string.
> #### When to use this {: .tip}
>
> Use this function when you just need to *validate* that a string is
> valid base 64 data, without actually producing a decoded output string.
> This function is both more performant and memory efficient than using
> `decode64/2`, checking that the result is `{:ok, ...}`, and then
> discarding the decoded binary.
## Options
Accepts the same options as `decode64/2`.
## Examples
iex> Base.valid64?("Zm9vYmFy")
true
iex> Base.valid64?("Zm9vYmFy\\n", ignore: :whitespace)
true
iex> Base.valid64?("Zm9vYg==")
true
"""
@doc since: "1.19.0"
@spec valid64?(binary, ignore: :whitespace, padding: boolean) :: boolean
def valid64?(string, opts \\ []) when is_binary(string) do
pad? = Keyword.get(opts, :padding, true)
string |> remove_ignored(opts[:ignore]) |> validate64base?(pad?)
end
@doc """
Decodes a base 64 encoded string with URL and filename safe alphabet
into a binary string.
@@ -615,120 +721,266 @@ defmodule Base do
string |> remove_ignored(opts[:ignore]) |> decode64url!(pad?)
end
@doc """
Validates a base 64 encoded string with URL and filename safe alphabet.
> #### When to use this {: .tip}
>
> Use this function when you just need to *validate* that a string is
> valid (URL-safe) base 64 data, without actually producing a decoded
> output string. This function is both more performant and memory efficient
> than using `url_decode64/2`, checking that the result is `{:ok, ...}`,
> and then discarding the decoded binary.
## Options
Accepts the same options as `url_decode64/2`.
## Examples
iex> Base.url_valid64?("_3_-_A==")
true
iex> Base.url_valid64?("_3_-_A==\\n", ignore: :whitespace)
true
iex> Base.url_valid64?("_3_-_A", padding: false)
true
"""
@doc since: "1.19.0"
@spec url_valid64?(binary, ignore: :whitespace, padding: boolean) :: boolean
def url_valid64?(string, opts \\ []) when is_binary(string) do
pad? = Keyword.get(opts, :padding, true)
string |> remove_ignored(opts[:ignore]) |> validate64url?(pad?)
end
for {base, alphabet} <- [base: b64_alphabet, url: b64url_alphabet] do
name = :"decode64#{base}!"
decode_name = :"decode64#{base}!"
validate_name = :"validate64#{base}?"
validate_main_name = :"validate_main64#{validate_name}?"
valid_char_name = :"valid_char64#{base}?"
{min, decoded} = alphabet |> Enum.with_index() |> to_decode_list.()
defp unquote(name)(char) do
try do
elem({unquote_splicing(decoded)}, char - unquote(min))
rescue
_ -> bad_character!(char)
else
nil -> bad_character!(char)
char -> char
defp unquote(validate_main_name)(<<>>), do: true
defp unquote(validate_main_name)(
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8, rest::binary>>
) do
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4) and
unquote(valid_char_name)(c5) and
unquote(valid_char_name)(c6) and
unquote(valid_char_name)(c7) and
unquote(valid_char_name)(c8) and
unquote(validate_main_name)(rest)
end
defp unquote(validate_name)(<<>>, _pad?), do: true
defp unquote(validate_name)(string, pad?) do
segs = div(byte_size(string) + 7, 8) - 1
<<main::size(^segs)-binary-unit(64), rest::binary>> = string
main_valid? = unquote(validate_main_name)(main)
case rest do
_ when not main_valid? ->
false
<<c1::8, c2::8, ?=, ?=>> ->
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2)
<<c1::8, c2::8, c3::8, ?=>> ->
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3)
<<c1::8, c2::8, c3::8, c4::8>> ->
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4)
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, ?=, ?=>> ->
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4) and
unquote(valid_char_name)(c5) and
unquote(valid_char_name)(c6)
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4) and
unquote(valid_char_name)(c5) and
unquote(valid_char_name)(c6) and
unquote(valid_char_name)(c7)
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4) and
unquote(valid_char_name)(c5) and
unquote(valid_char_name)(c6) and
unquote(valid_char_name)(c7) and
unquote(valid_char_name)(c8)
<<c1::8, c2::8>> when not pad? ->
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2)
<<c1::8, c2::8, c3::8>> when not pad? ->
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3)
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8>> when not pad? ->
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4) and
unquote(valid_char_name)(c5) and
unquote(valid_char_name)(c6)
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8>> when not pad? ->
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4) and
unquote(valid_char_name)(c5) and
unquote(valid_char_name)(c6) and
unquote(valid_char_name)(c7)
_ ->
false
end
end
defp unquote(name)(<<>>, _pad?), do: <<>>
@compile {:inline, [{valid_char_name, 1}]}
defp unquote(valid_char_name)(char)
when elem({unquote_splicing(decoded)}, char - unquote(min)) != nil,
do: true
defp unquote(name)(string, pad?) do
defp unquote(valid_char_name)(_char), do: false
defp unquote(decode_name)(char) do
index = char - unquote(min)
cond do
index not in 0..unquote(length(decoded) - 1) -> bad_character!(char)
new_char = elem({unquote_splicing(decoded)}, index) -> new_char
true -> bad_character!(char)
end
end
defp unquote(decode_name)(<<>>, _pad?), do: <<>>
defp unquote(decode_name)(string, pad?) do
segs = div(byte_size(string) + 7, 8) - 1
<<main::size(^segs)-binary-unit(64), rest::binary>> = string
main =
for <<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8 <- main>>, into: <<>> do
<<
unquote(name)(c1)::6,
unquote(name)(c2)::6,
unquote(name)(c3)::6,
unquote(name)(c4)::6,
unquote(name)(c5)::6,
unquote(name)(c6)::6,
unquote(name)(c7)::6,
unquote(name)(c8)::6
unquote(decode_name)(c1)::6,
unquote(decode_name)(c2)::6,
unquote(decode_name)(c3)::6,
unquote(decode_name)(c4)::6,
unquote(decode_name)(c5)::6,
unquote(decode_name)(c6)::6,
unquote(decode_name)(c7)::6,
unquote(decode_name)(c8)::6
>>
end
case rest do
<<c1::8, c2::8, ?=, ?=>> ->
<<main::bits, unquote(name)(c1)::6, bsr(unquote(name)(c2), 4)::2>>
<<main::bits, unquote(decode_name)(c1)::6, bsr(unquote(decode_name)(c2), 4)::2>>
<<c1::8, c2::8, c3::8, ?=>> ->
<<main::bits, unquote(name)(c1)::6, unquote(name)(c2)::6, bsr(unquote(name)(c3), 2)::4>>
<<main::bits, unquote(decode_name)(c1)::6, unquote(decode_name)(c2)::6,
bsr(unquote(decode_name)(c3), 2)::4>>
<<c1::8, c2::8, c3::8, c4::8>> ->
<<
main::bits,
unquote(name)(c1)::6,
unquote(name)(c2)::6,
unquote(name)(c3)::6,
unquote(name)(c4)::6
unquote(decode_name)(c1)::6,
unquote(decode_name)(c2)::6,
unquote(decode_name)(c3)::6,
unquote(decode_name)(c4)::6
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, ?=, ?=>> ->
<<
main::bits,
unquote(name)(c1)::6,
unquote(name)(c2)::6,
unquote(name)(c3)::6,
unquote(name)(c4)::6,
unquote(name)(c5)::6,
bsr(unquote(name)(c6), 4)::2
unquote(decode_name)(c1)::6,
unquote(decode_name)(c2)::6,
unquote(decode_name)(c3)::6,
unquote(decode_name)(c4)::6,
unquote(decode_name)(c5)::6,
bsr(unquote(decode_name)(c6), 4)::2
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
<<
main::bits,
unquote(name)(c1)::6,
unquote(name)(c2)::6,
unquote(name)(c3)::6,
unquote(name)(c4)::6,
unquote(name)(c5)::6,
unquote(name)(c6)::6,
bsr(unquote(name)(c7), 2)::4
unquote(decode_name)(c1)::6,
unquote(decode_name)(c2)::6,
unquote(decode_name)(c3)::6,
unquote(decode_name)(c4)::6,
unquote(decode_name)(c5)::6,
unquote(decode_name)(c6)::6,
bsr(unquote(decode_name)(c7), 2)::4
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
<<
main::bits,
unquote(name)(c1)::6,
unquote(name)(c2)::6,
unquote(name)(c3)::6,
unquote(name)(c4)::6,
unquote(name)(c5)::6,
unquote(name)(c6)::6,
unquote(name)(c7)::6,
unquote(name)(c8)::6
unquote(decode_name)(c1)::6,
unquote(decode_name)(c2)::6,
unquote(decode_name)(c3)::6,
unquote(decode_name)(c4)::6,
unquote(decode_name)(c5)::6,
unquote(decode_name)(c6)::6,
unquote(decode_name)(c7)::6,
unquote(decode_name)(c8)::6
>>
<<c1::8, c2::8>> when not pad? ->
<<main::bits, unquote(name)(c1)::6, bsr(unquote(name)(c2), 4)::2>>
<<main::bits, unquote(decode_name)(c1)::6, bsr(unquote(decode_name)(c2), 4)::2>>
<<c1::8, c2::8, c3::8>> when not pad? ->
<<main::bits, unquote(name)(c1)::6, unquote(name)(c2)::6, bsr(unquote(name)(c3), 2)::4>>
<<main::bits, unquote(decode_name)(c1)::6, unquote(decode_name)(c2)::6,
bsr(unquote(decode_name)(c3), 2)::4>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8>> when not pad? ->
<<
main::bits,
unquote(name)(c1)::6,
unquote(name)(c2)::6,
unquote(name)(c3)::6,
unquote(name)(c4)::6,
unquote(name)(c5)::6,
bsr(unquote(name)(c6), 4)::2
unquote(decode_name)(c1)::6,
unquote(decode_name)(c2)::6,
unquote(decode_name)(c3)::6,
unquote(decode_name)(c4)::6,
unquote(decode_name)(c5)::6,
bsr(unquote(decode_name)(c6), 4)::2
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8>> when not pad? ->
<<
main::bits,
unquote(name)(c1)::6,
unquote(name)(c2)::6,
unquote(name)(c3)::6,
unquote(name)(c4)::6,
unquote(name)(c5)::6,
unquote(name)(c6)::6,
bsr(unquote(name)(c7), 2)::4
unquote(decode_name)(c1)::6,
unquote(decode_name)(c2)::6,
unquote(decode_name)(c3)::6,
unquote(decode_name)(c4)::6,
unquote(decode_name)(c5)::6,
unquote(decode_name)(c6)::6,
bsr(unquote(decode_name)(c7), 2)::4
>>
_ ->
@@ -983,6 +1235,45 @@ defmodule Base do
end
end
@doc """
Checks if a base 32 encoded string is valid.
> #### When to use this {: .tip}
>
> Use this function when you just need to *validate* that a string is
> valid base 32 data, without actually producing a decoded output string.
> This function is both more performant and memory efficient than using
> `decode32/2`, checking that the result is `{:ok, ...}`, and then
> discarding the decoded binary.
## Options
Accepts the same options as `decode32/2`.
## Examples
iex> Base.valid32?("MZXW6YTBOI======")
true
iex> Base.valid32?("mzxw6ytboi======", case: :lower)
true
iex> Base.valid32?("zzz")
false
"""
@doc since: "1.19.0"
@spec valid32?(binary, case: decode_case, padding: boolean) :: boolean()
def valid32?(string, opts \\ []) when is_binary(string) do
pad? = Keyword.get(opts, :padding, true)
case Keyword.get(opts, :case, :upper) do
:upper -> validate32upper?(string, pad?)
:lower -> validate32lower?(string, pad?)
:mixed -> validate32mixed?(string, pad?)
end
end
@doc """
Decodes a base 32 encoded string with extended hexadecimal alphabet
into a binary string.
@@ -1078,6 +1369,45 @@ defmodule Base do
end
end
@doc """
Checks if a base 32 encoded string with extended hexadecimal alphabet is valid.
> #### When to use this {: .tip}
>
> Use this function when you just need to *validate* that a string is
> valid (extended hexadecimal) base 32 data, without actually producing
> a decoded output string. This function is both more performant and
> memory efficient than using `hex_decode32/2`, checking that the result
> is `{:ok, ...}`, and then discarding the decoded binary.
## Options
Accepts the same options as `hex_decode32/2`.
## Examples
iex> Base.hex_valid32?("CPNMUOJ1E8======")
true
iex> Base.hex_valid32?("cpnmuoj1e8======", case: :lower)
true
iex> Base.hex_valid32?("zzz", padding: false)
false
"""
@doc since: "1.19.0"
@spec hex_valid32?(binary, case: decode_case, padding: boolean) :: boolean
def hex_valid32?(string, opts \\ []) when is_binary(string) do
pad? = Keyword.get(opts, :padding, true)
case Keyword.get(opts, :case, :upper) do
:upper -> validate32hexupper?(string, pad?)
:lower -> validate32hexlower?(string, pad?)
:mixed -> validate32hexmixed?(string, pad?)
end
end
upper = Enum.with_index(b32_alphabet)
hexupper = Enum.with_index(b32hex_alphabet)
@@ -1089,120 +1419,223 @@ defmodule Base do
hexlower: to_lower_dec.(hexupper),
hexmixed: to_mixed_dec.(hexupper)
] do
name = :"decode32#{base}!"
decode_name = :"decode32#{base}!"
validate_name = :"validate32#{base}?"
validate_main_name = :"validate_main32#{validate_name}?"
valid_char_name = :"valid_char32#{base}?"
{min, decoded} = to_decode_list.(alphabet)
defp unquote(name)(char) do
try do
elem({unquote_splicing(decoded)}, char - unquote(min))
rescue
_ -> bad_character!(char)
else
nil -> bad_character!(char)
char -> char
defp unquote(validate_main_name)(<<>>), do: true
defp unquote(validate_main_name)(
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8, rest::binary>>
) do
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4) and
unquote(valid_char_name)(c5) and
unquote(valid_char_name)(c6) and
unquote(valid_char_name)(c7) and
unquote(valid_char_name)(c8) and
unquote(validate_main_name)(rest)
end
defp unquote(validate_name)(<<>>, _pad?), do: true
defp unquote(validate_name)(string, pad?) do
segs = div(byte_size(string) + 7, 8) - 1
<<main::size(^segs)-binary-unit(64), rest::binary>> = string
main_valid? = unquote(validate_main_name)(main)
case rest do
_ when not main_valid? ->
false
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2)
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4)
<<c1::8, c2::8, c3::8, c4::8, c5::8, ?=, ?=, ?=>> ->
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4) and
unquote(valid_char_name)(c5)
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4) and
unquote(valid_char_name)(c5) and
unquote(valid_char_name)(c6) and
unquote(valid_char_name)(c7)
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4) and
unquote(valid_char_name)(c5) and
unquote(valid_char_name)(c6) and
unquote(valid_char_name)(c7) and
unquote(valid_char_name)(c8)
<<c1::8, c2::8>> when not pad? ->
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2)
<<c1::8, c2::8, c3::8, c4::8>> when not pad? ->
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4)
<<c1::8, c2::8, c3::8, c4::8, c5::8>> when not pad? ->
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4) and
unquote(valid_char_name)(c5)
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8>> when not pad? ->
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4) and
unquote(valid_char_name)(c5) and
unquote(valid_char_name)(c6) and
unquote(valid_char_name)(c7)
_ ->
false
end
end
defp unquote(name)(<<>>, _), do: <<>>
@compile {:inline, [{valid_char_name, 1}]}
defp unquote(valid_char_name)(char)
when elem({unquote_splicing(decoded)}, char - unquote(min)) != nil,
do: true
defp unquote(name)(string, pad?) do
defp unquote(valid_char_name)(_char), do: false
defp unquote(decode_name)(char) do
index = char - unquote(min)
cond do
index not in 0..unquote(length(decoded) - 1) -> bad_character!(char)
new_char = elem({unquote_splicing(decoded)}, index) -> new_char
true -> bad_character!(char)
end
end
defp unquote(decode_name)(<<>>, _), do: <<>>
defp unquote(decode_name)(string, pad?) do
segs = div(byte_size(string) + 7, 8) - 1
<<main::size(^segs)-binary-unit(64), rest::binary>> = string
main =
for <<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8 <- main>>, into: <<>> do
<<
unquote(name)(c1)::5,
unquote(name)(c2)::5,
unquote(name)(c3)::5,
unquote(name)(c4)::5,
unquote(name)(c5)::5,
unquote(name)(c6)::5,
unquote(name)(c7)::5,
unquote(name)(c8)::5
unquote(decode_name)(c1)::5,
unquote(decode_name)(c2)::5,
unquote(decode_name)(c3)::5,
unquote(decode_name)(c4)::5,
unquote(decode_name)(c5)::5,
unquote(decode_name)(c6)::5,
unquote(decode_name)(c7)::5,
unquote(decode_name)(c8)::5
>>
end
case rest do
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
<<main::bits, unquote(name)(c1)::5, bsr(unquote(name)(c2), 2)::3>>
<<main::bits, unquote(decode_name)(c1)::5, bsr(unquote(decode_name)(c2), 2)::3>>
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
<<
main::bits,
unquote(name)(c1)::5,
unquote(name)(c2)::5,
unquote(name)(c3)::5,
bsr(unquote(name)(c4), 4)::1
unquote(decode_name)(c1)::5,
unquote(decode_name)(c2)::5,
unquote(decode_name)(c3)::5,
bsr(unquote(decode_name)(c4), 4)::1
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, ?=, ?=, ?=>> ->
<<
main::bits,
unquote(name)(c1)::5,
unquote(name)(c2)::5,
unquote(name)(c3)::5,
unquote(name)(c4)::5,
bsr(unquote(name)(c5), 1)::4
unquote(decode_name)(c1)::5,
unquote(decode_name)(c2)::5,
unquote(decode_name)(c3)::5,
unquote(decode_name)(c4)::5,
bsr(unquote(decode_name)(c5), 1)::4
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
<<
main::bits,
unquote(name)(c1)::5,
unquote(name)(c2)::5,
unquote(name)(c3)::5,
unquote(name)(c4)::5,
unquote(name)(c5)::5,
unquote(name)(c6)::5,
bsr(unquote(name)(c7), 3)::2
unquote(decode_name)(c1)::5,
unquote(decode_name)(c2)::5,
unquote(decode_name)(c3)::5,
unquote(decode_name)(c4)::5,
unquote(decode_name)(c5)::5,
unquote(decode_name)(c6)::5,
bsr(unquote(decode_name)(c7), 3)::2
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
<<
main::bits,
unquote(name)(c1)::5,
unquote(name)(c2)::5,
unquote(name)(c3)::5,
unquote(name)(c4)::5,
unquote(name)(c5)::5,
unquote(name)(c6)::5,
unquote(name)(c7)::5,
unquote(name)(c8)::5
unquote(decode_name)(c1)::5,
unquote(decode_name)(c2)::5,
unquote(decode_name)(c3)::5,
unquote(decode_name)(c4)::5,
unquote(decode_name)(c5)::5,
unquote(decode_name)(c6)::5,
unquote(decode_name)(c7)::5,
unquote(decode_name)(c8)::5
>>
<<c1::8, c2::8>> when not pad? ->
<<main::bits, unquote(name)(c1)::5, bsr(unquote(name)(c2), 2)::3>>
<<main::bits, unquote(decode_name)(c1)::5, bsr(unquote(decode_name)(c2), 2)::3>>
<<c1::8, c2::8, c3::8, c4::8>> when not pad? ->
<<
main::bits,
unquote(name)(c1)::5,
unquote(name)(c2)::5,
unquote(name)(c3)::5,
bsr(unquote(name)(c4), 4)::1
unquote(decode_name)(c1)::5,
unquote(decode_name)(c2)::5,
unquote(decode_name)(c3)::5,
bsr(unquote(decode_name)(c4), 4)::1
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8>> when not pad? ->
<<
main::bits,
unquote(name)(c1)::5,
unquote(name)(c2)::5,
unquote(name)(c3)::5,
unquote(name)(c4)::5,
bsr(unquote(name)(c5), 1)::4
unquote(decode_name)(c1)::5,
unquote(decode_name)(c2)::5,
unquote(decode_name)(c3)::5,
unquote(decode_name)(c4)::5,
bsr(unquote(decode_name)(c5), 1)::4
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8>> when not pad? ->
<<
main::bits,
unquote(name)(c1)::5,
unquote(name)(c2)::5,
unquote(name)(c3)::5,
unquote(name)(c4)::5,
unquote(name)(c5)::5,
unquote(name)(c6)::5,
bsr(unquote(name)(c7), 3)::2
unquote(decode_name)(c1)::5,
unquote(decode_name)(c2)::5,
unquote(decode_name)(c3)::5,
unquote(decode_name)(c4)::5,
unquote(decode_name)(c5)::5,
unquote(decode_name)(c6)::5,
bsr(unquote(decode_name)(c7), 3)::2
>>
_ ->
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Behaviour do
@moduledoc """
Mechanism for handling behaviours.
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Bitwise do
@moduledoc """
A set of functions that perform calculations on bits.
+102 -21
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Calendar do
@moduledoc """
This module defines the responsibilities for working with
@@ -54,9 +58,20 @@ defmodule Calendar do
@typedoc """
Microseconds with stored precision.
The precision represents the number of digits that must be used when
`value` always represents the total value in microseconds.
The `precision` represents the number of digits that must be used when
representing the microseconds to external format. If the precision is `0`,
it means microseconds must be skipped.
it means microseconds must be skipped. If the precision is `6`, it means
that `value` represents exactly the number of microseconds to be used.
## Examples
* `{0, 0}` means no microseconds.
* `{1, 6}` means 1µs.
* `{1000, 6}` means 1000µs (which is 1ms but measured at the microsecond precision).
* `{1000, 3}` means 1ms (which is measured at the millisecond precision).
"""
@type microsecond :: {value :: non_neg_integer, precision :: non_neg_integer}
@@ -147,6 +162,22 @@ defmodule Calendar do
"""
@type time_zone_database :: module()
@typedoc """
Options for formatting dates and times with `strftime/3`.
"""
@type strftime_opts :: [
preferred_datetime: String.t(),
preferred_date: String.t(),
preferred_time: String.t(),
am_pm_names: (:am | :pm -> String.t()) | (:am | :pm, map() -> String.t()),
month_names: (pos_integer() -> String.t()) | (pos_integer(), map() -> String.t()),
abbreviated_month_names:
(pos_integer() -> String.t()) | (pos_integer(), map() -> String.t()),
day_of_week_names: (pos_integer() -> String.t()) | (pos_integer(), map() -> String.t()),
abbreviated_day_of_week_names:
(pos_integer() -> String.t()) | (pos_integer(), map() -> String.t())
]
@doc """
Returns how many days there are in the given month of the given year.
"""
@@ -172,6 +203,15 @@ defmodule Calendar do
`starting_on` represents the starting day of the week. All
calendars must support at least the `:default` value. They may
also support other values representing their days of the week.
The value of `day_of_week` is an ordinal number meaning that a
value of `1` is defined to mean "first day of the week". It is
specifically not defined to mean `1` is `Monday`.
It is a requirement that `first_day_of_week` is less than `last_day_of_week`
and that `day_of_week` must be within that range. Therefore it can be said
that `day_of_week in first_day_of_week..last_day_of_week//1` must be
`true` for all values of `day_of_week`.
"""
@callback day_of_week(year, month, day, starting_on :: :default | atom) ::
{day_of_week(), first_day_of_week :: non_neg_integer(),
@@ -452,25 +492,30 @@ defmodule Calendar do
it can't contain the `%X` format and defaults to `"%H:%M:%S"`
if the option is not received
* `:am_pm_names` - a function that receives either `:am` or `:pm` and returns
* `:am_pm_names` - a function that receives either `:am` or `:pm`
(and also the datetime if the function is arity/2) and returns
the name of the period of the day, if the option is not received it defaults
to a function that returns `"am"` and `"pm"`, respectively
* `:month_names` - a function that receives a number and returns the name of
* `:month_names` - a function that receives a number (and also the
datetime if the function is arity/2) and returns the name of
the corresponding month, if the option is not received it defaults to a
function that returns the month names in English
* `:abbreviated_month_names` - a function that receives a number and returns the
* `:abbreviated_month_names` - a function that receives a number (and also
the datetime if the function is arity/2) and returns the
abbreviated name of the corresponding month, if the option is not received it
defaults to a function that returns the abbreviated month names in English
* `:day_of_week_names` - a function that receives a number and returns the name of
* `:day_of_week_names` - a function that receives a number and (and also the
datetime if the function is arity/2) returns the name of
the corresponding day of week, if the option is not received it defaults to a
function that returns the day of week names in English
* `:abbreviated_day_of_week_names` - a function that receives a number and returns
the abbreviated name of the corresponding day of week, if the option is not received
it defaults to a function that returns the abbreviated day of week names in English
* `:abbreviated_day_of_week_names` - a function that receives a number (and also
the datetime if the function is arity/2) and returns the abbreviated name of
the corresponding day of week, if the option is not received it defaults to a
function that returns the abbreviated day of week names in English
## Formatting syntax
@@ -504,7 +549,7 @@ defmodule Calendar do
B | Full month name | January
c | Preferred date+time representation | 2018-10-17 12:34:56
d | Day of the month | 01, 31
f | Microseconds *(does not support width and padding modifiers)* | 000000, 999999, 0123
f | Microseconds (uses its precision for width and padding) | 000000, 999999, 0123
H | Hour using a 24-hour clock | 00, 23
I | Hour using a 12-hour clock | 01, 12
j | Day of the year | 001, 366
@@ -526,6 +571,12 @@ defmodule Calendar do
Any other character will be interpreted as an invalid format and raise an error.
### `%f` Microseconds
`%f` does not support width and padding modifiers. It will be formatted by truncating
the microseconds to the precision of the `microseconds` field of the struct, with a
minimum precision of 1.
## Examples
Without user options:
@@ -569,9 +620,20 @@ defmodule Calendar do
...>)
"серпень"
Microsecond formatting:
iex> Calendar.strftime(~U[2019-08-26 13:52:06Z], "%y-%m-%d %H:%M:%S.%f")
"19-08-26 13:52:06.0"
iex> Calendar.strftime(~U[2019-08-26 13:52:06.048Z], "%y-%m-%d %H:%M:%S.%f")
"19-08-26 13:52:06.048"
iex> Calendar.strftime(~U[2019-08-26 13:52:06.048531Z], "%y-%m-%d %H:%M:%S.%f")
"19-08-26 13:52:06.048531"
"""
@doc since: "1.11.0"
@spec strftime(map(), String.t(), keyword()) :: String.t()
@spec strftime(map(), String.t(), strftime_opts()) :: String.t()
def strftime(date_or_time_or_datetime, string_format, user_options \\ [])
when is_map(date_or_time_or_datetime) and is_binary(string_format) do
parse(
@@ -624,12 +686,12 @@ defmodule Calendar do
format_modifiers(rest, width, pad, datetime, format_options, acc)
end
defp am_pm(hour, format_options) when hour > 11 do
format_options.am_pm_names.(:pm)
defp am_pm(hour, format_options, datetime) when hour > 11 do
apply_format(:pm, format_options.am_pm_names, datetime)
end
defp am_pm(hour, format_options) when hour <= 11 do
format_options.am_pm_names.(:am)
defp am_pm(hour, format_options, datetime) when hour <= 11 do
apply_format(:am, format_options.am_pm_names, datetime)
end
defp default_pad(format) when format in ~c"aAbBpPZ", do: ?\s
@@ -650,7 +712,7 @@ defmodule Calendar do
result =
datetime
|> Date.day_of_week()
|> format_options.abbreviated_day_of_week_names.()
|> apply_format(format_options.abbreviated_day_of_week_names, datetime)
|> pad_leading(width, pad)
parse(rest, datetime, format_options, [result | acc])
@@ -661,7 +723,7 @@ defmodule Calendar do
result =
datetime
|> Date.day_of_week()
|> format_options.day_of_week_names.()
|> apply_format(format_options.day_of_week_names, datetime)
|> pad_leading(width, pad)
parse(rest, datetime, format_options, [result | acc])
@@ -671,7 +733,7 @@ defmodule Calendar do
defp format_modifiers("b" <> rest, width, pad, datetime, format_options, acc) do
result =
datetime.month
|> format_options.abbreviated_month_names.()
|> apply_format(format_options.abbreviated_month_names, datetime)
|> pad_leading(width, pad)
parse(rest, datetime, format_options, [result | acc])
@@ -679,7 +741,10 @@ defmodule Calendar do
# Full month name
defp format_modifiers("B" <> rest, width, pad, datetime, format_options, acc) do
result = datetime.month |> format_options.month_names.() |> pad_leading(width, pad)
result =
datetime.month
|> apply_format(format_options.month_names, datetime)
|> pad_leading(width, pad)
parse(rest, datetime, format_options, [result | acc])
end
@@ -757,7 +822,11 @@ defmodule Calendar do
# "AM" or "PM" (noon is "PM", midnight as "AM")
defp format_modifiers("p" <> rest, width, pad, datetime, format_options, acc) do
result = datetime.hour |> am_pm(format_options) |> String.upcase() |> pad_leading(width, pad)
result =
datetime.hour
|> am_pm(format_options, datetime)
|> String.upcase()
|> pad_leading(width, pad)
parse(rest, datetime, format_options, [result | acc])
end
@@ -766,7 +835,7 @@ defmodule Calendar do
defp format_modifiers("P" <> rest, width, pad, datetime, format_options, acc) do
result =
datetime.hour
|> am_pm(format_options)
|> am_pm(format_options, datetime)
|> String.downcase()
|> pad_leading(width, pad)
@@ -932,6 +1001,18 @@ defmodule Calendar do
defp do_pad_leading(count, padding, acc),
do: do_pad_leading(count - 1, padding, [padding | acc])
defp apply_format(term, formatter, _datetime) when is_function(formatter, 1) do
formatter.(term)
end
defp apply_format(term, formatter, datetime) when is_function(formatter, 2) do
formatter.(term, datetime)
end
defp apply_format(_term, formatter, _datetime) do
raise ArgumentError, "formatter functions must be of arity 1 or 2, got: #{inspect(formatter)}"
end
defp options(user_options) do
default_options = %{
preferred_date: "%Y-%m-%d",
+35 -16
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Date do
@moduledoc """
A Date struct and functions.
@@ -31,11 +35,12 @@ defmodule Date do
Comparisons in Elixir using `==/2`, `>/2`, `</2` and similar are structural
and based on the `Date` struct fields. For proper comparison between
dates, use the `compare/2` function. The existence of the `compare/2`
function in this module also allows using `Enum.min/2` and `Enum.max/2`
functions to get the minimum and maximum date of an `Enum`. For example:
dates, use the `compare/2`, `after?/2` and `before?/2` functions.
The existence of the `compare/2` function in this module also allows
using `Enum.min/2` and `Enum.max/2` functions to get the minimum and
maximum date of an `Enum`. For example:
iex> Enum.min([~D[2017-03-31], ~D[2017-04-01]], Date)
iex> Enum.min([~D[2017-03-31], ~D[2017-04-01]], Date)
~D[2017-03-31]
## Using epochs
@@ -188,9 +193,8 @@ defmodule Date do
end
def utc_today(calendar) do
calendar
|> DateTime.utc_now()
|> DateTime.to_date()
%{year: year, month: month, day: day} = DateTime.utc_now(calendar)
%Date{year: year, month: month, day: day, calendar: calendar}
end
@doc """
@@ -317,7 +321,7 @@ defmodule Date do
@doc """
Converts the given date to a string according to its calendar.
### Examples
## Examples
iex> Date.to_string(~D[2000-02-28])
"2000-02-28"
@@ -395,7 +399,7 @@ defmodule Date do
or other calendars in which the days also start at midnight.
Attempting to convert dates from other calendars will raise an `ArgumentError`.
### Examples
## Examples
iex> Date.to_iso8601(~D[2000-02-28])
"2000-02-28"
@@ -629,7 +633,7 @@ defmodule Date do
## Examples
Imagine someone implements `Calendar.Holocene`, a calendar based on the
Gregorian calendar that adds exactly 10,000 years to the current Gregorian
Gregorian calendar that adds exactly 10 000 years to the current Gregorian
year:
iex> Date.convert(~D[2000-01-01], Calendar.Holocene)
@@ -663,7 +667,7 @@ defmodule Date do
## Examples
Imagine someone implements `Calendar.Holocene`, a calendar based on the
Gregorian calendar that adds exactly 10,000 years to the current Gregorian
Gregorian calendar that adds exactly 10 000 years to the current Gregorian
year:
iex> Date.convert!(~D[2000-01-01], Calendar.Holocene)
@@ -687,10 +691,15 @@ defmodule Date do
@doc """
Adds the number of days to the given `date`.
The days are counted as Gregorian days. The date is returned in the same
calendar as it was given in.
> #### Prefer `shift/2` {: .info}
>
> Prefer `shift/2` over `add/2`, as it offers a more ergonomic API.
>
> `add/2` always considers a day to be measured according to the
> `Calendar.ISO`.
To shift a date by a `Duration` and according to its underlying calendar, use `Date.shift/2`.
The days are counted as Gregorian days, independent of the underlying
calendar. The date is returned in the same calendar as it was given in.
## Examples
@@ -852,7 +861,7 @@ defmodule Date do
end
@doc """
Calculates the day of the week of a given `date`.
Calculates the ordinal day of the week of a given `date`.
Returns the day of the week as an integer. For the ISO 8601
calendar (the default), it is an integer from 1 to 7, where
@@ -861,10 +870,19 @@ defmodule Date do
An optional `starting_on` value may be supplied, which
configures the weekday the week starts on. The default value
for it is `:default`, which translates to `:monday` for the
built-in ISO calendar. Any other weekday may be given to.
built-in ISO 8601 calendar. Any other weekday may be used for
`starting_on`, in such cases, that weekday will be considered the first
day of the week, and therefore it will be assigned the ordinal number 1.
The other calendars, the value returned is an ordinal day of week.
For example, `1` may mean "first day of the week" and `7` is
defined to mean "seventh day of the week". Custom calendars may
also accept their own variations of the `starting_on` parameter
with their own meaning.
## Examples
# 2016-10-31 is a Monday and by default Monday is the first day of the week
iex> Date.day_of_week(~D[2016-10-31])
1
iex> Date.day_of_week(~D[2016-11-01])
@@ -874,6 +892,7 @@ defmodule Date do
iex> Date.day_of_week(~D[-0015-10-30])
3
# 2016-10-31 is a Monday but, as we start the week on Sunday, now it returns 2
iex> Date.day_of_week(~D[2016-10-31], :sunday)
2
iex> Date.day_of_week(~D[2016-11-01], :sunday)
+11 -3
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Date.Range do
@moduledoc """
Returns an inclusive range between dates.
@@ -91,7 +95,7 @@ defmodule Date.Range do
[date_from_iso_days(current, calendar)]
end
defp slice(current, step, remaining, calendar) do
defp slice(current, step, remaining, calendar) when remaining > 1 do
[
date_from_iso_days(current, calendar)
| slice(current + step, step, remaining - 1, calendar)
@@ -167,8 +171,12 @@ defmodule Date.Range do
when step < 0 and first_days < last_days,
do: 0
defp size(%Date.Range{first_in_iso_days: first_days, last_in_iso_days: last_days, step: step}),
do: abs(div(last_days - first_days, step)) + 1
defp size(%Date.Range{
first_in_iso_days: first_days,
last_in_iso_days: last_days,
step: step
}),
do: abs(div(last_days - first_days, step)) + 1
# TODO: Remove me on v2.0
defp size(
+118 -60
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule DateTime do
@moduledoc """
A datetime implementation with a time zone.
@@ -13,8 +17,8 @@ defmodule DateTime do
Remember, comparisons in Elixir using `==/2`, `>/2`, `</2` and friends
are structural and based on the DateTime struct fields. For proper
comparison between datetimes, use the `compare/2` function. The
existence of the `compare/2` function in this module also allows
comparison between datetimes, use the `compare/2`, `after?/2` and `before?/2` functions.
The existence of the `compare/2` function in this module also allows
using `Enum.min/2` and `Enum.max/2` functions to get the minimum and
maximum datetime of an `Enum`. For example:
@@ -379,13 +383,12 @@ defmodule DateTime do
@doc """
Converts the given Unix time to `DateTime`.
The integer can be given in different unit
according to `System.convert_time_unit/3` and it will
be converted to microseconds internally. Up to
253402300799 seconds is supported.
The integer can be given in different unit, according to `System.convert_time_unit/3`,
and it will be converted to microseconds internally, which is the maximum precision
supported by `DateTime`. In other words, any precision higher than microseconds will
lead to truncation.
Unix times are always in UTC and therefore the DateTime
will be returned in UTC.
Unix times are always in UTC. Therefore the DateTime will be returned in UTC.
## Examples
@@ -881,8 +884,10 @@ defmodule DateTime do
The `datetime` is expected to be using the ISO calendar
with a year greater than or equal to 0.
It will return the integer with the given unit,
according to `System.convert_time_unit/3`.
It will return the integer with the given unit, according
to `System.convert_time_unit/3`. If the given unit is different
than microseconds, the returned value will be either truncated
or padded accordingly.
## Examples
@@ -1041,7 +1046,7 @@ defmodule DateTime do
its abbreviation, which means information is lost when converting to such
format.
### Examples
## Examples
iex> dt = %DateTime{year: 2000, month: 2, day: 29, zone_abbr: "CET",
...> hour: 23, minute: 0, second: 7, microsecond: {0, 0},
@@ -1091,8 +1096,21 @@ defmodule DateTime do
@spec to_iso8601(Calendar.datetime(), :basic | :extended, nil | integer()) :: String.t()
def to_iso8601(datetime, format \\ :extended, offset \\ nil)
def to_iso8601(%{calendar: Calendar.ISO} = datetime, format, nil)
def to_iso8601(%{calendar: Calendar.ISO} = datetime, format, offset)
when format in [:extended, :basic] do
datetime
|> to_iso8601_iodata(format, offset)
|> IO.iodata_to_binary()
end
def to_iso8601(%{calendar: _} = datetime, format, offset)
when format in [:extended, :basic] do
datetime
|> convert!(Calendar.ISO)
|> to_iso8601(format, offset)
end
defp to_iso8601_iodata(datetime, format, nil) do
%{
year: year,
month: month,
@@ -1106,35 +1124,51 @@ defmodule DateTime do
std_offset: std_offset
} = datetime
datetime_to_string(year, month, day, hour, minute, second, microsecond, format) <>
Calendar.ISO.offset_to_string(utc_offset, std_offset, time_zone, format)
[
datetime_to_iodata(year, month, day, hour, minute, second, microsecond, format),
Calendar.ISO.offset_to_iodata(utc_offset, std_offset, time_zone, format)
]
end
def to_iso8601(
%{calendar: Calendar.ISO, microsecond: {_, precision}, time_zone: "Etc/UTC"} = datetime,
format,
0
)
when format in [:extended, :basic] do
defp to_iso8601_iodata(
%{microsecond: {_, precision}, time_zone: "Etc/UTC"} = datetime,
format,
0
) do
{year, month, day, hour, minute, second, {microsecond, _}} = shift_by_offset(datetime, 0)
datetime_to_string(year, month, day, hour, minute, second, {microsecond, precision}, format) <>
"Z"
[
datetime_to_iodata(
year,
month,
day,
hour,
minute,
second,
{microsecond, precision},
format
),
?Z
]
end
def to_iso8601(%{calendar: Calendar.ISO} = datetime, format, offset)
when format in [:extended, :basic] do
defp to_iso8601_iodata(datetime, format, offset) do
{_, precision} = datetime.microsecond
{year, month, day, hour, minute, second, {microsecond, _}} = shift_by_offset(datetime, offset)
datetime_to_string(year, month, day, hour, minute, second, {microsecond, precision}, format) <>
Calendar.ISO.offset_to_string(offset, 0, nil, format)
end
def to_iso8601(%{calendar: _} = datetime, format, offset) when format in [:extended, :basic] do
datetime
|> convert!(Calendar.ISO)
|> to_iso8601(format, offset)
[
datetime_to_iodata(
year,
month,
day,
hour,
minute,
second,
{microsecond, precision},
format
),
Calendar.ISO.offset_to_iodata(offset, 0, nil, format)
]
end
defp shift_by_offset(%{calendar: calendar} = datetime, offset) do
@@ -1147,10 +1181,12 @@ defmodule DateTime do
|> calendar.naive_datetime_from_iso_days()
end
defp datetime_to_string(year, month, day, hour, minute, second, microsecond, format) do
Calendar.ISO.date_to_string(year, month, day, format) <>
"T" <>
Calendar.ISO.time_to_string(hour, minute, second, microsecond, format)
defp datetime_to_iodata(year, month, day, hour, minute, second, microsecond, format) do
[
Calendar.ISO.date_to_iodata(year, month, day, format),
?T,
Calendar.ISO.time_to_iodata(hour, minute, second, microsecond, format)
]
end
@doc """
@@ -1354,7 +1390,7 @@ defmodule DateTime do
custom (but relatively common) representation which appends the time
zone abbreviation and full name to the datetime.
### Examples
## Examples
iex> dt = %DateTime{year: 2000, month: 2, day: 29, zone_abbr: "CET",
...> hour: 23, minute: 0, second: 7, microsecond: {0, 0},
@@ -1563,42 +1599,56 @@ defmodule DateTime do
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))
(datetime1 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(:microsecond)) -
(datetime2 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(:microsecond))
offset_diff = utc_offset2 + std_offset2 - (utc_offset1 + std_offset1)
naive_diff + System.convert_time_unit(offset_diff, :second, unit)
System.convert_time_unit(naive_diff, :microsecond, unit) +
System.convert_time_unit(offset_diff, :second, unit)
end
@doc """
Adds a specified amount of time to a `DateTime`.
> #### Prefer `shift/2` {: .info}
>
> Prefer `shift/2` over `add/3`, as it offers a more ergonomic API.
>
> `add/3` provides a lower-level API which only supports fixed units
> such as `:hour` and `:second`, but not `:month` (as the exact length
> of a month depends on the current month). `add/3` always considers
> the unit to be computed according to the `Calendar.ISO`.
Accepts an `amount_to_add` in any `unit`. `unit` can be `:day`,
`:hour`, `:minute`, `:second` or any subsecond precision from
`t:System.time_unit/0`. It defaults to `:second`. Negative values
will move backwards in time.
This function always considers the unit to be computed according
to the `Calendar.ISO`.
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,
so the elapsed time is precisely 24 hours. Similarly, adding just
a few seconds to a datetime just before "spring forward" can cause
wall time to increase by more than an hour.
ignoring 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, so the elapsed time is precisely
24 hours. Similarly, adding just a few seconds to a datetime just before
"spring forward" can cause wall time to increase by more than an hour.
While this means this function is precise in terms of elapsed time,
its result may be misleading in certain use cases. For example, if a
its result may be confusing in certain use cases. For example, if a
user requests a meeting to happen every day at 15:00 and you use this
function to compute all future meetings by adding day after day, this
function may change the meeting time to 14:00 or 16:00 if there are
changes to the current timezone. Computing of recurring datetimes is
not currently supported in Elixir's standard library but it is available
by third-party libraries.
changes to the current timezone.
### Examples
In case you don't want these changes to happen automatically or you
want to surface time zone conflicts to the user, you can add to
the datetime as a naive datetime and then use `from_naive/2`:
dt |> NaiveDateTime.add(1, :day) |> DateTime.from_naive(dt.time_zone)
The above will surface time jumps and ambiguous datetimes, allowing you
to deal with them accordingly.
## Examples
iex> dt = DateTime.from_naive!(~N[2018-11-15 10:00:00], "Europe/Copenhagen", FakeTimeZoneDatabase)
iex> dt |> DateTime.add(3600, :second, FakeTimeZoneDatabase)
@@ -1626,8 +1676,6 @@ defmodule DateTime do
iex> result.microsecond
{21000, 3}
To shift a datetime by a `Duration` and according to its underlying calendar, use `DateTime.shift/3`.
"""
@doc since: "1.8.0"
@spec add(
@@ -1701,7 +1749,7 @@ defmodule DateTime do
to UTC, and finally computing the new timezone in case of shifts.
This ensures `shift/3` always returns a valid datetime.
On the other hand, time zones that observe "Daylight Saving Time"
Consequently, time zones that observe "Daylight Saving Time"
or other changes, across summer/winter time will add/remove hours
from the resulting datetime:
@@ -1713,12 +1761,22 @@ defmodule DateTime do
DateTime.shift(dt, hour: 2)
#=> #DateTime<2018-11-04 01:00:00-08:00 PST America/Los_Angeles>
Although the first example shows a difference of 2 hours when
comparing the wall clocks of the given datetime with the returned one,
due to the "spring forward" time jump, the actual elapsed time is
still exactly of 1 hour.
In case you don't want these changes to happen automatically or you
want to surface time zone conflicts to the user, you can shift
the datetime as a naive datetime and then use `from_naive/2`:
dt |> NaiveDateTime.shift(duration) |> DateTime.from_naive(dt.time_zone)
The above will surface time jumps and ambiguous datetimes, allowing you
to deal with them accordingly.
## ISO calendar considerations
When using the default ISO calendar, durations are collapsed and
applied in the order of months, then seconds and microseconds:
@@ -1884,7 +1942,7 @@ defmodule DateTime do
## Examples
Imagine someone implements `Calendar.Holocene`, a calendar based on the
Gregorian calendar that adds exactly 10,000 years to the current Gregorian
Gregorian calendar that adds exactly 10 000 years to the current Gregorian
year:
iex> dt1 = %DateTime{year: 2000, month: 2, day: 29, zone_abbr: "AMT",
@@ -1913,7 +1971,7 @@ defmodule DateTime do
if Calendar.compatible_calendars?(dt_calendar, calendar) do
result_datetime =
datetime
|> to_iso_days
|> to_iso_days()
|> from_iso_days(datetime, calendar, precision)
{:ok, result_datetime}
@@ -1931,7 +1989,7 @@ defmodule DateTime do
## Examples
Imagine someone implements `Calendar.Holocene`, a calendar based on the
Gregorian calendar that adds exactly 10,000 years to the current Gregorian
Gregorian calendar that adds exactly 10 000 years to the current Gregorian
year:
iex> dt1 = %DateTime{year: 2000, month: 2, day: 29, zone_abbr: "AMT",
+47 -3
View File
@@ -1,3 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
defmodule Duration do
@moduledoc """
Struct and functions for handling durations.
@@ -88,6 +91,30 @@ defmodule Duration do
The second example consistently points to the last day of the month,
as it performs operations on the duration, rather than shifting date
after date.
## Comparing durations
In order to accurately compare durations, you need to either compare
only certain fields or use a reference time instant. This is because
some fields are relative to others. For example, you may say that
1 month is the same as 30 days, but if you add both of these durations
to `~D[2015-02-01]`, you would get different results, as that month
has only 28 days.
Therefore, if you wish to compare durations, one option is to use
`Date.shift/2` (or `DateTime.shift/2` or similar), and then compare
the dates:
iex> date = ~D[2015-02-01]
iex> Date.compare(Date.shift(date, month: 1), Date.shift(date, day: 30))
:lt
Or alternatively convert the durations to a fixed unit by using `to_timeout/1`,
which supports durations only up to weeks, raising if it has the month or year
fields set.
iex> to_timeout(hour: 24) == to_timeout(day: 1)
true
"""
@moduledoc since: "1.17.0"
@@ -113,7 +140,7 @@ defmodule Duration do
hour: integer,
minute: integer,
second: integer,
microsecond: {integer, 0..6}
microsecond: Calendar.microsecond()
}
@typedoc """
@@ -127,13 +154,29 @@ defmodule Duration do
| {:hour, integer}
| {:minute, integer}
| {:second, integer}
| {:microsecond, {integer, 0..6}}
| {:microsecond, Calendar.microsecond()}
@typedoc """
The duration type specifies a `%Duration{}` struct or a keyword list of valid duration unit pairs.
"""
@type duration :: t | [unit_pair]
@typedoc """
Options for `Duration.to_string/2`.
"""
@type to_string_opts :: [
units: [
year: String.t(),
month: String.t(),
week: String.t(),
day: String.t(),
hour: String.t(),
minute: String.t(),
second: String.t()
],
separator: String.t()
]
@microseconds_per_second 1_000_000
@doc """
@@ -409,6 +452,7 @@ defmodule Duration do
"""
@doc since: "1.18.0"
@spec to_string(t, to_string_opts) :: String.t()
def to_string(%Duration{} = duration, opts \\ []) do
units = Keyword.get(opts, :units, [])
separator = Keyword.get(opts, :separator, " ")
@@ -532,7 +576,7 @@ defmodule Duration do
sign,
Integer.to_string(second),
?.,
ms |> Integer.to_string() |> String.pad_leading(6, "0") |> binary_part(0, p)
Calendar.ISO.microseconds_to_iodata(ms, p)
]
end
+264 -57
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Calendar.ISO do
@moduledoc """
The default calendar implementation, a Gregorian calendar following ISO 8601.
@@ -1220,6 +1224,37 @@ defmodule Calendar.ISO do
:basic | :extended
) :: String.t()
def time_to_string(
hour,
minute,
second,
microsecond,
format \\ :extended
) do
time_to_iodata(hour, minute, second, microsecond, format)
|> IO.iodata_to_binary()
end
@doc """
Converts the given time into a iodata.
See `time_to_string/5` for more information.
## Examples
iex> data = Calendar.ISO.time_to_iodata(2, 2, 2, {2, 6})
iex> IO.iodata_to_binary(data)
"02:02:02.000002"
"""
@doc since: "1.19.0"
@spec time_to_iodata(
Calendar.hour(),
Calendar.minute(),
Calendar.second(),
Calendar.microsecond(),
:basic | :extended
) :: iodata
def time_to_iodata(
hour,
minute,
second,
@@ -1228,24 +1263,43 @@ defmodule Calendar.ISO do
)
when is_hour(hour) and is_minute(minute) and is_second(second) and
is_microsecond(ms_value, ms_precision) and format in [:basic, :extended] do
time_to_string_guarded(hour, minute, second, microsecond, format)
time_to_iodata_guarded(hour, minute, second, microsecond, format)
end
defp time_to_string_guarded(hour, minute, second, {_, 0}, format) do
time_to_string_format(hour, minute, second, format)
defp time_to_iodata_guarded(hour, minute, second, {_, 0}, format) do
time_to_iodata_format(hour, minute, second, format)
end
defp time_to_string_guarded(hour, minute, second, {microsecond, precision}, format) do
time_to_string_format(hour, minute, second, format) <>
"." <> (microsecond |> zero_pad(6) |> binary_part(0, precision))
defp time_to_iodata_guarded(hour, minute, second, {microsecond, precision}, format) do
[
time_to_iodata_format(hour, minute, second, format),
?.
| microseconds_to_iodata(microsecond, precision)
]
end
defp time_to_string_format(hour, minute, second, :extended) do
zero_pad(hour, 2) <> ":" <> zero_pad(minute, 2) <> ":" <> zero_pad(second, 2)
@doc false
def microseconds_to_iodata(_microsecond, 0), do: []
def microseconds_to_iodata(microsecond, 6), do: zero_pad(microsecond, 6)
def microseconds_to_iodata(microsecond, precision) do
num = div(microsecond, scale_factor(precision))
zero_pad(num, precision)
end
defp time_to_string_format(hour, minute, second, :basic) do
zero_pad(hour, 2) <> zero_pad(minute, 2) <> zero_pad(second, 2)
defp scale_factor(1), do: 100_000
defp scale_factor(2), do: 10_000
defp scale_factor(3), do: 1_000
defp scale_factor(4), do: 100
defp scale_factor(5), do: 10
defp scale_factor(6), do: 1
defp time_to_iodata_format(hour, minute, second, :extended) do
[zero_pad(hour, 2), ?:, zero_pad(minute, 2), ?: | zero_pad(second, 2)]
end
defp time_to_iodata_format(hour, minute, second, :basic) do
[zero_pad(hour, 2), zero_pad(minute, 2) | zero_pad(second, 2)]
end
@doc """
@@ -1273,18 +1327,36 @@ defmodule Calendar.ISO do
@doc since: "1.4.0"
@spec date_to_string(year, month, day, :basic | :extended) :: String.t()
@impl true
def date_to_string(year, month, day, format \\ :extended)
def date_to_string(year, month, day, format \\ :extended) do
date_to_iodata(year, month, day, format)
|> IO.iodata_to_binary()
end
@doc """
Converts the given date into a iodata.
See `date_to_string/4` for more information.
## Examples
iex> data = Calendar.ISO.date_to_iodata(2015, 2, 28)
iex> IO.iodata_to_binary(data)
"2015-02-28"
"""
@doc since: "1.19.0"
@spec date_to_iodata(year, month, day, :basic | :extended) :: iodata
def date_to_iodata(year, month, day, format \\ :extended)
when is_integer(year) and is_integer(month) and is_integer(day) and
format in [:basic, :extended] do
date_to_string_guarded(year, month, day, format)
date_to_iodata_guarded(year, month, day, format)
end
defp date_to_string_guarded(year, month, day, :extended) do
zero_pad(year, 4) <> "-" <> zero_pad(month, 2) <> "-" <> zero_pad(day, 2)
defp date_to_iodata_guarded(year, month, day, :extended) do
[zero_pad(year, 4), ?-, zero_pad(month, 2), ?- | zero_pad(day, 2)]
end
defp date_to_string_guarded(year, month, day, :basic) do
zero_pad(year, 4) <> zero_pad(month, 2) <> zero_pad(day, 2)
defp date_to_iodata_guarded(year, month, day, :basic) do
[zero_pad(year, 4), zero_pad(month, 2) | zero_pad(day, 2)]
end
@doc """
@@ -1327,8 +1399,61 @@ defmodule Calendar.ISO do
microsecond,
format \\ :extended
) do
date_to_string(year, month, day, format) <>
" " <> time_to_string(hour, minute, second, microsecond, format)
naive_datetime_to_iodata(
year,
month,
day,
hour,
minute,
second,
microsecond,
format
)
|> IO.iodata_to_binary()
end
@doc """
Converts the given naive_datetime into a iodata.
See `naive_datetime_to_iodata/8` for more information.
## Examples
iex> data = Calendar.ISO.naive_datetime_to_iodata(2015, 2, 28, 1, 2, 3, {4, 6}, :basic)
iex> IO.iodata_to_binary(data)
"20150228 010203.000004"
iex> data = Calendar.ISO.naive_datetime_to_iodata(2015, 2, 28, 1, 2, 3, {4, 6}, :extended)
iex> IO.iodata_to_binary(data)
"2015-02-28 01:02:03.000004"
"""
@doc since: "1.19.0"
@spec naive_datetime_to_iodata(
year,
month,
day,
Calendar.hour(),
Calendar.minute(),
Calendar.second(),
Calendar.microsecond(),
:basic | :extended
) :: iodata
def naive_datetime_to_iodata(
year,
month,
day,
hour,
minute,
second,
microsecond,
format \\ :extended
) do
[
date_to_iodata(year, month, day, format),
?\s
| time_to_iodata(hour, minute, second, microsecond, format)
]
end
@doc """
@@ -1394,20 +1519,89 @@ defmodule Calendar.ISO do
utc_offset,
std_offset,
format \\ :extended
) do
datetime_to_iodata(
year,
month,
day,
hour,
minute,
second,
microsecond,
time_zone,
zone_abbr,
utc_offset,
std_offset,
format
)
|> IO.iodata_to_binary()
end
@doc """
Converts the given datetime into a iodata.
See `datetime_to_iodata/12` for more information.
## Examples
iex> time_zone = "Etc/UTC"
iex> data = Calendar.ISO.datetime_to_iodata(2017, 8, 1, 1, 2, 3, {4, 5}, time_zone, "UTC", 0, 0)
iex> IO.iodata_to_binary(data)
"2017-08-01 01:02:03.00000Z"
"""
@doc since: "1.19.0"
@spec datetime_to_iodata(
year,
month,
day,
Calendar.hour(),
Calendar.minute(),
Calendar.second(),
Calendar.microsecond(),
Calendar.time_zone(),
Calendar.zone_abbr(),
Calendar.utc_offset(),
Calendar.std_offset(),
:basic | :extended
) :: iodata
def datetime_to_iodata(
year,
month,
day,
hour,
minute,
second,
microsecond,
time_zone,
zone_abbr,
utc_offset,
std_offset,
format \\ :extended
)
when is_time_zone(time_zone) and is_zone_abbr(zone_abbr) and is_utc_offset(utc_offset) and
is_std_offset(std_offset) do
date_to_string(year, month, day, format) <>
" " <>
time_to_string(hour, minute, second, microsecond, format) <>
offset_to_string(utc_offset, std_offset, time_zone, format) <>
zone_to_string(utc_offset, std_offset, zone_abbr, time_zone)
[
date_to_iodata(year, month, day, format),
?\s,
time_to_iodata(hour, minute, second, microsecond, format),
offset_to_iodata(utc_offset, std_offset, time_zone, format),
zone_to_iodata(utc_offset, std_offset, zone_abbr, time_zone)
]
end
@doc false
def offset_to_string(0, 0, "Etc/UTC", _format), do: "Z"
def offset_to_string(utc, std, _zone, format) do
def offset_to_string(utc, std, zone, format) do
offset_to_iodata(utc, std, zone, format)
|> IO.iodata_to_binary()
end
@doc false
def offset_to_iodata(0, 0, "Etc/UTC", _format), do: ?Z
def offset_to_iodata(utc, std, _zone, format) do
total = utc + std
second = abs(total)
minute = second |> rem(3600) |> div(60)
@@ -1416,15 +1610,15 @@ defmodule Calendar.ISO do
end
defp format_offset(total, hour, minute, :extended) do
sign(total) <> zero_pad(hour, 2) <> ":" <> zero_pad(minute, 2)
[sign(total), zero_pad(hour, 2), ?: | zero_pad(minute, 2)]
end
defp format_offset(total, hour, minute, :basic) do
sign(total) <> zero_pad(hour, 2) <> zero_pad(minute, 2)
[sign(total), zero_pad(hour, 2) | zero_pad(minute, 2)]
end
defp zone_to_string(_, _, _, "Etc/UTC"), do: ""
defp zone_to_string(_, _, abbr, zone), do: " " <> abbr <> " " <> zone
defp zone_to_iodata(_, _, _, "Etc/UTC"), do: []
defp zone_to_iodata(_, _, abbr, zone), do: [?\s, abbr, ?\s | zone]
@doc """
Determines if the date given is valid according to the proleptic Gregorian calendar.
@@ -1485,16 +1679,24 @@ defmodule Calendar.ISO do
{0, 1}
end
defp sign(total) when total < 0, do: "-"
defp sign(_), do: "+"
defp sign(total) when total < 0, do: ?-
defp sign(_), do: ?+
defp zero_pad(val, count) when val >= 0 do
defp zero_pad(val, count) when val >= 0 and count <= 6 do
num = Integer.to_string(val)
:binary.copy("0", max(count - byte_size(num), 0)) <> num
case max(count - byte_size(num), 0) do
0 -> num
1 -> ["0" | num]
2 -> ["00" | num]
3 -> ["000" | num]
4 -> ["0000" | num]
5 -> ["00000" | num]
end
end
defp zero_pad(val, count) do
"-" <> zero_pad(-val, count)
[?- | zero_pad(-val, count)]
end
@doc """
@@ -1791,16 +1993,13 @@ defmodule Calendar.ISO do
end
defp parse_microsecond("." <> rest) do
case parse_microsecond(rest, 0, "") do
{"", 0, _} ->
case parse_microsecond(rest, 0, []) do
{[], 0, _} ->
:error
{microsecond, precision, rest} when precision in 1..6 ->
pad = String.duplicate("0", 6 - byte_size(microsecond))
{{String.to_integer(microsecond <> pad), precision}, rest}
{microsecond, _precision, rest} ->
{{String.to_integer(binary_part(microsecond, 0, 6)), 6}, rest}
{microsecond, precision, rest} ->
scale = scale_factor(precision)
{{:erlang.list_to_integer(microsecond) * scale, precision}, rest}
end
end
@@ -1812,34 +2011,42 @@ defmodule Calendar.ISO do
{{0, 0}, rest}
end
defp parse_microsecond(<<head, tail::binary>>, precision, acc) when head in ?0..?9,
do: parse_microsecond(tail, precision + 1, <<acc::binary, head>>)
defp parse_microsecond(<<head, tail::binary>>, 6, acc) when head in ?0..?9,
do: parse_microsecond(tail, 6, acc)
defp parse_microsecond(rest, precision, acc), do: {acc, precision, rest}
defp parse_microsecond(<<head, tail::binary>>, precision, acc) when head in ?0..?9,
do: parse_microsecond(tail, precision + 1, [head | acc])
defp parse_microsecond(rest, precision, acc) do
{:lists.reverse(acc), precision, rest}
end
defp parse_offset(""), do: {nil, ""}
defp parse_offset("Z"), do: {0, ""}
defp parse_offset("-00:00"), do: :error
defp parse_offset(<<?+, hour::2-bytes, ?:, min::2-bytes, rest::binary>>),
do: parse_offset(1, hour, min, rest)
defp parse_offset(<<?+, h1, h2, ?:, m1, m2, rest::binary>>),
do: parse_offset(1, h1, h2, m1, m2, rest)
defp parse_offset(<<?-, hour::2-bytes, ?:, min::2-bytes, rest::binary>>),
do: parse_offset(-1, hour, min, rest)
defp parse_offset(<<?-, h1, h2, ?:, m1, m2, rest::binary>>),
do: parse_offset(-1, h1, h2, m1, m2, rest)
defp parse_offset(<<?+, hour::2-bytes, min::2-bytes, rest::binary>>),
do: parse_offset(1, hour, min, rest)
defp parse_offset(<<?+, h1, h2, m1, m2, rest::binary>>),
do: parse_offset(1, h1, h2, m1, m2, rest)
defp parse_offset(<<?-, hour::2-bytes, min::2-bytes, rest::binary>>),
do: parse_offset(-1, hour, min, rest)
defp parse_offset(<<?-, h1, h2, m1, m2, rest::binary>>),
do: parse_offset(-1, h1, h2, m1, m2, rest)
defp parse_offset(<<?+, hour::2-bytes, rest::binary>>), do: parse_offset(1, hour, "00", rest)
defp parse_offset(<<?-, hour::2-bytes, rest::binary>>), do: parse_offset(-1, hour, "00", rest)
defp parse_offset(<<?+, h1, h2, rest::binary>>), do: parse_offset(1, h1, h2, ?0, ?0, rest)
defp parse_offset(<<?-, h1, h2, rest::binary>>), do: parse_offset(-1, h1, h2, ?0, ?0, rest)
defp parse_offset(_), do: :error
defp parse_offset(sign, hour, min, rest) do
with {hour, ""} when hour < 24 <- Integer.parse(hour),
{min, ""} when min < 60 <- Integer.parse(min) do
defp parse_offset(sign, h1, h2, m1, m2, rest) do
with true <- h1 in ?0..?2 and h2 in ?0..?9,
true <- m1 in ?0..?5 and m2 in ?0..?9,
hour = (h1 - ?0) * 10 + h2 - ?0,
min = (m1 - ?0) * 10 + m2 - ?0,
true <- hour < 24 do
{(hour * 60 + min) * 60 * sign, rest}
else
_ -> :error
+41 -23
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule NaiveDateTime do
@moduledoc """
A NaiveDateTime struct (without a time zone) and functions.
@@ -36,10 +40,10 @@ defmodule NaiveDateTime do
Comparisons in Elixir using `==/2`, `>/2`, `</2` and similar are structural
and based on the `NaiveDateTime` struct fields. For proper comparison
between naive datetimes, use the `compare/2` function. The existence of the
`compare/2` function in this module also allows using `Enum.min/2` and
`Enum.max/2` functions to get the minimum and maximum naive datetime of an
`Enum`. For example:
between naive datetimes, use the `compare/2`, `after?/2` and `before?/2` functions.
The existence of the `compare/2` function in this module also allows
using `Enum.min/2` and `Enum.max/2` functions to get the minimum and
maximum naive datetime of an `Enum`. For example:
iex> Enum.min([~N[2020-01-01 23:00:07], ~N[2000-01-01 23:00:07]], NaiveDateTime)
~N[2000-01-01 23:00:07]
@@ -387,14 +391,20 @@ defmodule NaiveDateTime do
@doc """
Adds a specified amount of time to a `NaiveDateTime`.
> #### Prefer `shift/2` {: .info}
>
> Prefer `shift/2` over `add/3`, as it offers a more ergonomic API.
>
> `add/3` provides a lower-level API which only supports fixed units
> such as `:hour` and `:second`, but not `:month` (as the exact length
> of a month depends on the current month). `add/3` always considers
> the unit to be computed according to the `Calendar.ISO`.
Accepts an `amount_to_add` in any `unit`. `unit` can be `:day`,
`:hour`, `:minute`, `:second` or any subsecond precision from
`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
to the `Calendar.ISO`.
## Examples
It uses seconds by default:
@@ -443,8 +453,6 @@ defmodule NaiveDateTime do
iex> NaiveDateTime.add(dt, 21, :second)
~N[2000-02-29 23:00:28]
To shift a naive datetime by a `Duration` and according to its underlying calendar, use `NaiveDateTime.shift/2`.
"""
@doc since: "1.4.0"
@spec add(Calendar.naive_datetime(), integer, :day | :hour | :minute | System.time_unit()) :: t
@@ -754,10 +762,10 @@ defmodule NaiveDateTime do
@doc """
Converts the given naive datetime to a string according to its calendar.
For redability, this function follows the RFC3339 suggestion of removing
For readability, this function follows the RFC3339 suggestion of removing
the "T" separator between the date and time components.
### Examples
## Examples
iex> NaiveDateTime.to_string(~N[2000-02-28 23:00:13])
"2000-02-28 23:00:13"
@@ -904,7 +912,7 @@ defmodule NaiveDateTime do
Only supports converting naive datetimes which are in the ISO calendar,
attempting to convert naive datetimes from other calendars will raise.
### Examples
## Examples
iex> NaiveDateTime.to_iso8601(~N[2000-02-28 23:00:13])
"2000-02-28T23:00:13"
@@ -930,6 +938,19 @@ defmodule NaiveDateTime do
def to_iso8601(%{calendar: Calendar.ISO} = naive_datetime, format)
when format in [:basic, :extended] do
naive_datetime
|> to_iso8601_iodata(format)
|> IO.iodata_to_binary()
end
def to_iso8601(%{calendar: _} = naive_datetime, format)
when format in [:basic, :extended] do
naive_datetime
|> convert!(Calendar.ISO)
|> to_iso8601(format)
end
defp to_iso8601_iodata(naive_datetime, format) do
%{
year: year,
month: month,
@@ -940,14 +961,11 @@ defmodule NaiveDateTime do
microsecond: microsecond
} = naive_datetime
Calendar.ISO.date_to_string(year, month, day, format) <>
"T" <> Calendar.ISO.time_to_string(hour, minute, second, microsecond, format)
end
def to_iso8601(%{calendar: _} = naive_datetime, format) when format in [:basic, :extended] do
naive_datetime
|> convert!(Calendar.ISO)
|> to_iso8601(format)
[
Calendar.ISO.date_to_iodata(year, month, day, format),
?T,
Calendar.ISO.time_to_iodata(hour, minute, second, microsecond, format)
]
end
@doc """
@@ -1247,7 +1265,7 @@ defmodule NaiveDateTime do
## Examples
Imagine someone implements `Calendar.Holocene`, a calendar based on the
Gregorian calendar that adds exactly 10,000 years to the current Gregorian
Gregorian calendar that adds exactly 10 000 years to the current Gregorian
year:
iex> NaiveDateTime.convert(~N[2000-01-01 13:30:15], Calendar.Holocene)
@@ -1295,7 +1313,7 @@ defmodule NaiveDateTime do
if Calendar.compatible_calendars?(ndt_calendar, calendar) do
result_naive_datetime =
naive_datetime
|> to_iso_days
|> to_iso_days()
|> from_iso_days(calendar, precision)
{:ok, result_naive_datetime}
@@ -1313,7 +1331,7 @@ defmodule NaiveDateTime do
## Examples
Imagine someone implements `Calendar.Holocene`, a calendar based on the
Gregorian calendar that adds exactly 10,000 years to the current Gregorian
Gregorian calendar that adds exactly 10 000 years to the current Gregorian
year:
iex> NaiveDateTime.convert!(~N[2000-01-01 13:30:15], Calendar.Holocene)
+57 -15
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Time do
@moduledoc """
A Time struct and functions.
@@ -31,9 +35,10 @@ defmodule Time do
Comparisons in Elixir using `==/2`, `>/2`, `</2` and similar are structural
and based on the `Time` struct fields. For proper comparison between
times, use the `compare/2` function. The existence of the `compare/2`
function in this module also allows using `Enum.min/2` and `Enum.max/2`
functions to get the minimum and maximum time of an `Enum`. For example:
times, use the `compare/2`, `after?/2` and `before?/2` functions.
The existence of the `compare/2` function in this module also allows
using `Enum.min/2` and `Enum.max/2` functions to get the minimum and
maximum time of an `Enum`. For example:
iex> Enum.min([~T[23:00:07.001], ~T[10:00:07.001]], Time)
~T[10:00:07.001]
@@ -55,17 +60,52 @@ defmodule Time do
@doc """
Returns the current time in UTC.
You can pass a time unit to automatically truncate the resulting time.
The default unit if none gets passed is `:native` which results on a default resolution of microseconds.
## Examples
iex> time = Time.utc_now()
iex> time.hour >= 0
true
iex> time = Time.utc_now(:second)
iex> time.microsecond
{0, 0}
"""
@doc since: "1.4.0"
@spec utc_now(Calendar.calendar()) :: t
def utc_now(calendar \\ Calendar.ISO) do
{:ok, _, time, microsecond} = Calendar.ISO.from_unix(:os.system_time(), :native)
@spec utc_now(Calendar.calendar() | :native | :microsecond | :millisecond | :second) :: t
def utc_now(calendar_or_time_unit \\ Calendar.ISO) do
case calendar_or_time_unit do
unit when unit in [:native, :microsecond, :millisecond, :second] ->
utc_now(unit, Calendar.ISO)
calendar ->
utc_now(:native, calendar)
end
end
@doc """
Returns the current time in UTC, supporting a precision and a specific calendar.
## Examples
iex> time = Time.utc_now(:microsecond, Calendar.ISO)
iex> time.hour >= 0
true
iex> time = Time.utc_now(:second, Calendar.ISO)
iex> time.microsecond
{0, 0}
"""
@doc since: "1.19.0"
@spec utc_now(:native | :microsecond | :millisecond | :second, Calendar.calendar()) :: t
def utc_now(time_unit, calendar)
when time_unit in [:native, :microsecond, :millisecond, :second] do
{:ok, _, time, microsecond} = Calendar.ISO.from_unix(System.os_time(time_unit), time_unit)
{hour, minute, second} = time
iso_time = %Time{
@@ -185,7 +225,7 @@ defmodule Time do
@doc """
Converts the given `time` to a string.
### Examples
## Examples
iex> Time.to_string(~T[23:00:00])
"23:00:00"
@@ -294,7 +334,7 @@ defmodule Time do
format, for human readability. It also supports the "basic" format through
passing the `:basic` option.
### Examples
## Examples
iex> Time.to_iso8601(~T[23:00:13])
"23:00:13"
@@ -465,14 +505,18 @@ defmodule Time do
@doc """
Adds the `amount_to_add` of `unit`s to the given `time`.
> #### Prefer `shift/2` {: .info}
>
> Prefer `shift/2` over `add/3`, as it offers a more ergonomic API.
>
> `add/3` always considers the unit to be computed according to
> the `Calendar.ISO`.
Accepts an `amount_to_add` in any `unit`. `unit` can be
`:hour`, `:minute`, `:second` or any subsecond precision from
`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
to the `Calendar.ISO`.
Note the result value represents the time of day, meaning that it is cyclic,
for instance, it will never go over 24 hours for the ISO calendar.
@@ -509,8 +553,6 @@ defmodule Time do
iex> result.microsecond
{21000, 3}
To shift a time by a `Duration` and according to its underlying calendar, use `Time.shift/2`.
"""
@doc since: "1.6.0"
@spec add(Calendar.time(), integer, :hour | :minute | System.time_unit()) :: t
@@ -741,7 +783,7 @@ defmodule Time do
## Examples
Imagine someone implements `Calendar.Holocene`, a calendar based on the
Gregorian calendar that adds exactly 10,000 years to the current Gregorian
Gregorian calendar that adds exactly 10 000 years to the current Gregorian
year:
iex> Time.convert(~T[13:30:15], Calendar.Holocene)
@@ -797,7 +839,7 @@ defmodule Time do
## Examples
Imagine someone implements `Calendar.Holocene`, a calendar based on the
Gregorian calendar that adds exactly 10,000 years to the current Gregorian
Gregorian calendar that adds exactly 10 000 years to the current Gregorian
year:
iex> Time.convert!(~T[13:30:15], Calendar.Holocene)
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Calendar.TimeZoneDatabase do
@moduledoc """
This module defines a behaviour for providing time zone data.
+199 -79
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Code do
@moduledoc ~S"""
Utilities for managing code compilation, code evaluation, and code loading.
@@ -244,19 +248,71 @@ defmodule Code do
"""
@type position() :: line() | {line :: pos_integer(), column :: pos_integer()}
@typedoc """
Options for code formatting functions.
"""
@type format_opt ::
{:file, binary()}
| {:line, pos_integer()}
| {:line_length, pos_integer()}
| {:locals_without_parens, keyword()}
| {:force_do_end_blocks, boolean()}
| {:migrate, boolean()}
| {:migrate_bitstring_modifiers, boolean()}
| {:migrate_call_parens_on_pipe, boolean()}
| {:migrate_charlists_as_sigils, boolean()}
| {:migrate_unless, boolean()}
| {atom(), term()}
@typedoc """
Options for `quoted_to_algebra/2`.
"""
@type quoted_to_algebra_opt ::
{:line, pos_integer() | nil}
| {:escape, boolean()}
| {:locals_without_parens, keyword()}
| {:comments, [term()]}
@typedoc """
Options for parsing functions that convert strings to quoted expressions.
"""
@type parser_opts :: [
file: binary(),
line: pos_integer(),
column: pos_integer(),
indentation: non_neg_integer(),
columns: boolean(),
unescape: boolean(),
existing_atoms_only: boolean(),
token_metadata: boolean(),
literal_encoder: (term(), Macro.metadata() -> term()),
static_atoms_encoder: (atom() -> term()),
emit_warnings: boolean()
]
@typedoc """
Options for environment evaluation functions like eval_string/3 and eval_quoted/3.
"""
@type env_eval_opts :: [
file: binary(),
line: pos_integer(),
module: module(),
prune_binding: boolean()
]
@boolean_compiler_options [
:docs,
:debug_info,
:ignore_already_consolidated,
:ignore_module_conflict,
:infer_signatures,
:relative_paths
]
@list_compiler_options [:no_warn_undefined, :tracers, :parser_options]
@list_compiler_options [:tracers, :parser_options]
@available_compiler_options @boolean_compiler_options ++
@list_compiler_options ++ [:on_undefined_variable]
@list_compiler_options ++
[:on_undefined_variable, :infer_signatures, :no_warn_undefined]
@doc """
Lists all required files.
@@ -343,7 +399,7 @@ defmodule Code do
* `:cache` - (since v1.15.0) when true, the code path is cached
the first time it is traversed in order to reduce file system
operations. It requires Erlang/OTP 26, otherwise it is a no-op.
operations.
"""
@spec append_path(Path.t(), cache: boolean()) :: true | false
@@ -374,7 +430,7 @@ defmodule Code do
* `:cache` - (since v1.15.0) when true, the code path is cached
the first time it is traversed in order to reduce file system
operations. It requires Erlang/OTP 26, otherwise it is a no-op.
operations.
"""
@spec prepend_path(Path.t(), cache: boolean()) :: boolean()
@@ -403,7 +459,7 @@ defmodule Code do
* `:cache` - when true, the code path is cached the first time
it is traversed in order to reduce file system operations.
It requires Erlang/OTP 26, otherwise it is a no-op.
"""
@doc since: "1.15.0"
@spec prepend_paths([Path.t()], cache: boolean()) :: :ok
@@ -432,7 +488,7 @@ defmodule Code do
* `:cache` - when true, the code path is cached the first time
it is traversed in order to reduce file system operations.
It requires Erlang/OTP 26, otherwise it is a no-op.
"""
@doc since: "1.15.0"
@spec append_paths([Path.t()], cache: boolean()) :: :ok
@@ -548,14 +604,15 @@ defmodule Code do
all imports, requires and aliases defined in the current environment
will be automatically carried over:
iex> {result, binding} = Code.eval_string("a + b", [a: 1, b: 2], __ENV__)
iex> require Integer
iex> {result, binding} = Code.eval_string("if Integer.is_odd(a), do: a + b", [a: 1, b: 2], __ENV__)
iex> result
3
iex> Enum.sort(binding)
[a: 1, b: 2]
"""
@spec eval_string(List.Chars.t(), binding, Macro.Env.t() | keyword) :: {term, binding}
@spec eval_string(List.Chars.t(), binding, Macro.Env.t() | env_eval_opts) :: {term, binding}
def eval_string(string, binding \\ [], opts \\ [])
def eval_string(string, binding, %Macro.Env{} = env) do
@@ -610,7 +667,8 @@ defmodule Code do
"""
@doc since: "1.15.0"
@spec with_diagnostics(keyword(), (-> result)) :: {result, [diagnostic(:warning | :error)]}
@spec with_diagnostics([log: boolean()], (-> result)) ::
{result, [diagnostic(:warning | :error)]}
when result: term()
def with_diagnostics(opts \\ [], fun) do
value = :erlang.get(:elixir_code_diagnostics)
@@ -643,7 +701,7 @@ defmodule Code do
Defaults to `true`.
"""
@doc since: "1.15.0"
@spec print_diagnostic(diagnostic(:warning | :error), keyword()) :: :ok
@spec print_diagnostic(diagnostic(:warning | :error), snippet: boolean()) :: :ok
def print_diagnostic(diagnostic, opts \\ []) do
read_snippet? = Keyword.get(opts, :snippet, true)
:elixir_errors.print_diagnostic(diagnostic, read_snippet?)
@@ -667,7 +725,7 @@ defmodule Code do
* `:line` - the line the string starts, used for error reporting
* `:line_length` - the line length to aim for when formatting
the document. Defaults to 98. This value indicates when an expression
the document. Defaults to `98`. This value indicates when an expression
should be broken over multiple lines but it is not guaranteed
to do so. See the "Line length" section below for more information
@@ -696,6 +754,14 @@ defmodule Code do
modifiers, where `<<foo::custom_type>>` becomes `<<foo::custom_type()>>`.
Defaults to the value of the `:migrate` option. This option changes the AST.
* `:migrate_call_parens_on_pipe` (since v1.19.0) - when `true`,
formats calls on the right-hand side of the pipe operator to always include
parentheses, for example `foo |> bar` becomes `foo |> bar()` and
`foo |> mod.fun` becomes `foo |> mod.fun()`.
Parentheses are always added for qualified calls like `foo |> Bar.bar` even
when this option is `false`.
Defaults to the value of the `:migrate` option. This option changes the AST.
* `:migrate_charlists_as_sigils` (since v1.18.0) - when `true`,
formats charlists as [`~c`](`Kernel.sigil_c/2`) sigils, for example
`'foo'` becomes `~c"foo"`.
@@ -963,12 +1029,18 @@ defmodule Code do
the code representation (AST). While the formatter can preserve code
comments between expressions and function arguments, the formatter
cannot currently preserve them around operators. For example, the following
code will move the code comments to before the operator usage:
code:
foo() ||
# also check for bar
bar()
will move the code comments to before the operator usage:
# also check for bar
foo() ||
bar()
In some situations, code comments can be seen as ambiguous by the formatter.
For example, the comment in the anonymous function below
@@ -1016,9 +1088,9 @@ defmodule Code do
address the deprecation warnings.
"""
@doc since: "1.6.0"
@spec format_string!(binary, keyword) :: iodata
@spec format_string!(binary, [format_opt]) :: iodata
def format_string!(string, opts \\ []) when is_binary(string) and is_list(opts) do
line_length = Keyword.get(opts, :line_length, 98)
{line_length, opts} = Keyword.pop(opts, :line_length, 98)
to_quoted_opts =
[
@@ -1041,7 +1113,7 @@ defmodule Code do
available options.
"""
@doc since: "1.6.0"
@spec format_file!(binary, keyword) :: iodata
@spec format_file!(binary, [format_opt]) :: iodata
def format_file!(file, opts \\ []) when is_binary(file) and is_list(opts) do
string = File.read!(file)
formatted = format_string!(string, [file: file, line: 1] ++ opts)
@@ -1056,7 +1128,8 @@ defmodule Code do
Macro arguments are typically transformed by unquoting them into the
returned quoted expressions (instead of evaluated).
See `eval_string/3` for a description of `binding` and `opts`.
See `eval_string/3` for a description of arguments and return types.
The options are described under `env_for_eval/1`.
## Examples
@@ -1078,7 +1151,7 @@ defmodule Code do
[a: 1, b: 2]
"""
@spec eval_quoted(Macro.t(), binding, Macro.Env.t() | keyword) :: {term, binding}
@spec eval_quoted(Macro.t(), binding, Macro.Env.t() | env_eval_opts) :: {term, binding}
def eval_quoted(quoted, binding \\ [], env_or_opts \\ []) do
{value, binding, _env} =
eval_verify(:eval_quoted, [quoted, binding, env_for_eval(env_or_opts)])
@@ -1109,8 +1182,15 @@ defmodule Code do
* `:line` - the line on which the script starts
* `:module` - the module to run the environment on
* `:prune_binding` - (since v1.14.2) prune binding to keep only
variables read or written by the evaluated code. Note that
variables used by modules are always pruned, even if later used
by the modules. You can submit to the `:on_module` tracer event
and access the variables used by the module from its environment.
"""
@doc since: "1.14.0"
@spec env_for_eval(Macro.Env.t() | env_eval_opts) :: Macro.Env.t()
def env_for_eval(env_or_opts), do: :elixir.env_for_eval(env_or_opts)
@doc """
@@ -1124,15 +1204,11 @@ defmodule Code do
## Options
* `:prune_binding` - (since v1.14.2) prune binding to keep only
variables read or written by the evaluated code. Note that
variables used by modules are always pruned, even if later used
by the modules. You can submit to the `:on_module` tracer event
and access the variables used by the module from its environment.
It accepts the same options as `env_for_eval/1`.
"""
@doc since: "1.14.0"
@spec eval_quoted_with_env(Macro.t(), binding, Macro.Env.t(), keyword) ::
@spec eval_quoted_with_env(Macro.t(), binding, Macro.Env.t(), env_eval_opts) ::
{term, binding, Macro.Env.t()}
def eval_quoted_with_env(quoted, binding, %Macro.Env{} = env, opts \\ [])
when is_list(binding) do
@@ -1151,10 +1227,14 @@ defmodule Code do
Defaults to `"nofile"`.
* `:line` - the starting line of the string being parsed.
Defaults to 1.
Defaults to `1`.
* `:column` - (since v1.11.0) the starting column of the string being parsed.
Defaults to 1.
Defaults to `1`.
* `:indentation` - (since v1.19.0) the indentation for the string being parsed.
This is useful when the code parsed is embedded within another document.
Defaults to `0`.
* `:columns` - when `true`, attach a `:column` key to the quoted
metadata. Defaults to `false`.
@@ -1230,8 +1310,16 @@ defmodule Code do
* atoms used to represent single-letter sigils like `:sigil_X`
(but multi-letter sigils like `:sigil_XYZ` are encoded).
## Examples
iex> Code.string_to_quoted("1 + 3")
{:ok, {:+, [line: 1], [1, 3]}}
iex> Code.string_to_quoted("1 \ 3")
{:error, {[line: 1, column: 4], "syntax error before: ", "\"3\""}}
"""
@spec string_to_quoted(List.Chars.t(), keyword) ::
@spec string_to_quoted(List.Chars.t(), parser_opts) ::
{:ok, Macro.t()} | {:error, {location :: keyword, binary | {binary, binary}, binary}}
def string_to_quoted(string, opts \\ []) when is_list(opts) do
file = Keyword.get(opts, :file, "nofile")
@@ -1258,7 +1346,7 @@ defmodule Code do
Check `string_to_quoted/2` for options information.
"""
@spec string_to_quoted!(List.Chars.t(), keyword) :: Macro.t()
@spec string_to_quoted!(List.Chars.t(), parser_opts) :: Macro.t()
def string_to_quoted!(string, opts \\ []) when is_list(opts) do
file = Keyword.get(opts, :file, "nofile")
line = Keyword.get(opts, :line, 1)
@@ -1309,7 +1397,7 @@ defmodule Code do
"""
@doc since: "1.13.0"
@spec string_to_quoted_with_comments(List.Chars.t(), keyword) ::
@spec string_to_quoted_with_comments(List.Chars.t(), parser_opts) ::
{:ok, Macro.t(), list(map())} | {:error, {location :: keyword, term, term}}
def string_to_quoted_with_comments(string, opts \\ []) when is_list(opts) do
charlist = to_charlist(string)
@@ -1339,7 +1427,7 @@ defmodule Code do
Check `string_to_quoted/2` for options information.
"""
@doc since: "1.13.0"
@spec string_to_quoted_with_comments!(List.Chars.t(), keyword) :: {Macro.t(), list(map())}
@spec string_to_quoted_with_comments!(List.Chars.t(), parser_opts) :: {Macro.t(), list(map())}
def string_to_quoted_with_comments!(string, opts \\ []) do
charlist = to_charlist(string)
@@ -1348,13 +1436,11 @@ defmodule Code do
{forms, comments}
{:error, {location, error, token}} ->
:elixir_errors.parse_error(
location,
Keyword.get(opts, :file, "nofile"),
error,
token,
{charlist, Keyword.get(opts, :line, 1), Keyword.get(opts, :column, 1)}
)
file = Keyword.get(opts, :file, "nofile")
line = Keyword.get(opts, :line, 1)
column = Keyword.get(opts, :column, 1)
input = {charlist, line, column, Keyword.get(opts, :indentation, 0)}
:elixir_errors.parse_error(location, file, error, token, input)
end
end
@@ -1364,7 +1450,7 @@ defmodule Code do
comment = %{
line: line,
column: column,
previous_eol_count: previous_eol_count(tokens),
previous_eol_count: min(previous_eol_count(tokens), last_comment_distance(comments, line)),
next_eol_count: next_eol_count(rest, 0),
text: List.to_string(comment)
}
@@ -1378,6 +1464,9 @@ defmodule Code do
defp next_eol_count([?\r, ?\n | rest], count), do: next_eol_count(rest, count + 1)
defp next_eol_count(_, count), do: count
defp last_comment_distance([%{line: last_line} | _], line), do: line - last_line
defp last_comment_distance([], _line), do: :infinity
defp previous_eol_count([{token, {_, _, count}} | _])
when token in [:eol, :",", :";"] and count > 0 do
count
@@ -1423,6 +1512,9 @@ defmodule Code do
## Options
This function accepts all options supported by `format_string!/2` for controlling
code formatting, plus these additional options:
* `:comments` - the list of comments associated with the quoted expression.
Defaults to `[]`. It is recommended that both `:token_metadata` and
`:literal_encoder` options are given to `string_to_quoted_with_comments/2`
@@ -1433,17 +1525,13 @@ defmodule Code do
`string_to_quoted/2`, setting this option to `false` will prevent it from
escaping the sequences twice. Defaults to `true`.
* `:locals_without_parens` - a keyword list of name and arity
pairs that should be kept without parens whenever possible.
The arity may be the atom `:*`, which implies all arities of
that name. The formatter already includes a list of functions
and this option augments this list.
* `:syntax_colors` - a keyword list of colors the output is colorized.
See `Inspect.Opts` for more information.
See `format_string!/2` for the full list of formatting options including
`:file`, `:line`, `:line_length`, `:locals_without_parens`, `:force_do_end_blocks`,
`:syntax_colors`, and all migration options like `:migrate_charlists_as_sigils`.
"""
@doc since: "1.13.0"
@spec quoted_to_algebra(Macro.t(), keyword) :: Inspect.Algebra.t()
@spec quoted_to_algebra(Macro.t(), [format_opt() | quoted_to_algebra_opt()]) ::
Inspect.Algebra.t()
def quoted_to_algebra(quoted, opts \\ []) do
quoted
|> Code.Normalizer.normalize(opts)
@@ -1563,7 +1651,7 @@ defmodule Code do
## Examples
Code.compiler_options(infer_signatures: false)
#=> %{infer_signatures: true}
#=> %{infer_signatures: [:elixir]}
"""
@spec compiler_options(Enumerable.t({atom, term})) :: %{optional(atom) => term}
@@ -1647,14 +1735,16 @@ defmodule Code do
* `:ignore_module_conflict` - when `true`, does not warn when a module has
already been defined. Defaults to `false`.
* `:infer_signatures` (since v1.18.0) - when `false`, it disables module-local
* `:infer_signatures` (since v1.18.0) - a list of applications of which modules
should be using during type inference. When `false`, it disables module-local
signature inference used when type checking remote calls to the compiled
module. Type checking will be executed regardless of the value of this option.
Defaults to `true`.
Defaults to `true`, which is equivalent to setting it to `[:elixir]` only.
`mix test` automatically disables this option via the `:test_elixirc_options`
project configuration, as there is typically no need to store infer signatures
for test files.
When setting this option, we recommend running `mix clean` so the current module
may be compiled from scratch. `mix test` automatically disables this option via
the `:test_elixirc_options` project configuration, as there is typically no need
to infer signatures for test files.
* `:relative_paths` - when `true`, uses relative paths in quoted nodes,
warnings, and errors generated by the compiler. Note disabling this option
@@ -1705,28 +1795,6 @@ defmodule Code do
:ok
end
# TODO: Remove me in Elixir v2.0
def put_compiler_option(:warnings_as_errors, _value) do
IO.warn(
":warnings_as_errors is deprecated as part of Code.put_compiler_option/2, " <>
"instead you must pass it as a --warnings-as-errors flag. " <>
"If you need to set it as a default in a mix task, you can also set it under aliases: " <>
"[compile: \"compile --warnings-as-errors\"]"
)
:ok
end
def put_compiler_option(:no_warn_undefined, value) do
if value != :all and not is_list(value) do
raise "compiler option :no_warn_undefined should be a list or the atom :all, " <>
"got: #{inspect(value)}"
end
:elixir_config.put(:no_warn_undefined, value)
:ok
end
def put_compiler_option(key, value) when key in @list_compiler_options do
if not is_list(value) do
raise "compiler option #{inspect(key)} should be a list, got: #{inspect(value)}"
@@ -1746,9 +1814,61 @@ defmodule Code do
:ok
end
# TODO: Remove this option on Elixir v2.0
# TODO: Warn if mode is :warn on Elixir v1.19
def put_compiler_option(:infer_signatures, value) do
value =
cond do
value == false ->
false
value == true ->
[:elixir]
is_list(value) and Enum.all?(value, &is_atom/1) ->
value
true ->
raise "compiler option :infer_signatures should be a boolean or a list of applications, got: #{inspect(value)}"
end
:elixir_config.put(:infer_signatures, value)
:ok
end
def put_compiler_option(:no_warn_undefined, value) do
if value != :all and not is_list(value) do
raise "compiler option :no_warn_undefined should be a list or the atom :all, " <>
"got: #{inspect(value)}"
end
:elixir_config.put(:no_warn_undefined, value)
:ok
end
# TODO: Remove me in Elixir v2.0
def put_compiler_option(:warnings_as_errors, _value) do
IO.warn(
":warnings_as_errors is deprecated as part of Code.put_compiler_option/2, " <>
"instead you must pass it as a --warnings-as-errors flag. " <>
"If you need to set it as a default in a mix task, you can also set it under aliases: " <>
"[compile: \"compile --warnings-as-errors\"]"
)
:ok
end
# TODO: Remove me in Elixir v2.0
def put_compiler_option(:on_undefined_variable, value) when value in [:raise, :warn] do
if value == :warn do
IO.warn_once(
{__MODULE__, :on_undefined_variable},
fn ->
"setting :on_undefined_variable to :warn is deprecated. " <>
"The warning behaviour will be removed in future releases"
end,
3
)
end
:elixir_config.put(:on_undefined_variable, value)
:ok
end
@@ -2013,7 +2133,7 @@ defmodule Code do
case :code.ensure_loaded(module) do
{:error, :nofile} = error ->
if can_await_module_compilation?() do
case Kernel.ErrorHandler.ensure_compiled(module, :module, mode) do
case Kernel.ErrorHandler.ensure_compiled(module, :module, mode, nil) do
:found -> {:module, module}
:deadlock -> {:error, :unavailable}
:not_found -> {:error, :nofile}
@@ -2035,7 +2155,7 @@ defmodule Code do
## Examples
iex> Code.loaded?(Atom)
iex> Code.loaded?(String)
true
iex> Code.loaded?(NotYetLoaded)
+90 -34
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Code.Formatter do
@moduledoc false
import Inspect.Algebra, except: [format: 2, surround: 3, surround: 4]
@@ -154,6 +158,7 @@ defmodule Code.Formatter do
@doc """
Converts the quoted expression into an algebra document.
"""
@spec to_algebra(Macro.t(), keyword()) :: Inspect.Algebra.t()
def to_algebra(quoted, opts \\ []) do
comments = Keyword.get(opts, :comments, [])
@@ -191,6 +196,7 @@ defmodule Code.Formatter do
sigils = Keyword.get(opts, :sigils, [])
migrate = Keyword.get(opts, :migrate, false)
migrate_bitstring_modifiers = Keyword.get(opts, :migrate_bitstring_modifiers, migrate)
migrate_call_parens_on_pipe = Keyword.get(opts, :migrate_call_parens_on_pipe, migrate)
migrate_charlists_as_sigils = Keyword.get(opts, :migrate_charlists_as_sigils, migrate)
migrate_unless = Keyword.get(opts, :migrate_unless, migrate)
syntax_colors = Keyword.get(opts, :syntax_colors, [])
@@ -218,6 +224,7 @@ defmodule Code.Formatter do
sigils: sigils,
file: file,
migrate_bitstring_modifiers: migrate_bitstring_modifiers,
migrate_call_parens_on_pipe: migrate_call_parens_on_pipe,
migrate_charlists_as_sigils: migrate_charlists_as_sigils,
migrate_unless: migrate_unless,
inspect_opts: %Inspect.Opts{syntax_colors: syntax_colors}
@@ -487,7 +494,16 @@ defmodule Code.Formatter do
binary_op_to_algebra(:in, "not in", meta, left, right, context, state)
end
# disable migrate_unless within defmacro
# disable migrate_call_parens_on_pipe within defmacro
defp quoted_to_algebra(
{atom, _, [{:|>, _, _}, _]} = ast,
context,
%{migrate_call_parens_on_pipe: true} = state
)
when atom in [:defmacro, :defmacrop] do
quoted_to_algebra(ast, context, %{state | migrate_call_parens_on_pipe: false})
end
defp quoted_to_algebra(
{atom, _, [{:unless, _, _}, _]} = ast,
context,
@@ -602,9 +618,12 @@ defmodule Code.Formatter do
end
doc =
with_next_break_fits(next_break_fits?(right_arg, state), right, fn right ->
concat(group(left), group(nest(glue(op, group(right)), 2, :break)))
end)
concat(
group(left),
with_next_break_fits(next_break_fits?(right_arg, state), right, fn right ->
nest(glue(op, right), 2, :break)
end)
)
{doc, state}
end
@@ -787,15 +806,13 @@ defmodule Code.Formatter do
{right, state} =
binary_operand_to_algebra(right_arg, right_context, state, op, op_info, :right, 0)
doc =
{op_string, right} =
cond do
op in @no_space_binary_operators ->
op_doc = color_doc(op_string, :operator, state.inspect_opts)
concat(concat(group(left), op_doc), group(right))
{op_string, group(right)}
op in @no_newline_binary_operators ->
op_doc = color_doc(" " <> op_string <> " ", :operator, state.inspect_opts)
concat(concat(group(left), op_doc), group(right))
{" " <> op_string <> " ", group(right)}
true ->
eol? = eol?(meta, state)
@@ -803,14 +820,15 @@ defmodule Code.Formatter do
next_break_fits? =
op in @next_break_fits_operators and next_break_fits?(right_arg, state) and not eol?
with_next_break_fits(next_break_fits?, right, fn right ->
op_doc = color_doc(" " <> op_string, :operator, state.inspect_opts)
right = nest(glue(op_doc, group(right)), nesting, :break)
right = if eol?, do: force_unfit(right), else: right
concat(group(left), group(right))
end)
{" " <> op_string,
with_next_break_fits(next_break_fits?, right, fn right ->
right = nest(concat(break(), right), nesting, :break)
if eol?, do: force_unfit(right), else: right
end)}
end
op_doc = color_doc(op_string, :operator, state.inspect_opts)
doc = concat(concat(group(left), op_doc), group(right))
{doc, state}
end
@@ -832,6 +850,38 @@ defmodule Code.Formatter do
{wrap_in_parens(doc), state}
end
# |> var
# |> var()
defp binary_operand_to_algebra(
{var, meta, var_context},
context,
%{migrate_call_parens_on_pipe: true} = state,
:|>,
_parent_info,
:right,
_nesting
)
when is_atom(var) and is_atom(var_context) do
operand = {var, meta, []}
quoted_to_algebra(operand, context, state)
end
# |> var.fun
# |> var.fun()
defp binary_operand_to_algebra(
{{:., _, [_, fun]} = call, meta, []},
context,
%{migrate_call_parens_on_pipe: true} = state,
:|>,
_parent_info,
:right,
_nesting
)
when is_atom(fun) do
meta = Keyword.put_new_lazy(meta, :closing, fn -> [line: meta[:line]] end)
quoted_to_algebra({call, meta, []}, context, state)
end
defp binary_operand_to_algebra(operand, context, state, parent_op, parent_info, side, nesting) do
{parent_assoc, parent_prec} = parent_info
@@ -1215,7 +1265,7 @@ defmodule Code.Formatter do
args_doc =
if skip_parens? do
left_doc
|> concat(next_break_fits(group(right_doc, :inherit), :enabled))
|> concat(group(right_doc, :optimistic))
|> nest(:cursor, :break)
else
right_doc =
@@ -1223,8 +1273,7 @@ defmodule Code.Formatter do
|> nest(2, :break)
|> concat(break(""))
|> concat(")")
|> group(:inherit)
|> next_break_fits(:enabled)
|> group(:optimistic)
concat(nest(left_doc, 2, :break), right_doc)
end
@@ -1267,13 +1316,11 @@ defmodule Code.Formatter do
|> concat(args_doc)
|> nest(2)
|> concat(extra)
|> group()
skip_parens? ->
" "
|> concat(args_doc)
|> concat(extra)
|> group()
true ->
"("
@@ -1281,13 +1328,12 @@ defmodule Code.Formatter do
|> nest(2, :break)
|> concat(args_doc)
|> concat(extra)
|> group()
end
if next_break_fits? do
{next_break_fits(doc, :disabled), state}
{group(doc, :pessimistic), state}
else
{doc, state}
{group(doc), state}
end
end
@@ -1753,10 +1799,17 @@ defmodule Code.Formatter do
doc =
case args do
[_ | _] -> concat_to_last_group(doc, ",")
[] when last_arg_mode == :force_comma -> concat_to_last_group(doc, ",")
[] when last_arg_mode == :next_break_fits -> next_break_fits(doc, :enabled)
[] when last_arg_mode == :none -> doc
[_ | _] ->
concat_to_last_group(doc, ",")
[] when last_arg_mode == :force_comma ->
concat_to_last_group(doc, ",")
[] when last_arg_mode == :next_break_fits ->
doc |> ungroup_if_group() |> group(:optimistic)
[] when last_arg_mode == :none ->
doc
end
{{doc, @empty, 1}, state}
@@ -2274,11 +2327,14 @@ defmodule Code.Formatter do
defp with_next_break_fits(condition, doc, fun) do
if condition do
doc
|> next_break_fits(:enabled)
|> group(:optimistic)
|> fun.()
|> next_break_fits(:disabled)
|> group(:pessimistic)
else
fun.(doc)
doc
|> group()
|> fun.()
|> group()
end
end
@@ -2445,16 +2501,16 @@ defmodule Code.Formatter do
# Relying on the inner document is brittle and error prone.
# It would be best if we had a mechanism to apply this.
defp concat_to_last_group({:doc_cons, left, right}, concat) do
{:doc_cons, left, concat_to_last_group(right, concat)}
defp concat_to_last_group([left | right], concat) do
[left | concat_to_last_group(right, concat)]
end
defp concat_to_last_group({:doc_group, group, mode}, concat) do
{:doc_group, {:doc_cons, group, concat}, mode}
{:doc_group, concat(group, concat), mode}
end
defp concat_to_last_group(other, concat) do
{:doc_cons, other, concat}
concat(other, concat)
end
defp ungroup_if_group({:doc_group, group, _mode}), do: group
+120 -18
View File
@@ -1,3 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
defmodule Code.Fragment do
@moduledoc """
This module provides conveniences for analyzing fragments of
@@ -8,6 +11,59 @@ defmodule Code.Fragment do
@type position :: {line :: pos_integer(), column :: pos_integer()}
@typedoc """
Options for cursor context functions.
Currently, these options are not used but reserved for future extensibility.
"""
@type cursor_opts :: []
@typedoc """
Options for converting code fragments to quoted expressions.
"""
@type container_cursor_to_quoted_opts :: [
file: String.t(),
line: pos_integer(),
column: pos_integer(),
columns: boolean(),
token_metadata: boolean(),
literal_encoder: (term(), Macro.metadata() -> term()),
trailing_fragment: String.t()
]
@doc ~S"""
Returns the list of lines in the given string, preserving their line endings.
Only the line endings recognized by the Elixir compiler are
considered, namely `\r\n` and `\n`. If you would like the retrieve
lines without their line endings, use `String.split(string, ["\r\n", "\n"])`.
## Examples
iex> Code.Fragment.lines("foo\r\nbar\r\nbaz")
["foo\r\n", "bar\r\n", "baz"]
iex> Code.Fragment.lines("foo\nbar\nbaz")
["foo\n", "bar\n", "baz"]
iex> Code.Fragment.lines("")
[""]
"""
@doc since: "1.19.0"
def lines(string) do
lines(string, <<>>)
end
defp lines(<<?\n, rest::binary>>, acc),
do: [<<acc::binary, ?\n>> | lines(rest, <<>>)]
defp lines(<<char, rest::binary>>, acc),
do: lines(rest, <<acc::binary, char>>)
defp lines(<<>>, acc),
do: [acc]
@doc """
Receives a string and returns the cursor context.
@@ -43,6 +99,9 @@ defmodule Code.Fragment do
or `{:local_or_var, charlist}` and `charlist` is a static part
Examples are `__MODULE__.Submodule` or `@hello.Submodule`
* `{:block_keyword_or_binary_operator, charlist}` - may be a block keyword (do, end, after,
catch, else, rescue) or a binary operator
* `{:dot, inside_dot, charlist}` - the context is a dot
where `inside_dot` is either a `{:var, charlist}`, `{:alias, charlist}`,
`{:module_attribute, charlist}`, `{:unquoted_atom, charlist}` or a `dot`
@@ -133,9 +192,10 @@ defmodule Code.Fragment do
references, and more.
"""
@doc since: "1.13.0"
@spec cursor_context(List.Chars.t(), keyword()) ::
@spec cursor_context(List.Chars.t(), cursor_opts()) ::
{:alias, charlist}
| {:alias, inside_alias, charlist}
| {:block_keyword_or_binary_operator, charlist}
| {:dot, inside_dot, charlist}
| {:dot_arity, inside_dot, charlist}
| {:dot_call, inside_dot, charlist}
@@ -185,15 +245,15 @@ defmodule Code.Fragment do
cursor_context(to_charlist(other), opts)
end
@operators ~c"\\<>+-*/:=|&~^%!"
@starter_punctuation ~c",([{;"
@non_starter_punctuation ~c")]}\"'.$"
@operators ~c"\\<>+-*/:=|&~^%!$"
@starting_punctuation ~c",([{;"
@closing_punctuation ~c")]}\"'"
@space ~c"\t\s"
@trailing_identifier ~c"?!"
@tilde_op_prefix ~c"<=~"
@non_identifier @trailing_identifier ++
@operators ++ @starter_punctuation ++ @non_starter_punctuation ++ @space
@operators ++ @starting_punctuation ++ @closing_punctuation ++ @space ++ [?.]
@textual_operators ~w(when not and or in)c
@keywords ~w(do end after else catch rescue fn true false nil)c
@@ -223,11 +283,11 @@ defmodule Code.Fragment do
# A local arity definition
[?/ | rest] -> arity_to_cursor_context(strip_spaces(rest, spaces + 1))
# Starting a new expression
[h | _] when h in @starter_punctuation -> {:expr, 0}
# It is a local or remote call without parens
rest when spaces > 0 -> call_to_cursor_context({rest, spaces})
[h | _] when h in @starting_punctuation -> {:expr, 0}
# It is keyword, binary operator, a local or remote call without parens
rest when spaces > 0 -> closing_or_call_to_cursor_context({rest, spaces})
# It is an identifier
_ -> identifier_to_cursor_context(reverse, 0, false)
_ -> identifier_to_cursor_context(reverse, spaces, false)
end
end
@@ -242,7 +302,8 @@ defmodule Code.Fragment do
{{:local_or_var, acc}, count} -> {{:local_arity, acc}, count}
{{:dot, base, acc}, count} -> {{:dot_arity, base, acc}, count}
{{:operator, acc}, count} -> {{:operator_arity, acc}, count}
{_, _} -> {:none, 0}
{{:sigil, _}, _} -> {:none, 0}
{_, _} -> {{:operator, ~c"/"}, 1}
end
end
@@ -266,8 +327,16 @@ defmodule Code.Fragment do
end
end
defp closing_or_call_to_cursor_context({reverse, spaces}) do
if closing?(reverse) do
{{:block_keyword_or_binary_operator, ~c""}, 0}
else
call_to_cursor_context({reverse, spaces})
end
end
defp identifier_to_cursor_context([?., ?., ?: | _], n, _), do: {{:unquoted_atom, ~c".."}, n + 3}
defp identifier_to_cursor_context([?., ?., ?. | _], n, _), do: {{:local_or_var, ~c"..."}, n + 3}
defp identifier_to_cursor_context([?., ?., ?. | _], n, _), do: {{:operator, ~c"..."}, n + 3}
defp identifier_to_cursor_context([?., ?: | _], n, _), do: {{:unquoted_atom, ~c"."}, n + 2}
defp identifier_to_cursor_context([?., ?. | _], n, _), do: {{:operator, ~c".."}, n + 2}
@@ -317,8 +386,13 @@ defmodule Code.Fragment do
{~c"." ++ rest, count} when rest == [] or hd(rest) != ?. ->
dot(rest, count + 1, acc)
_ ->
{{:local_or_var, acc}, count}
{rest, rest_count} ->
response =
if rest_count > count and closing?(rest),
do: :block_keyword_or_binary_operator,
else: :local_or_var
{{response, acc}, count}
end
{:capture_arg, acc, count} ->
@@ -326,6 +400,28 @@ defmodule Code.Fragment do
end
end
# If it is a closing punctuation
defp closing?([h | _]) when h in @closing_punctuation, do: true
# Closing bitstring (but deal with operators)
defp closing?([?>, ?> | rest]), do: rest == [] or hd(rest) not in [?>, ?~]
# Keywords
defp closing?(rest) do
case split_non_identifier(rest, []) do
{~c"nil", _} -> true
{~c"true", _} -> true
{~c"false", _} -> true
{[digit | _], _} when digit in ?0..?9 -> true
{[upper | _], _} when upper in ?A..?Z -> true
{[_ | _], [?: | rest]} -> rest == [] or hd(rest) != ?:
{_, _} -> false
end
end
defp split_non_identifier([h | t], acc) when h not in @non_identifier,
do: split_non_identifier(t, [h | acc])
defp split_non_identifier(rest, acc), do: {acc, rest}
defp identifier([?? | rest], count), do: check_identifier(rest, count + 1, [??])
defp identifier([?! | rest], count), do: check_identifier(rest, count + 1, [?!])
defp identifier(rest, count), do: check_identifier(rest, count, [])
@@ -587,7 +683,7 @@ defmodule Code.Fragment do
of examples and their return values.
"""
@doc since: "1.13.0"
@spec surround_context(List.Chars.t(), position(), keyword()) ::
@spec surround_context(List.Chars.t(), position(), cursor_opts()) ::
%{begin: position, end: position, context: context} | :none
when context:
{:alias, charlist}
@@ -696,6 +792,12 @@ defmodule Code.Fragment do
{{:local_or_var, acc}, offset} ->
build_surround({:local_or_var, acc}, reversed, line, offset)
{{:block_keyword_or_binary_operator, acc}, offset} when acc in @textual_operators ->
build_surround({:operator, acc}, reversed, line, offset)
{{:block_keyword_or_binary_operator, acc}, offset} when acc in @keywords ->
build_surround({:keyword, acc}, reversed, line, offset)
{{:module_attribute, ~c""}, offset} ->
build_surround({:operator, ~c"@"}, reversed, line, offset)
@@ -1112,10 +1214,10 @@ defmodule Code.Fragment do
Defaults to `"nofile"`.
* `:line` - the starting line of the string being parsed.
Defaults to 1.
Defaults to `1`.
* `:column` - the starting column of the string being parsed.
Defaults to 1.
Defaults to `1`.
* `:columns` - when `true`, attach a `:column` key to the quoted
metadata. Defaults to `false`.
@@ -1134,7 +1236,7 @@ defmodule Code.Fragment do
"""
@doc since: "1.13.0"
@spec container_cursor_to_quoted(List.Chars.t(), keyword()) ::
@spec container_cursor_to_quoted(List.Chars.t(), container_cursor_to_quoted_opts()) ::
{:ok, Macro.t()} | {:error, {location :: keyword, binary | {binary, binary}, binary}}
def container_cursor_to_quoted(fragment, opts \\ []) do
{trailing_fragment, opts} = Keyword.pop(opts, :trailing_fragment)
@@ -1230,7 +1332,7 @@ defmodule Code.Fragment do
defp drop_tokens([{:do, _} | tokens], counter), do: drop_tokens(tokens, counter + 1)
defp drop_tokens([_ | tokens], counter), do: drop_tokens(tokens, counter)
defp drop_tokens([], 0), do: []
defp drop_tokens([], _counter), do: []
defp maybe_missing_stab?([{:after, _} | _], _stab_choice?), do: true
defp maybe_missing_stab?([{:do, _} | _], _stab_choice?), do: true
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Code.Identifier do
@moduledoc false
+4
View File
@@ -1,3 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
defmodule Code.Normalizer do
@moduledoc false
@@ -11,6 +14,7 @@ defmodule Code.Normalizer do
Wraps literals in the quoted expression to conform to the AST format expected
by the formatter.
"""
@spec normalize(Macro.t(), keyword()) :: Macro.t()
def normalize(quoted, opts \\ []) do
line = Keyword.get(opts, :line, nil)
escape = Keyword.get(opts, :escape, true)
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Code.Typespec do
@moduledoc false
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defprotocol Collectable do
@moduledoc """
A protocol to traverse data structures.
+14 -4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Config do
@moduledoc ~S"""
A simple keyword-based configuration API.
@@ -94,6 +98,12 @@ defmodule Config do
(assembled with `mix release`).
"""
@type config_opts :: [
imports: [Path.t()] | :disabled,
env: atom(),
target: atom()
]
@opts_key {__MODULE__, :opts}
@config_key {__MODULE__, :config}
@imports_key {__MODULE__, :imports}
@@ -131,7 +141,6 @@ defmodule Config do
config :logger,
level: :warn,
backends: [:console]
config :logger,
level: :info,
@@ -139,7 +148,7 @@ defmodule Config do
will have a final configuration for `:logger` of:
[level: :info, backends: [:console], truncate: 1024]
[level: :info, truncate: 1024]
"""
@doc since: "1.9.0"
@@ -224,7 +233,8 @@ defmodule Config do
Returns the environment this configuration file is executed on.
In Mix projects this function returns the environment this configuration
file is executed on. In releases, the environment when `mix release` ran.
file is executed on.
In releases, returns the `MIX_ENV` specified when running `mix release`.
This is most often used to execute conditional code:
@@ -302,7 +312,7 @@ defmodule Config do
end
@doc false
@spec __eval__!(Path.t(), binary(), keyword) :: {keyword, [Path.t()] | :disabled}
@spec __eval__!(Path.t(), binary(), config_opts) :: {keyword, [Path.t()] | :disabled}
def __eval__!(file, content, opts \\ []) when is_binary(file) and is_list(opts) do
env = Keyword.get(opts, :env)
target = Keyword.get(opts, :target)
+15
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Config.Provider do
@moduledoc """
Specifies a provider API that loads configuration during boot.
@@ -107,6 +111,16 @@ defmodule Config.Provider do
"""
@type config_path :: {:system, binary(), binary()} | binary()
@typedoc """
Options for `init/3`.
"""
@type init_opts :: [
extra_config: config(),
prune_runtime_sys_config_after_boot: boolean(),
reboot_system_after_config: boolean(),
validate_compile_env: [{atom(), [atom()], term()}]
]
@doc """
Invoked when initializing a config provider.
@@ -192,6 +206,7 @@ defmodule Config.Provider do
@reboot_mode_key :config_provider_reboot_mode
@doc false
@spec init([{module(), term()}], config_path(), init_opts()) :: config()
def init(providers, config_path, opts \\ []) when is_list(providers) and is_list(opts) do
validate_config_path!(config_path)
providers = for {provider, init} <- providers, do: {provider, provider.init(init)}
+13 -3
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Config.Reader do
@moduledoc """
API for reading config files defined with `Config`.
@@ -42,6 +46,12 @@ defmodule Config.Reader do
@behaviour Config.Provider
@type config_opts :: [
imports: [Path.t()] | :disabled,
env: atom(),
target: atom()
]
@impl true
def init(opts) when is_list(opts) do
{path, opts} = Keyword.pop!(opts, :path)
@@ -64,7 +74,7 @@ defmodule Config.Reader do
Accepts the same options as `read!/2`.
"""
@doc since: "1.11.0"
@spec eval!(Path.t(), binary, keyword) :: keyword
@spec eval!(Path.t(), binary, config_opts) :: keyword
def eval!(file, contents, opts \\ [])
when is_binary(file) and is_binary(contents) and is_list(opts) do
Config.__eval__!(Path.expand(file), contents, opts) |> elem(0)
@@ -86,7 +96,7 @@ defmodule Config.Reader do
"""
@doc since: "1.9.0"
@spec read!(Path.t(), keyword) :: keyword
@spec read!(Path.t(), config_opts) :: keyword
def read!(file, opts \\ []) when is_binary(file) and is_list(opts) do
file = Path.expand(file)
Config.__eval__!(file, File.read!(file), opts) |> elem(0)
@@ -100,7 +110,7 @@ defmodule Config.Reader do
option cannot be disabled in `read_imports!/2`.
"""
@doc since: "1.9.0"
@spec read_imports!(Path.t(), keyword) :: {keyword, [Path.t()]}
@spec read_imports!(Path.t(), config_opts) :: {keyword, [Path.t()]}
def read_imports!(file, opts \\ []) when is_binary(file) and is_list(opts) do
if opts[:imports] == :disabled do
raise ArgumentError, ":imports must be a list of paths"
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Dict do
@moduledoc ~S"""
Generic API for dictionaries.
+31 -66
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule DynamicSupervisor do
@moduledoc ~S"""
A supervisor optimized to only start children dynamically.
@@ -133,67 +137,6 @@ defmodule DynamicSupervisor do
A supervisor is bound to the same name registration rules as a `GenServer`.
Read more about these rules in the documentation for `GenServer`.
## Migrating from Supervisor's :simple_one_for_one
In case you were using the deprecated `:simple_one_for_one` strategy from
the `Supervisor` module, you can migrate to the `DynamicSupervisor` in
few steps.
Imagine the given "old" code:
defmodule MySupervisor do
use Supervisor
def start_link(init_arg) do
Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
def start_child(foo, bar, baz) do
# This will start child by calling MyWorker.start_link(init_arg, foo, bar, baz)
Supervisor.start_child(__MODULE__, [foo, bar, baz])
end
@impl true
def init(init_arg) do
children = [
# Or the deprecated: worker(MyWorker, [init_arg])
%{id: MyWorker, start: {MyWorker, :start_link, [init_arg]}}
]
Supervisor.init(children, strategy: :simple_one_for_one)
end
end
It can be upgraded to the DynamicSupervisor like this:
defmodule MySupervisor do
use DynamicSupervisor
def start_link(init_arg) do
DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
def start_child(foo, bar, baz) do
# If MyWorker is not using the new child specs, we need to pass a map:
# spec = %{id: MyWorker, start: {MyWorker, :start_link, [foo, bar, baz]}}
spec = {MyWorker, foo: foo, bar: bar, baz: baz}
DynamicSupervisor.start_child(__MODULE__, spec)
end
@impl true
def init(init_arg) do
DynamicSupervisor.init(
strategy: :one_for_one,
extra_arguments: [init_arg]
)
end
end
The difference is that the `DynamicSupervisor` expects the child specification
at the moment `start_child/2` is called, and no longer on the init callback.
If there are any initial arguments given on initialization, such as `[initial_arg]`,
it can be given in the `:extra_arguments` flag on `DynamicSupervisor.init/1`.
"""
@behaviour GenServer
@@ -226,7 +169,15 @@ defmodule DynamicSupervisor do
@typedoc "Supported strategies"
@type strategy :: :one_for_one
@typedoc "Return values of `start_child` functions"
@typedoc """
Return values of `start_child` functions.
Unlike `Supervisor`, this module ignores the child spec ids,
so `{:error, {:already_started, pid}}` is not returned for child specs
given with the same id. `{:error, {:already_started, pid}}` is returned
however if a duplicate name is used when using
[name registration](`m:GenServer#module-name-registration`).
"""
@type on_start_child ::
{:ok, pid}
| {:ok, pid, info :: term}
@@ -396,11 +347,17 @@ defmodule DynamicSupervisor do
@doc """
Dynamically adds a child specification to `supervisor` and starts that child.
`child_spec` should be a valid child specification as detailed in the
"Child specification" section of the documentation for `Supervisor`. The child
process will be started as defined in the child specification. Note that while
`child_spec` should be a valid [child specification](`m:Supervisor#module-child-specification`).
The child process will be started as defined in the child specification. Note that while
the `:id` field is still required in the spec, the value is ignored and
therefore does not need to be unique.
therefore does not need to be unique. Unlike `Supervisor`, this module does not
return `{:error, {:already_started, pid}}` for child specs given with the same id.
`{:error, {:already_started, pid}}` is returned however if a duplicate name is
used when using [name registration](`m:GenServer#module-name-registration`).
This function will block the `DynamicSupervisor` until the child initializes.
When starting too many processes dynamically, you may want to use a
`PartitionSupervisor` to split the work across multiple processes.
If the child process start function returns `{:ok, child}` or `{:ok, child,
info}`, then child specification and PID are added to the supervisor and
@@ -505,6 +462,14 @@ defmodule DynamicSupervisor do
@doc """
Terminates the given child identified by `pid`.
This function will block the `DynamicSupervisor` until the child
terminates, which may take an arbitrary amount of time if the child
is trapping exits and implements its own terminate callback.
For this reason, it is often better to ask the child process
itself to terminate, often by declaring in its child spec it has
a restart strategy of `:transient` (or `:temporary`) and then
sending it a message to stop with reason `:shutdown`.
If successful, this function returns `:ok`. If there is no process with
the given PID, this function returns `{:error, :not_found}`.
"""
+186 -62
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defprotocol Enumerable do
@moduledoc """
Enumerable protocol used by `Enum` and `Stream` modules.
@@ -756,25 +760,9 @@ defmodule Enum do
@doc since: "1.12.0"
@spec count_until(t, pos_integer) :: non_neg_integer
def count_until(enumerable, limit) when is_integer(limit) and limit > 0 do
stop_at = limit - 1
case Enumerable.count(enumerable) do
{:ok, value} ->
Kernel.min(value, limit)
{:error, module} ->
enumerable
|> module.reduce(
{:cont, 0},
fn
_, ^stop_at ->
{:halt, limit}
_, acc ->
{:cont, acc + 1}
end
)
|> elem(1)
case enumerable do
list when is_list(list) -> count_until_list(list, limit, 0)
_ -> count_until_enum(enumerable, limit)
end
end
@@ -793,24 +781,10 @@ defmodule Enum do
@doc since: "1.12.0"
@spec count_until(t, (element -> as_boolean(term)), pos_integer) :: non_neg_integer
def count_until(enumerable, fun, limit) when is_integer(limit) and limit > 0 do
stop_at = limit - 1
Enumerable.reduce(enumerable, {:cont, 0}, fn
entry, ^stop_at ->
if fun.(entry) do
{:halt, limit}
else
{:cont, stop_at}
end
entry, acc ->
if fun.(entry) do
{:cont, acc + 1}
else
{:cont, acc}
end
end)
|> elem(1)
case enumerable do
list when is_list(list) -> count_until_list(list, fun, limit, 0)
_ -> count_until_enum(enumerable, fun, limit)
end
end
@doc """
@@ -1074,14 +1048,14 @@ defmodule Enum do
6
iex> Enum.fetch!([2, 4, 6], 4)
** (Enum.OutOfBoundsError) out of bounds error
** (Enum.OutOfBoundsError) out of bounds error at position 4 when traversing enumerable [2, 4, 6]
"""
@spec fetch!(t, index) :: element
def fetch!(enumerable, index) when is_integer(index) do
case slice_forward(enumerable, index, 1, 1) do
[value] -> value
[] -> raise Enum.OutOfBoundsError
[] -> raise Enum.OutOfBoundsError, index: index, enumerable: enumerable
end
end
@@ -2430,7 +2404,7 @@ defmodule Enum do
{:ok, count, fun} when is_function(fun, 3) ->
fun.(random_count(count), 1, 1)
# TODO: Remove deprecation on Elixir v1.20.
# TODO: Remove me on v2.0
{:ok, count, fun} when is_function(fun, 2) ->
IO.warn(
"#{inspect(Enumerable.impl_for(enumerable))} must return a three arity function on slice/1"
@@ -2860,14 +2834,19 @@ defmodule Enum do
end
@doc """
Applies the given function to each element in the `enumerable`,
storing the result in a list and passing it as the accumulator
for the next computation. Uses the first element in the `enumerable`
as the starting value.
Passes each element from `enumerable` to the `fun` as the first argument,
stores the `fun` result in a list and passes the result as the second argument
for the next computation.
The `fun` isn't applied for the first element of the `enumerable`,
the element is taken as it is.
## Examples
iex> Enum.scan(1..5, &(&1 + &2))
iex> Enum.scan(["a", "b", "c", "d", "e"], fn element, acc -> element <> String.first(acc) end)
["a", "ba", "cb", "dc", "ed"]
iex> Enum.scan(1..5, fn element, acc -> element + acc end)
[1, 3, 6, 10, 15]
"""
@@ -2887,13 +2866,18 @@ defmodule Enum do
end
@doc """
Applies the given function to each element in the `enumerable`,
storing the result in a list and passing it as the accumulator
for the next computation. Uses the given `acc` as the starting value.
Passes each element from `enumerable` to the `fun` as the first argument,
stores the `fun` result in a list and passes the result as the second argument
for the next computation.
Passes the given `acc` as the second argument for the `fun` with the first element.
## Examples
iex> Enum.scan(1..5, 0, &(&1 + &2))
iex> Enum.scan(["a", "b", "c", "d", "e"], "_", fn element, acc -> element <> String.first(acc) end)
["a_", "ba", "cb", "dc", "ed"]
iex> Enum.scan(1..5, 0, fn element, acc -> element + acc end)
[1, 3, 6, 10, 15]
"""
@@ -3627,9 +3611,14 @@ defmodule Enum do
end
def take(enumerable, amount) when is_integer(amount) and amount < 0 do
{count, fun} = slice_count_and_fun(enumerable, 1)
first = Kernel.max(amount + count, 0)
fun.(first, count - first, 1)
case slice_count_and_fun(enumerable, 1) do
{0, _fun} ->
[]
{count, fun} ->
first = Kernel.max(amount + count, 0)
fun.(first, count - first, 1)
end
end
@doc """
@@ -3906,6 +3895,7 @@ defmodule Enum do
"""
@spec unzip(t) :: {[element], [element]}
def unzip(enumerable)
def unzip([_ | _] = list) do
:lists.reverse(list) |> unzip([], [])
@@ -3939,8 +3929,9 @@ defmodule Enum do
If an integer offset is given as `fun_or_offset`, it will index from the given
offset instead of from zero.
If a function is given as `fun_or_offset`, it will index by invoking the function
for each element and index (zero-based) of the enumerable.
If a 2-arity function is given as `fun_or_offset`, the function will be invoked
for each element in `enumerable` as the first argument and with a zero-based
index as the second. `with_index/2` returns a list with the result of each invocation.
## Examples
@@ -4051,10 +4042,10 @@ defmodule Enum do
key in the left map and the matching key in the right map, but there is no such
guarantee because map keys are not ordered! Consider the following:
left = %{:a => 1, 1 => 3}
left = %{:a => 1, 1 => 3}
right = %{:a => 1, :b => :c}
Enum.zip(left, right)
# [{{1, 3}, {:a, 1}}, {{:a, 1}, {:b, :c}}]
#=> [{{1, 3}, {:a, 1}}, {{:a, 1}, {:b, :c}}]
As you can see `:a` does not get paired with `:a`. If this is what you want,
you should use `Map.merge/3`.
@@ -4127,8 +4118,11 @@ defmodule Enum do
iex> Enum.zip_reduce([1, 2], [3, 4], 0, fn x, y, acc -> x + y + acc end)
10
iex> Enum.zip_reduce([1, 2], [3, 4], [], fn x, y, acc -> [x + y | acc] end)
[6, 4]
If one of the lists has more entries than the others,
those entries are discarded:
iex> Enum.zip_reduce([1, 2, 3], [4, 5], [], fn x, y, acc -> [x + y | acc] end)
[7, 5]
"""
@doc since: "1.12.0"
@spec zip_reduce(t, t, acc, (enum1_elem :: term, enum2_elem :: term, acc -> acc)) :: acc
@@ -4164,7 +4158,10 @@ defmodule Enum do
...> end)
[{1, 2, 3}, {1, 2, 3}]
iex> enums = [[1, 2], [a: 3, b: 4], [5, 6]]
If one of the lists has more entries than the others,
those entries are discarded:
iex> enums = [[1, 2], [a: 3, b: 4], [5, 6, 7]]
...> Enum.zip_reduce(enums, [], fn elements, acc ->
...> [List.to_tuple(elements) | acc]
...> end)
@@ -4174,8 +4171,8 @@ defmodule Enum do
@spec zip_reduce(t, acc, ([term], acc -> acc)) :: acc when acc: term
def zip_reduce([], acc, reducer) when is_function(reducer, 2), do: acc
def zip_reduce(enums, acc, reducer) when is_function(reducer, 2) do
R.zip_with(enums, & &1).({:cont, acc}, &{:cont, reducer.(&1, &2)}) |> elem(1)
def zip_reduce(enumerables, acc, reducer) when is_function(reducer, 2) do
R.zip_with(enumerables, & &1).({:cont, acc}, &{:cont, reducer.(&1, &2)}) |> elem(1)
end
## Helpers
@@ -4348,6 +4345,64 @@ defmodule Enum do
enum |> reduce([], &reduce(&1, &2, fun)) |> :lists.reverse()
end
# count_until
@compile {:inline, count_until_list: 3}
defp count_until_list([], _limit, acc), do: acc
defp count_until_list([_head | tail], limit, acc) do
case acc + 1 do
^limit -> limit
acc -> count_until_list(tail, limit, acc)
end
end
defp count_until_enum(enumerable, limit) do
case Enumerable.count(enumerable) do
{:ok, value} ->
Kernel.min(value, limit)
{:error, module} ->
module.reduce(enumerable, {:cont, 0}, fn _entry, acc ->
case acc + 1 do
^limit -> {:halt, limit}
acc -> {:cont, acc}
end
end)
|> elem(1)
end
end
@compile {:inline, count_until_list: 4}
defp count_until_list([], _fun, _limit, acc), do: acc
defp count_until_list([head | tail], fun, limit, acc) do
if fun.(head) do
case acc + 1 do
^limit -> limit
acc -> count_until_list(tail, fun, limit, acc)
end
else
count_until_list(tail, fun, limit, acc)
end
end
defp count_until_enum(enumerable, fun, limit) do
Enumerable.reduce(enumerable, {:cont, 0}, fn entry, acc ->
if fun.(entry) do
case acc + 1 do
^limit -> {:halt, limit}
acc -> {:cont, acc}
end
else
{:cont, acc}
end
end)
|> elem(1)
end
# dedup
defp dedup_list([value | tail], acc) do
@@ -4588,7 +4643,7 @@ defmodule Enum do
amount = Kernel.min(amount, count - start) |> amount_with_step(step)
fun.(start, amount, step)
# TODO: Remove me on v2.0.
# TODO: Remove me on v2.0
{:ok, count, fun} when is_function(fun, 2) ->
IO.warn(
"#{inspect(Enumerable.impl_for(enumerable))} must return a three arity function on slice/1"
@@ -5005,3 +5060,72 @@ defimpl Enumerable, for: Function do
description: "only anonymous functions of arity 2 are enumerable"
end
end
defimpl Enumerable, for: Range do
def reduce(first..last//step, acc, fun) do
reduce(first, last, acc, fun, step)
end
# TODO: Remove me on v2.0
def reduce(%{__struct__: Range, first: first, last: last} = range, acc, fun) do
step = if first <= last, do: 1, else: -1
reduce(Map.put(range, :step, step), acc, fun)
end
defp reduce(_first, _last, {:halt, acc}, _fun, _step) do
{:halted, acc}
end
defp reduce(first, last, {:suspend, acc}, fun, step) do
{:suspended, acc, &reduce(first, last, &1, fun, step)}
end
defp reduce(first, last, {:cont, acc}, fun, step)
when step > 0 and first <= last
when step < 0 and first >= last do
reduce(first + step, last, fun.(first, acc), fun, step)
end
defp reduce(_, _, {:cont, acc}, _fun, _up) do
{:done, acc}
end
def member?(first..last//step, value) when is_integer(value) do
if step > 0 do
{:ok, first <= value and value <= last and rem(value - first, step) == 0}
else
{:ok, last <= value and value <= first and rem(value - first, step) == 0}
end
end
# TODO: Remove me on v2.0
def member?(%{__struct__: Range, first: first, last: last} = range, value)
when is_integer(value) do
step = if first <= last, do: 1, else: -1
member?(Map.put(range, :step, step), value)
end
def member?(_, _value) do
{:ok, false}
end
def count(range) do
{:ok, Range.size(range)}
end
def slice(first.._//step = range) do
{:ok, Range.size(range), &slice(first + &1 * step, step + &3 - 1, &2)}
end
# TODO: Remove me on v2.0
def slice(%{__struct__: Range, first: first, last: last} = range) do
step = if first <= last, do: 1, else: -1
slice(Map.put(range, :step, step))
end
defp slice(current, _step, 1), do: [current]
defp slice(current, step, remaining) when remaining > 1 do
[current | slice(current + step, step, remaining - 1)]
end
end
+289 -159
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Exception do
@moduledoc """
Functions for dealing with throw/catch/exit and exceptions.
@@ -78,6 +82,11 @@ defmodule Exception do
@doc """
Gets the message for an `exception`.
This function will invoke the `c:message/1` callback on the exception
module to retrieve the message. If the callback raises an exception or
returns a non-binary value, this function will rescue the error and
return a descriptive error message instead.
"""
@spec message(t) :: String.t()
def message(%module{__exception__: true} = exception) do
@@ -149,7 +158,7 @@ defmodule Exception do
end
@doc """
Normalizes and formats throw/errors/exits and stacktraces.
Normalizes and formats throws/errors/exits and stacktraces.
It relies on `format_banner/3` and `format_stacktrace/1`
to generate the final format.
@@ -173,15 +182,33 @@ defmodule Exception do
end
end
@doc false
def __format_message_with_term__(message, term) do
inspected =
term
|> inspect(pretty: true)
|> String.split("\n")
|> Enum.map_intersperse("\n", fn
"" -> ""
line -> " " <> line
end)
IO.iodata_to_binary([message, "\n\n", inspected, "\n"])
end
@doc """
Attaches information to exceptions for extra debugging.
Attaches information to throws/errors/exits for extra debugging.
This operation is potentially expensive, as it reads data
from the file system, parses beam files, evaluates code and
so on.
If the exception module implements the optional `c:blame/2`
callback, it will be invoked to perform the computation.
If `kind` argument is `:error` and the `error` is an Erlang exception, this function will
normalize it. If the `error` argument is an Elixir exception, this function will invoke
the optional `c:blame/2` callback on the exception module if it is implemented.
Unlike `message/1`, this function will not rescue errors - if the callback raises an exception,
the error will propagate to the caller. It is your choice if you want to rescue and return
the original exception, return a different exception, or let it cascade.
"""
@doc since: "1.5.0"
@spec blame(:error, any, stacktrace) :: {t, stacktrace}
@@ -1040,7 +1067,12 @@ defmodule ArgumentError do
An exception raised when an argument to a function is invalid.
You can raise this exception when you want to signal that an argument to
a function is invalid.
a function is invalid. For example, this exception is raised when calling
`Integer.to_string/1` with an invalid argument:
iex> Integer.to_string(1.0)
** (ArgumentError) errors were found at the given arguments:
...
`ArgumentError` exceptions have a single field, `:message` (a `t:String.t/0`),
which is public and can be accessed freely when reading or creating `ArgumentError`
@@ -1057,8 +1089,7 @@ defmodule ArithmeticError do
For example, this exception is raised if you divide by `0`:
iex> 1 / 0
** (ArithmeticError) bad argument in arithmetic expression: 1 / 0
** (ArithmeticError) bad argument in arithmetic expression
"""
defexception message: "bad argument in arithmetic expression"
@@ -1104,7 +1135,10 @@ defmodule SystemLimitError do
@moduledoc """
An exception raised when a system limit has been reached.
For example, this can happen if you try to create an atom that is too large.
For example, this can happen if you try to create an atom that is too large:
iex> String.to_atom(String.duplicate("a", 100_000))
** (SystemLimitError) a system limit has been reached
"""
defexception message: "a system limit has been reached"
@@ -1116,8 +1150,9 @@ defmodule MismatchedDelimiterError do
For example:
* `[1, 2, 3}`
* `fn a -> )`
iex> Code.eval_string("[1, 2, 3}")
** (MismatchedDelimiterError) mismatched delimiter found on nofile:1:9:
...
The following fields of this exceptions are public and can be accessed freely:
@@ -1131,7 +1166,6 @@ defmodule MismatchedDelimiterError do
* `:closing_delimiter` - an atom representing the mismatched closing delimiter
* `:expected_delimiter` - an atom representing the closing delimiter
* `:description` - a description of the mismatched delimiter error
"""
defexception [
@@ -1190,6 +1224,12 @@ defmodule SyntaxError do
@moduledoc """
An exception raised when there's a syntax error when parsing code.
For example:
iex> Code.eval_string("5 + 5h")
** (SyntaxError) invalid syntax found on nofile:1:5:
...
The following fields of this exceptions are public and can be accessed freely:
* `:file` (`t:Path.t/0` or `nil`) - the file where the error occurred, or `nil` if
@@ -1197,7 +1237,6 @@ defmodule SyntaxError do
* `:line` - the line where the error occurred
* `:column` - the column where the error occurred
* `:description` - a description of the syntax error
"""
defexception [:file, :line, :column, :snippet, description: "syntax error"]
@@ -1241,6 +1280,12 @@ defmodule TokenMissingError do
@moduledoc """
An exception raised when a token is missing when parsing code.
For example:
iex> Code.eval_string("[1, 2, 3")
** (TokenMissingError) token missing on nofile:1:9:
...
The following fields of this exceptions are public and can be accessed freely:
* `:file` (`t:Path.t/0` or `nil`) - the file where the error occurred, or `nil` if
@@ -1252,6 +1297,8 @@ defmodule TokenMissingError do
* `:opening_delimiter` - an atom representing the opening delimiter
* `:expected_delimiter` - an atom representing the expected delimiter
* `:description` - a description of the missing token error
This is mostly raised by Elixir tooling when compiling and evaluating code.
"""
defexception [
@@ -1331,12 +1378,18 @@ defmodule CompileError do
@moduledoc """
An exception raised when there's an error when compiling code.
For example:
1 = y
** (CompileError) iex:1: undefined variable "y"
The following fields of this exceptions are public and can be accessed freely:
* `:file` (`t:Path.t/0` or `nil`) - the file where the error occurred, or `nil` if
the error occurred in code that did not come from a file
* `:line` (`t:non_neg_integer/0`) - the line where the error occurred
This is mostly raised by Elixir tooling when compiling and evaluating code.
"""
defexception [:file, :line, description: "compile error"]
@@ -1354,12 +1407,19 @@ defmodule Kernel.TypespecError do
@moduledoc """
An exception raised when there's an error in a typespec.
For example, if your typespec definition points to an invalid type, you get an exception:
@type my_type :: intger()
will raise:
** (Kernel.TypespecError) type intger/0 undefined
The following fields of this exceptions are public and can be accessed freely:
* `:file` (`t:Path.t/0` or `nil`) - the file where the error occurred, or `nil` if
the error occurred in code that did not come from a file
* `:line` (`t:non_neg_integer/0`) - the line where the error occurred
"""
defexception [:file, :line, :description]
@@ -1374,6 +1434,16 @@ defmodule Kernel.TypespecError do
end
defmodule BadFunctionError do
@moduledoc """
An exception raised when a function is expected, but something else was given.
For example:
iex> value = "hello"
iex> value.()
** (BadFunctionError) expected a function, got: "hello"
"""
defexception [:term]
@impl true
@@ -1387,37 +1457,61 @@ defmodule BadFunctionError do
end
defmodule BadStructError do
@moduledoc deprecated:
"This exception is deprecated alongside the struct update syntax that raises it"
defexception [:struct, :term]
@impl true
def message(exception) do
"expected a struct named #{inspect(exception.struct)}, got: #{inspect(exception.term)}"
Exception.__format_message_with_term__(
"expected a struct named #{inspect(exception.struct)}, got:",
exception.term
)
end
end
defmodule BadMapError do
@moduledoc """
An exception raised when something expected a map, but received something else.
An exception raised when a map is expected, but something else was given.
For example:
iex> value = "hello"
iex> %{value | key: "value"}
** (BadMapError) expected a map, got:
...
"""
defexception [:term]
@impl true
def message(exception) do
"expected a map, got: #{inspect(exception.term)}"
Exception.__format_message_with_term__(
"expected a map, got:",
exception.term
)
end
end
defmodule BadBooleanError do
@moduledoc """
An exception raised when an operator expected a boolean, but received something else.
An exception raised when a boolean is expected, but something else was given.
This exception is raised by `and` and `or` when the first argument is not a boolean:
iex> 123 and true
** (BadBooleanError) expected a boolean on left-side of "and", got:
...
"""
defexception [:term, :operator]
@impl true
def message(exception) do
"expected a boolean on left-side of \"#{exception.operator}\", got: #{inspect(exception.term)}"
Exception.__format_message_with_term__(
"expected a boolean on left-side of \"#{exception.operator}\", got:",
exception.term
)
end
end
@@ -1425,21 +1519,26 @@ defmodule MatchError do
@moduledoc """
An exception raised when a pattern match (`=/2`) fails.
For example:
iex> [_ | _] = []
** (MatchError) no match of right hand side value:
...
The following fields of this exception are public and can be accessed freely:
* `:term` (`t:term/0`) - the term that did not match the pattern
For example, this exception gets raised for code like this:
[_ | _] = []
"""
defexception [:term]
@impl true
def message(exception) do
"no match of right hand side value: #{inspect(exception.term)}"
Exception.__format_message_with_term__(
"no match of right hand side value:",
exception.term
)
end
end
@@ -1448,24 +1547,28 @@ defmodule CaseClauseError do
An exception raised when a term in a `case/2` expression
does not match any of the defined `->` clauses.
For example:
iex> case System.unique_integer() do
...> bin when is_binary(bin) -> :oops
...> :ok -> :neither_this_one
...> end
** (CaseClauseError) no case clause matching:
...
The following fields of this exception are public and can be accessed freely:
* `:term` (`t:term/0`) - the term that did not match any of the clauses
For example, this exception gets raised for a `case/2` like the following:
case System.unique_integer() do
bin when is_binary(bin) -> :oops
:ok -> :neither_this_one
end
"""
defexception [:term]
@impl true
def message(exception) do
"no case clause matching: #{inspect(exception.term)}"
Exception.__format_message_with_term__(
"no case clause matching:",
exception.term
)
end
end
@@ -1474,28 +1577,32 @@ defmodule WithClauseError do
An exception raised when a term in a `with/1` expression
does not match any of the defined `->` clauses in its `else`.
The following fields of this exception are public and can be accessed freely:
* `:term` (`t:term/0`) - the term that did not match any of the clauses
For example, this exception gets raised for a `with/1` like the following, because
the `{:ok, 2}` term does not match the `:error` or `{:error, _}` clauses in the
`else`:
with {:ok, 1} <- {:ok, 2} do
:woah
else
:error -> :error
{:error, _} -> :error
end
iex> with {:ok, 1} <- {:ok, 2} do
...> :woah
...> else
...> :error -> :error
...> {:error, _} -> :error
...> end
** (WithClauseError) no with clause matching:
...
The following fields of this exception are public and can be accessed freely:
* `:term` (`t:term/0`) - the term that did not match any of the clauses
"""
defexception [:term]
@impl true
def message(exception) do
"no with clause matching: #{inspect(exception.term)}"
Exception.__format_message_with_term__(
"no with clause matching:",
exception.term
)
end
end
@@ -1505,11 +1612,11 @@ defmodule CondClauseError do
For example, this exception gets raised for a `cond/1` like the following:
cond do
1 + 1 == 3 -> :woah
nil -> "yeah this won't happen
end
iex> cond do
...> 1 + 1 == 3 -> :woah
...> nil -> "yeah this won't happen"
...> end
** (CondClauseError) no cond clause evaluated to a truthy value
"""
defexception []
@@ -1522,8 +1629,20 @@ end
defmodule TryClauseError do
@moduledoc """
An exception raised when a term in a `try/1` expression
does not match any of the defined `->` clauses in its `else`.
An exception raised when none of the `else` clauses in a `try/1` match.
For example:
iex> try do
...> :ok
...> rescue
...> e -> e
...> else
...> # :ok -> :ok is missing
...> :not_ok -> :not_ok
...> end
** (TryClauseError) no try clause matching:
...
The following fields of this exception are public and can be accessed freely:
@@ -1534,13 +1653,22 @@ defmodule TryClauseError do
@impl true
def message(exception) do
"no try clause matching: #{inspect(exception.term)}"
Exception.__format_message_with_term__(
"no try clause matching:",
exception.term
)
end
end
defmodule BadArityError do
@moduledoc """
An exception raised when a function is called with the wrong number of arguments.
For example:
my_function = fn x, y -> x + y end
my_function.(42)
** (BadArityError) #Function<41.39164016/2 in :erl_eval.expr/6> with arity 2 called with 1 argument (42)
"""
defexception [:function, :args]
@@ -1563,22 +1691,17 @@ defmodule UndefinedFunctionError do
@moduledoc """
An exception raised when a function is invoked that is not defined.
For example:
# Let's use apply/3 as otherwise Elixir emits a compile-time warning
iex> apply(String, :non_existing_fun, ["hello"])
** (UndefinedFunctionError) function String.non_existing_fun/1 is undefined or private
The following fields of this exception are public and can be accessed freely:
* `:module` (`t:module/0`) - the module name
* `:function` (`t:atom/0`) - the function name
* `:arity` (`t:non_neg_integer/0`) - the arity of the function
For example, if you try to call `MyMod.non_existing_fun("hello", 1)`,
the error would look like:
%UndefinedFunctionError{
module: MyMod,
function: :non_existing_fun,
arity: 2,
# Other private fields...
}
"""
@function_threshold 0.77
@@ -1788,7 +1911,7 @@ defmodule UndefinedFunctionError do
end
defp format_fa({_dist, fun, arity}) do
[" * ", Macro.inspect_atom(:remote_call, fun), ?/, Integer.to_string(arity), ?\n]
[" * ", Macro.inspect_atom(:remote_call, fun), ?/, Integer.to_string(arity), ?\n]
end
defp exports_for(module) do
@@ -1818,22 +1941,16 @@ defmodule FunctionClauseError do
@moduledoc """
An exception raised when a function call doesn't match any defined clause.
For example:
iex> URI.parse(:wrong_argument)
** (FunctionClauseError) no function clause matching in URI.parse/1
The following fields of this exception are public and can be accessed freely:
* `:module` (`t:module/0`) - the module name
* `:function` (`t:atom/0`) - the function name
* `:arity` (`t:non_neg_integer/0`) - the arity of the function
For example, if you try to call a function such as `URI.parse/1` with something
other than a string, the error would look like:
%FunctionClauseError{
module: URI,
function: :parse,
arity: 1,
# Other private fields...
}
"""
defexception [:module, :function, :arity, :kind, :args, :clauses]
@@ -1953,11 +2070,15 @@ defmodule Code.LoadError do
@moduledoc """
An exception raised when a file cannot be loaded.
This is typically raised by functions in the `Code` module, for example:
Code.require_file("missing_file.exs")
** (Code.LoadError) could not load missing_file.exs. Reason: enoent
The following fields of this exception are public and can be accessed freely:
* `:file` (`t:String.t/0`) - the file name
* `:reason` (`t:term/0`) - the reason why the file could not be loaded
"""
defexception [:file, :message, :reason]
@@ -1974,23 +2095,16 @@ defmodule Protocol.UndefinedError do
@moduledoc """
An exception raised when a protocol is not implemented for a given value.
For example:
iex> Enum.at("A string!", 0)
** (Protocol.UndefinedError) protocol Enumerable not implemented for BitString
...
The following fields of this exception are public and can be accessed freely:
* `:protocol` (`t:module/0`) - the protocol that is not implemented
* `:value` (`t:term/0`) - the value that does not implement the protocol
For example, this code:
Enum.at("A string!", 0)
would raise the following exception:
%Protocol.UndefinedError{
protocol: Enumerable,
value: "A string!",
# ...
}
"""
defexception [:protocol, :value, description: ""]
@@ -2003,7 +2117,7 @@ defmodule Protocol.UndefinedError do
# Indent only lines with contents on them
|> String.replace(~r/^(?=.+)/m, " ")
"protocol #{inspect(protocol)} not implemented for type " <>
"protocol #{inspect(protocol)} not implemented for " <>
value_type(value) <>
maybe_description(description) <>
maybe_available(protocol) <>
@@ -2038,7 +2152,7 @@ defmodule Protocol.UndefinedError do
". There are no implementations for this protocol."
{:consolidated, types} ->
". This protocol is implemented for the following type(s): " <>
". This protocol is implemented for: " <>
Enum.map_join(types, ", ", &inspect/1)
:not_consolidated ->
@@ -2052,13 +2166,17 @@ defmodule KeyError do
An exception raised when a key is not found in a data structure.
For example, this is raised by `Map.fetch!/2` when the given key
cannot be found in the given map.
cannot be found in the given map:
iex> map = %{name: "Alice", age: 25}
iex> Map.fetch!(map, :first_name)
** (KeyError) key :first_name not found in:
...
The following fields of this exception are public and can be accessed freely:
* `:term` (`t:term/0`) - the data structure that was searched
* `:key` (`t:term/0`) - the key that was not found
"""
defexception [:key, :term, :message]
@@ -2080,7 +2198,10 @@ defmodule KeyError do
"make sure to add parentheses after the function name)"
true ->
message <> " in: #{inspect(term, pretty: true, limit: :infinity)}"
Exception.__format_message_with_term__(
message <> " in:",
term
)
end
end
@@ -2122,7 +2243,7 @@ defmodule KeyError do
case suggestions do
[] -> []
suggestions -> [". Did you mean:\n\n" | format_suggestions(suggestions)]
suggestions -> ["\nDid you mean:\n\n" | format_suggestions(suggestions)]
end
end
@@ -2131,11 +2252,20 @@ defmodule KeyError do
|> Enum.sort(&(elem(&1, 0) >= elem(&2, 0)))
|> Enum.take(@max_suggestions)
|> Enum.sort(&(elem(&1, 1) <= elem(&2, 1)))
|> Enum.map(fn {_, key} -> [" * ", inspect(key), ?\n] end)
|> Enum.map(fn {_, key} -> [" * ", inspect(key), ?\n] end)
end
end
defmodule UnicodeConversionError do
@moduledoc """
An exception raised when converting data to or from Unicode.
For example:
iex> String.to_charlist(<<0xFF>>)
** (UnicodeConversionError) invalid encoding starting at <<255>>
"""
defexception [:encoded, :message]
def exception(opts) do
@@ -2213,10 +2343,31 @@ defmodule Enum.OutOfBoundsError do
An exception that is raised when a function expects an enumerable to have
a certain size but finds that it is too small.
For example, this is raised by `Access.at!/1`.
For example:
iex> Enum.fetch!([1, 2, 3], 5)
** (Enum.OutOfBoundsError) out of bounds error at position 5 when traversing enumerable [1, 2, 3]
"""
defexception message: "out of bounds error"
defexception [:enumerable, :index, :message]
@impl true
def message(exception = %{message: nil}), do: message(exception.index, exception.enumerable)
def message(%{message: message}), do: message
def message(index, enumerable) do
"out of bounds error" <>
if index do
" at position #{index}"
else
""
end <>
if enumerable do
" when traversing enumerable #{inspect(enumerable)}"
else
""
end
end
end
defmodule Enum.EmptyError do
@@ -2224,7 +2375,11 @@ defmodule Enum.EmptyError do
An exception that is raised when something expects a non-empty enumerable
but finds an empty one.
For example, this is raised by `Enum.min/3`.
For example:
iex> Enum.min([])
** (Enum.EmptyError) empty error
"""
defexception message: "empty error"
@@ -2234,6 +2389,11 @@ defmodule File.Error do
@moduledoc """
An exception that is raised when a file operation fails.
For example, this exception is raised, when trying to read a non existent file:
iex> File.read!("nonexistent_file.txt")
** (File.Error) could not read file "nonexistent_file.txt": no such file or directory
The following fields of this exception are public and can be accessed freely:
* `:path` (`t:Path.t/0`) - the path of the file that caused the error
@@ -2262,6 +2422,11 @@ defmodule File.CopyError do
@moduledoc """
An exception that is raised when copying a file fails.
For example, this exception is raised when trying to copy to file or directory that isn't present:
iex> File.cp_r!("non_existent", "source_dir/subdir")
** (File.CopyError) could not copy recursively from "non_existent" to "source_dir/subdir". non_existent: no such file or directory
The following fields of this exception are public and can be accessed freely:
* `:source` (`t:Path.t/0`) - the source path
@@ -2291,6 +2456,11 @@ defmodule File.RenameError do
@moduledoc """
An exception that is raised when renaming a file fails.
For example, this exception is raised when trying to rename a file that isn't present:
iex> File.rename!("source.txt", "target.txt")
** (File.RenameError) could not rename from "source.txt" to "target.txt": no such file or directory
The following fields of this exception are public and can be accessed freely:
* `:source` (`t:Path.t/0`) - the source path
@@ -2320,6 +2490,11 @@ defmodule File.LinkError do
@moduledoc """
An exception that is raised when linking a file fails.
For example, this exception is raised when trying to link to file that isn't present:
iex> File.ln!("existing.txt", "link.txt")
** (File.LinkError) could not create hard link from "link.txt" to "existing.txt": no such file or directory
The following fields of this exception are public and can be accessed freely:
* `:existing` (`t:Path.t/0`) - the existing file to link
@@ -2334,12 +2509,25 @@ defmodule File.LinkError do
def message(exception) do
formatted = IO.iodata_to_binary(:file.format_error(exception.reason))
"could not #{exception.action} from #{inspect(exception.existing)} to " <>
"#{inspect(exception.new)}: #{formatted}"
"could not #{exception.action} from #{inspect(exception.new)} to " <>
"#{inspect(exception.existing)}: #{formatted}"
end
end
defmodule ErlangError do
@moduledoc """
An exception raised when invoking an Erlang code that errors
with a value not handled by Elixir.
Most common error reasons, such as `:badarg` are automatically
converted into exceptions by Elixir. However, you may invoke some
code that emits a custom error reason and those get wrapped into
`ErlangError`:
iex> :erlang.error(:some_invalid_error)
** (ErlangError) Erlang error: :some_invalid_error
"""
defexception [:original, :reason]
@impl true
@@ -2362,7 +2550,7 @@ defmodule ErlangError do
is_map(module) and is_atom(function) and is_map_key(module, function) ->
"you attempted to apply a function named #{inspect(function)} on a map/struct. " <>
"If you are using Kernel.apply/3, make sure the module is an atom. " <>
if is_function(module[function]) do
if is_function(Map.get(module, function)) do
"If you are trying to invoke an anonymous function in a map/struct, " <>
"add a dot between the function name and the parenthesis: map.#{function}.()"
else
@@ -2547,61 +2735,3 @@ defmodule ErlangError do
defp nth(3), do: "3rd"
defp nth(n), do: "#{n}th"
end
defmodule Inspect.Error do
@moduledoc """
Raised when a struct cannot be inspected.
"""
@enforce_keys [:exception_module, :exception_message, :stacktrace, :inspected_struct]
defexception @enforce_keys
@impl true
def exception(arguments) when is_list(arguments) do
exception = Keyword.fetch!(arguments, :exception)
exception_module = exception.__struct__
exception_message = Exception.message(exception) |> String.trim_trailing("\n")
stacktrace = Keyword.fetch!(arguments, :stacktrace)
inspected_struct = Keyword.fetch!(arguments, :inspected_struct)
%Inspect.Error{
exception_module: exception_module,
exception_message: exception_message,
stacktrace: stacktrace,
inspected_struct: inspected_struct
}
end
@impl true
def message(%__MODULE__{
exception_module: exception_module,
exception_message: exception_message,
inspected_struct: inspected_struct
}) do
~s'''
got #{inspect(exception_module)} with message:
"""
#{pad(exception_message, 4)}
"""
while inspecting:
#{pad(inspected_struct, 4)}
'''
end
@doc false
def pad(message, padding_length)
when is_binary(message) and is_integer(padding_length) and padding_length >= 0 do
padding = String.duplicate(" ", padding_length)
message
|> String.split("\n")
|> Enum.map(fn
"" -> "\n"
line -> [padding, line, ?\n]
end)
|> IO.iodata_to_binary()
|> String.trim_trailing("\n")
end
end
+478 -55
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule File do
@moduledoc ~S"""
This module contains functions to manipulate files.
@@ -262,8 +266,15 @@ defmodule File do
* `:enotdir` - a component of `path` is not a directory;
on some platforms, `:enoent` is returned instead
## Examples
File.mkdir("test/unit")
#=> :ok
File.mkdir("non/existing")
#=> {:error, :enoent}
"""
@spec mkdir(Path.t()) :: :ok | {:error, posix}
@spec mkdir(Path.t()) :: :ok | {:error, posix | :badarg}
def mkdir(path) do
:file.make_dir(IO.chardata_to_string(path))
end
@@ -271,6 +282,14 @@ defmodule File do
@doc """
Same as `mkdir/1`, but raises a `File.Error` exception in case of failure.
Otherwise `:ok`.
## Examples
File.mkdir!("test/unit")
#=> :ok
File.mkdir!("non/existing")
** (File.Error) could not make directory "non/existing": no such file or directory
"""
@spec mkdir!(Path.t()) :: :ok
def mkdir!(path) do
@@ -298,9 +317,17 @@ defmodule File do
directories of `path`
* `:enospc` - there is no space left on the device
* `:enotdir` - a component of `path` is not a directory
* `:eperm` - missed required permissions
## Examples
File.mkdir_p("non/existing/parents")
#=> :ok
File.mkdir_p("/usr/sbin/temp")
#=> {:error, :eperm}
"""
@spec mkdir_p(Path.t()) :: :ok | {:error, posix}
@spec mkdir_p(Path.t()) :: :ok | {:error, posix | :badarg}
def mkdir_p(path) do
do_mkdir_p(IO.chardata_to_string(path))
end
@@ -310,24 +337,23 @@ defmodule File do
end
defp do_mkdir_p(path) do
if dir?(path) do
parent = Path.dirname(path)
if parent == path do
:ok
else
parent = Path.dirname(path)
case do_mkdir_p(parent) do
:ok ->
case :file.make_dir(path) do
{:error, :eexist} ->
if dir?(path), do: :ok, else: {:error, :enotdir}
if parent == path do
# Protect against infinite loop
{:error, :einval}
else
_ = do_mkdir_p(parent)
other ->
other
end
case :file.make_dir(path) do
{:error, :eexist} = error ->
if dir?(path), do: :ok, else: error
other ->
other
end
e ->
e
end
end
end
@@ -335,6 +361,14 @@ defmodule File do
@doc """
Same as `mkdir_p/1`, but raises a `File.Error` exception in case of failure.
Otherwise `:ok`.
## Examples
File.mkdir_p!("non/existing/parents")
#=> :ok
File.mkdir_p!("/usr/sbin/temp")
** (File.Error) could not make directory (with -p) "/usr/sbin/temp": not owner
"""
@spec mkdir_p!(Path.t()) :: :ok
def mkdir_p!(path) do
@@ -365,8 +399,16 @@ defmodule File do
* `:enomem` - there is not enough memory for the contents of the file
You can use `:file.format_error/1` to get a descriptive string of the error.
## Examples
File.read("hello.txt")
#=> {:ok, "world"}
File.read("non_existing.txt")
#=> {:error, :enoent}
"""
@spec read(Path.t()) :: {:ok, binary} | {:error, posix}
@spec read(Path.t()) :: {:ok, binary} | {:error, posix | :badarg | :terminated | :system_limit}
def read(path) do
:file.read_file(IO.chardata_to_string(path))
end
@@ -374,6 +416,14 @@ defmodule File do
@doc """
Returns a binary with the contents of the given filename,
or raises a `File.Error` exception if an error occurs.
## Examples
File.read!("hello.txt")
#=> "world"
File.read!("non_existing.txt")
** (File.Error) could not read file "non_existing.txt": no such file or directory
"""
@spec read!(Path.t()) :: binary
def read!(path) do
@@ -407,8 +457,16 @@ defmodule File do
Note: Since file times are stored in POSIX time format on most operating systems,
it is faster to retrieve file information with the `time: :posix` option.
## Examples
File.stat("hello.txt")
#=> {:ok, %File.Stat{...}}
File.stat("non_existing.txt", time: :posix)
#=> {:error, :enoent}
"""
@spec stat(Path.t(), stat_options) :: {:ok, File.Stat.t()} | {:error, posix}
@spec stat(Path.t(), stat_options) :: {:ok, File.Stat.t()} | {:error, posix | :badarg}
def stat(path, opts \\ []) do
opts = Keyword.put_new(opts, :time, :universal)
@@ -424,6 +482,14 @@ defmodule File do
@doc """
Same as `stat/2` but returns the `File.Stat` directly,
or raises a `File.Error` exception if an error is returned.
## Examples
File.stat!("hello.txt")
#=> %File.Stat{...}
File.stat!("non_existing.txt", time: :posix)
** (File.Error) could not read file stats "non_existing.txt": no such file or directory
"""
@spec stat!(Path.t(), stat_options) :: File.Stat.t()
def stat!(path, opts \\ []) do
@@ -460,8 +526,16 @@ defmodule File do
Note: Since file times are stored in POSIX time format on most operating systems,
it is faster to retrieve file information with the `time: :posix` option.
## Examples
File.lstat("link_to_hello")
#=> {:ok, %File.Stat{type: :symlink, ...}}
File.lstat("non_existing.txt", time: :posix)
#=> {:error, :enoent}
"""
@spec lstat(Path.t(), stat_options) :: {:ok, File.Stat.t()} | {:error, posix}
@spec lstat(Path.t(), stat_options) :: {:ok, File.Stat.t()} | {:error, posix | :badarg}
def lstat(path, opts \\ []) do
opts = Keyword.put_new(opts, :time, :universal)
@@ -477,6 +551,14 @@ defmodule File do
@doc """
Same as `lstat/2` but returns the `File.Stat` struct directly,
or raises a `File.Error` exception if an error is returned.
## Examples
File.lstat!("link_to_hello")
#=> %File.Stat{type: :symlink, ...}
File.lstat!("non_existing.txt", time: :posix)
** (File.Error) could not read file stats "non_existing.txt": no such file or directory
"""
@spec lstat!(Path.t(), stat_options) :: File.Stat.t()
def lstat!(path, opts \\ []) do
@@ -506,9 +588,16 @@ defmodule File do
* `:enoent` - path does not exist
* `:enotsup` - symbolic links are not supported on the current platform
## Examples
File.read_link("link_to_hello")
#=> {:ok, "hello.txt"}
File.read_link("hello.txt")
#=> {:error, :einval}
"""
@doc since: "1.5.0"
@spec read_link(Path.t()) :: {:ok, binary} | {:error, posix}
@spec read_link(Path.t()) :: {:ok, binary} | {:error, posix | :badarg}
def read_link(path) do
case path |> IO.chardata_to_string() |> :file.read_link() do
{:ok, target} -> {:ok, IO.chardata_to_string(target)}
@@ -519,6 +608,14 @@ defmodule File do
@doc """
Same as `read_link/1` but returns the target directly,
or raises a `File.Error` exception if an error is returned.
## Examples
File.read_link!("link_to_hello")
#=> "hello.txt"
File.read_link!("hello.txt")
** (File.Error) could not read link "hello.txt": invalid argument
"""
@doc since: "1.5.0"
@spec read_link!(Path.t()) :: binary
@@ -535,8 +632,16 @@ defmodule File do
@doc """
Writes the given `File.Stat` back to the file system at the given
path. Returns `:ok` or `{:error, reason}`.
## Examples
File.write_stat("hello.txt", new_stat)
#=> :ok
File.write_stat("non_existing.txt", new_stat)
#=> {:error, :enoent}
"""
@spec write_stat(Path.t(), File.Stat.t(), stat_options) :: :ok | {:error, posix}
@spec write_stat(Path.t(), File.Stat.t(), stat_options) :: :ok | {:error, posix | :badarg}
def write_stat(path, stat, opts \\ []) do
opts = Keyword.put_new(opts, :time, :universal)
:file.write_file_info(IO.chardata_to_string(path), File.Stat.to_record(stat), opts)
@@ -545,6 +650,14 @@ defmodule File do
@doc """
Same as `write_stat/3` but raises a `File.Error` exception if it fails.
Returns `:ok` otherwise.
## Examples
File.write_stat!("hello.txt", new_stat)
#=> :ok
File.write_stat!("non_existing.txt", new_stat)
** (File.Error) could not write file stats "non_existing.txt": no such file or directory
"""
@spec write_stat!(Path.t(), File.Stat.t(), stat_options) :: :ok
def write_stat!(path, stat, opts \\ []) do
@@ -585,14 +698,15 @@ defmodule File do
#=> :ok
"""
@spec touch(Path.t(), erlang_time() | posix_time()) :: :ok | {:error, posix}
@spec touch(Path.t(), erlang_time() | posix_time()) ::
:ok | {:error, posix | :badarg | :terminated | :system_limit}
def touch(path, time \\ System.os_time(:second))
def touch(path, time) when is_tuple(time) do
path = IO.chardata_to_string(path)
with {:error, :enoent} <- :elixir_utils.change_universal_time(path, time),
:ok <- write(path, "", [:append]),
:ok <- write(path, "", [:raw, :append]),
do: :elixir_utils.change_universal_time(path, time)
end
@@ -600,7 +714,7 @@ defmodule File do
path = IO.chardata_to_string(path)
with {:error, :enoent} <- :elixir_utils.change_posix_time(path, time),
:ok <- write(path, "", [:append]),
:ok <- write(path, "", [:raw, :append]),
do: :elixir_utils.change_posix_time(path, time)
end
@@ -639,9 +753,17 @@ defmodule File do
Returns `:ok` if successful, `{:error, reason}` otherwise.
If the operating system does not support hard links, returns
`{:error, :enotsup}`.
## Examples
File.ln("hello.txt", "hard_link_to_hello")
#=> :ok
File.ln("non_existing.txt", "link")
#=> {:error, :enoent}
"""
@doc since: "1.5.0"
@spec ln(Path.t(), Path.t()) :: :ok | {:error, posix}
@spec ln(Path.t(), Path.t()) :: :ok | {:error, posix | :badarg}
def ln(existing, new) do
:file.make_link(IO.chardata_to_string(existing), IO.chardata_to_string(new))
end
@@ -649,6 +771,14 @@ defmodule File do
@doc """
Same as `ln/2` but raises a `File.LinkError` exception if it fails.
Returns `:ok` otherwise.
## Examples
File.ln!("hello.txt", "hard_link_to_hello")
#=> :ok
File.ln!("non_existing.txt", "link")
** (File.LinkError) could not create hard link from "non_existing.txt" to "link": no such file or directory
"""
@doc since: "1.5.0"
@spec ln!(Path.t(), Path.t()) :: :ok
@@ -672,9 +802,23 @@ defmodule File do
Returns `:ok` if successful, `{:error, reason}` otherwise.
If the operating system does not support symlinks, returns
`{:error, :enotsup}`.
Creates a symlink even if the `existing` target actually doesn't exist
## Examples
File.ln_s("hello.txt", "link_to_hello")
#=> :ok
File.ln_s("non_existing.txt", "link")
#=> :ok
# Returns error if `new` file exists
File.ln_s("non_existing.txt", "existed_link")
#=> {:error, :eexist}
"""
@doc since: "1.5.0"
@spec ln_s(Path.t(), Path.t()) :: :ok | {:error, posix}
@spec ln_s(Path.t(), Path.t()) :: :ok | {:error, posix | :badarg}
def ln_s(existing, new) do
:file.make_symlink(IO.chardata_to_string(existing), IO.chardata_to_string(new))
end
@@ -682,6 +826,15 @@ defmodule File do
@doc """
Same as `ln_s/2` but raises a `File.LinkError` exception if it fails.
Returns `:ok` otherwise.
## Examples
File.ln_s!("hello.txt", "link_to_hello")
#=> :ok
# Raises if `new` file exists
File.ln_s!("non_existing.txt", "existed_link")
** (File.LinkError) could not create symlink from "non_existing.txt" to "existed_link": file already exists
"""
@spec ln_s!(Path.t(), Path.t()) :: :ok
def ln_s!(existing, new) do
@@ -719,9 +872,17 @@ defmodule File do
Typical error reasons are the same as in `open/2`,
`read/1` and `write/3`.
## Examples
File.copy("hello.txt", "hello_copy.txt")
#=> {:ok, 6}
File.copy("non_existing.txt", "copy.txt")
#=> {:error, :enoent}
"""
@spec copy(Path.t() | io_device, Path.t() | io_device, pos_integer | :infinity) ::
{:ok, non_neg_integer} | {:error, posix}
{:ok, non_neg_integer} | {:error, posix | :badarg | :terminated}
def copy(source, destination, bytes_count \\ :infinity) do
source = normalize_path_or_io_device(source)
destination = normalize_path_or_io_device(destination)
@@ -732,6 +893,14 @@ defmodule File do
@doc """
The same as `copy/3` but raises a `File.CopyError` exception if it fails.
Returns the `bytes_copied` otherwise.
## Examples
File.copy!("hello.txt", "hello_copy.txt")
#=> 6
File.copy!("non_existing.txt", "copy.txt")
** (File.CopyError) could not copy from "non_existing.txt" to "copy.txt": no such file or directory
"""
@spec copy!(Path.t() | io_device, Path.t() | io_device, pos_integer | :infinity) ::
non_neg_integer
@@ -765,13 +934,17 @@ defmodule File do
# Rename file "a.txt" to "b.txt"
File.rename("a.txt", "b.txt")
#=> :ok
# Rename directory "samples" to "tmp"
File.rename("samples", "tmp")
#=> :ok
File.rename("non_existing.txt", "existing.txt")
#=> {:error, :enoent}
"""
@doc since: "1.1.0"
@spec rename(Path.t(), Path.t()) :: :ok | {:error, posix}
@spec rename(Path.t(), Path.t()) :: :ok | {:error, posix | :badarg}
def rename(source, destination) do
source = IO.chardata_to_string(source)
destination = IO.chardata_to_string(destination)
@@ -781,6 +954,14 @@ defmodule File do
@doc """
The same as `rename/2` but raises a `File.RenameError` exception if it fails.
Returns `:ok` otherwise.
## Examples
File.rename!("samples", "tmp")
#=> :ok
File.rename!("non_existing.txt", "existing.txt")
** (File.RenameError) could not rename from "non_existing.txt" to "existing.txt": no such file or directory
"""
@doc since: "1.9.0"
@spec rename!(Path.t(), Path.t()) :: :ok
@@ -798,7 +979,7 @@ defmodule File do
end
end
@doc """
@doc ~S"""
Copies the contents of `source_file` to `destination_file` preserving its modes.
`source_file` must be a file or a symbolic link to one. `destination_file` must
@@ -825,12 +1006,33 @@ defmodule File do
The default callback returns `true`. On earlier versions, this callback could be
given as third argument, but such behavior is now deprecated.
## Examples
File.cp("hello.txt", "hello_copy.txt")
#=> :ok
File.cp("hello.txt", "hello_copy.txt", on_conflict: fn source, destination ->
IO.gets("Overwriting #{destination} by #{source}. Type y to confirm. ") == "y\n"
end)
#=> :ok
File.cp("non_existing.txt", "copy.txt")
#=> {:error, :enoent}
"""
@spec cp(Path.t(), Path.t(), on_conflict: on_conflict_callback) :: :ok | {:error, posix}
@spec cp(Path.t(), Path.t(), on_conflict: on_conflict_callback) ::
:ok | {:error, posix | :badarg | :terminated}
def cp(source_file, destination_file, options \\ [])
# TODO: Deprecate me on Elixir v1.19
def cp(source_file, destination_file, callback) when is_function(callback, 2) do
IO.warn_once(
{__MODULE__, :cp},
fn ->
"passing a callback to File.cp/3 is deprecated, pass it as a on_conflict: callback option instead"
end,
3
)
cp(source_file, destination_file, on_conflict: callback)
end
@@ -851,9 +1053,22 @@ defmodule File do
Path.expand(p1) !== Path.expand(p2)
end
@doc """
@doc ~S"""
The same as `cp/3`, but raises a `File.CopyError` exception if it fails.
Returns `:ok` otherwise.
## Examples
File.cp!("hello.txt", "hello_copy.txt")
#=> :ok
File.cp!("hello.txt", "hello_copy.txt", on_conflict: fn source, destination ->
IO.gets("Overwriting #{destination} by #{source}. Type y to confirm. ") == "y\n"
end)
#=> :ok
File.cp!("non_existing.txt", "copy.txt")
** (File.CopyError) could not copy from "non_existing.txt" to "copy.txt": no such file or directory
"""
@spec cp!(Path.t(), Path.t(), on_conflict: on_conflict_callback) :: :ok
def cp!(source_file, destination_file, options \\ []) do
@@ -916,26 +1131,39 @@ defmodule File do
# Copies file "a.txt" to "b.txt"
File.cp_r("a.txt", "b.txt")
#=> {:ok, ["b.txt"]}
# Copies all files in "samples" to "tmp"
File.cp_r("samples", "tmp")
#=> {:ok, ["z.txt", "y.txt", "x.txt]}
# Same as before, but asks the user how to proceed in case of conflicts
File.cp_r("samples", "tmp", on_conflict: fn source, destination ->
IO.gets("Overwriting #{destination} by #{source}. Type y to confirm. ") == "y\n"
end)
#=> {:ok, ["z.txt", "y.txt", "x.txt]}
File.cp_r("non_existing.txt", "copy.txt")
#=> {:error, :enoent}
"""
@spec cp_r(Path.t(), Path.t(),
on_conflict: on_conflict_callback,
dereference_symlinks: boolean()
) ::
{:ok, [binary]} | {:error, posix, binary}
{:ok, [binary]} | {:error, posix | :badarg | :terminated, binary}
def cp_r(source, destination, options \\ [])
# TODO: Deprecate me on Elixir v1.19
def cp_r(source, destination, callback) when is_function(callback, 2) do
IO.warn_once(
{__MODULE__, :cp_r},
fn ->
"passing a callback to File.cp_r/3 is deprecated, pass it as a on_conflict: callback option instead"
end,
3
)
cp_r(source, destination, on_conflict: callback)
end
@@ -962,6 +1190,17 @@ defmodule File do
@doc """
The same as `cp_r/3`, but raises a `File.CopyError` exception if it fails.
Returns the list of copied files otherwise.
## Examples
File.cp_r!("a.txt", "b.txt")
#=> ["b.txt"]
File.cp_r!("samples", "tmp")
#=> ["z.txt", "y.txt", "x.txt]
File.cp_r!("non_existing.txt", "copy.txt")
** (File.CopyError) could not copy recursively from "non_existing.txt" to "copy.txt". non_existing.txt: no such file or directory
"""
@spec cp_r!(Path.t(), Path.t(),
on_conflict: on_conflict_callback,
@@ -985,7 +1224,13 @@ defmodule File do
defp do_cp_r(src, dest, on_conflict, dereference?, acc) when is_list(acc) do
case :elixir_utils.read_link_type(src) do
{:ok, :regular} ->
do_cp_file(src, dest, on_conflict, acc)
case do_cp_file(src, dest, on_conflict, acc) do
# we don't have a way to make a distinction between a non-existing src
# or dest being a non-existing dir in the case of :enoent,
# but we already know that src exists here.
{:error, :enoent, _} -> {:error, :enoent, dest}
other -> other
end
{:ok, :symlink} ->
case :file.read_link(src) do
@@ -1122,9 +1367,19 @@ defmodule File do
the parent directories
* `:eisdir` - the named file is a directory
Check `File.open/2` for other available options.
Check `File.open/2` for the list of available `modes`.
## Examples
File.write("hello.txt", "world!")
#=> :ok
File.write("temp", "world!")
#=> {:error, :eisdir}
"""
@spec write(Path.t(), iodata, [mode]) :: :ok | {:error, posix}
@spec write(Path.t(), iodata, [mode]) ::
:ok | {:error, posix | :badarg | :terminated | :system_limit}
def write(path, content, modes \\ []) do
modes = normalize_modes(modes, false)
:file.write_file(IO.chardata_to_string(path), content, modes)
@@ -1133,6 +1388,14 @@ defmodule File do
@doc """
Same as `write/3` but raises a `File.Error` exception if it fails.
Returns `:ok` otherwise.
## Examples
File.write!("hello.txt", "world!")
#=> :ok
File.write!("temp", "world!")
** (File.Error) could not write to file "temp": illegal operation on a directory
"""
@spec write!(Path.t(), iodata, [mode]) :: :ok
def write!(path, content, modes \\ []) do
@@ -1171,9 +1434,8 @@ defmodule File do
File.rm("tmp_dir/")
#=> {:error, :eperm}
"""
@spec rm(Path.t()) :: :ok | {:error, posix}
@spec rm(Path.t()) :: :ok | {:error, posix | :badarg}
def rm(path) do
path = IO.chardata_to_string(path)
@@ -1211,6 +1473,14 @@ defmodule File do
@doc """
Same as `rm/1`, but raises a `File.Error` exception in case of failure.
Otherwise `:ok`.
## Examples
File.rm!("file.txt")
#=> :ok
File.rm!("non_existing/")
** (File.Error) could not remove file "non_existing/": no such file or directory
"""
@spec rm!(Path.t()) :: :ok
def rm!(path) do
@@ -1239,9 +1509,8 @@ defmodule File do
File.rmdir("file.txt")
#=> {:error, :enotdir}
"""
@spec rmdir(Path.t()) :: :ok | {:error, posix}
@spec rmdir(Path.t()) :: :ok | {:error, posix | :badarg}
def rmdir(path) do
:file.del_dir(IO.chardata_to_string(path))
end
@@ -1249,8 +1518,19 @@ defmodule File do
@doc """
Same as `rmdir/1`, but raises a `File.Error` exception in case of failure.
Otherwise `:ok`.
## Examples
File.rmdir!("tmp_dir")
#=> :ok
File.rmdir!("non_empty_dir")
** (File.Error) could not remove directory "non_empty_dir": directory is not empty
File.rmdir!("file.txt")
** (File.Error) could not remove directory "file.txt": not a directory
"""
@spec rmdir!(Path.t()) :: :ok | {:error, posix}
@spec rmdir!(Path.t()) :: :ok
def rmdir!(path) do
case rmdir(path) do
:ok ->
@@ -1281,8 +1561,10 @@ defmodule File do
File.rm_rf("unknown")
#=> {:ok, []}
File.rm_rf("/tmp")
#=> {:error, :eperm, "/tmp"}
"""
@spec rm_rf(Path.t()) :: {:ok, [binary]} | {:error, posix, binary}
@spec rm_rf(Path.t()) :: {:ok, [binary]} | {:error, posix | :badarg, binary}
def rm_rf(path) do
{major, _} = :os.type()
@@ -1376,7 +1658,18 @@ defmodule File do
@doc """
Same as `rm_rf/1` but raises a `File.Error` exception in case of failures,
otherwise the list of files or directories removed.
otherwise returns the list of files or directories removed.
## Examples
File.rm_rf!("samples")
#=> ["samples", "samples/1.txt"]
File.rm_rf!("unknown")
#=> []
File.rm_rf!("/tmp")
** (File.Error) could not remove files and directories recursively from "/tmp": not owner
"""
@spec rm_rf!(Path.t()) :: [binary]
def rm_rf!(path) do
@@ -1480,8 +1773,10 @@ defmodule File do
File.close(file)
"""
@spec open(Path.t(), [mode | :ram]) :: {:ok, io_device | file_descriptor} | {:error, posix}
@spec open(Path.t(), (io_device | file_descriptor -> res)) :: {:ok, res} | {:error, posix}
@spec open(Path.t(), [mode | :ram]) ::
{:ok, io_device | file_descriptor} | {:error, posix | :badarg | :system_limit}
@spec open(Path.t(), (io_device | file_descriptor -> res)) ::
{:ok, res} | {:error, posix | :badarg | :system_limit}
when res: var
def open(path, modes_or_function \\ [])
@@ -1508,16 +1803,17 @@ defmodule File do
is given. For this reason, we do not recommend passing
`:delayed_write` to this function.
See `open/2` for the list of available `modes`.
## Examples
File.open("file.txt", [:read, :write], fn file ->
IO.read(file, :line)
end)
See `open/2` for the list of available `modes`.
#=> {:ok, "file content"}
"""
@spec open(Path.t(), [mode | :ram], (io_device | file_descriptor -> res)) ::
{:ok, res} | {:error, posix}
{:ok, res} | {:error, posix | :badarg | :system_limit}
when res: var
def open(path, modes, function) when is_list(modes) and is_function(function, 1) do
case open(path, modes) do
@@ -1538,6 +1834,13 @@ defmodule File do
could not be opened. Returns the IO device otherwise.
See `open/2` for the list of available modes.
## Examples
File.open!("file.txt", fn file ->
IO.read(file, :line)
end)
#=> "file content"
"""
@spec open!(Path.t(), [mode | :ram]) :: io_device | file_descriptor
@spec open!(Path.t(), (io_device | file_descriptor -> res)) :: res when res: var
@@ -1558,6 +1861,13 @@ defmodule File do
If it succeeds opening the file, it returns the `function` result on the IO device.
See `open/2` for the list of available `modes`.
## Examples
File.open!("file.txt", [:read, :write], fn file ->
IO.read(file, :line)
end)
#=> "file content"
"""
@spec open!(Path.t(), [mode | :ram], (io_device | file_descriptor -> res)) :: res when res: var
def open!(path, modes, function) do
@@ -1577,8 +1887,17 @@ defmodule File do
if read permissions do not exist for the parent directories of the
current directory. For this reason, returns `{:ok, cwd}` in case
of success, `{:error, reason}` otherwise.
## Examples
File.cwd()
#=> {:ok, "/Users/user/elixir/elixir_lang"}
# Missing read permission for one of the parents of the current directory
File.cwd()
#=> {:error, :eacces}
"""
@spec cwd() :: {:ok, binary} | {:error, posix}
@spec cwd() :: {:ok, binary} | {:error, posix | :badarg}
def cwd() do
case :file.get_cwd() do
{:ok, base} -> {:ok, IO.chardata_to_string(fix_drive_letter(base))}
@@ -1597,6 +1916,11 @@ defmodule File do
@doc """
The same as `cwd/0`, but raises a `File.Error` exception if it fails.
## Examples
File.cwd!()
#=> "/Users/user/elixir/elixir_lang"
"""
@spec cwd!() :: binary
def cwd!() do
@@ -1619,14 +1943,30 @@ defmodule File do
of `System.cmd/3` and `Port.open/2`.
Returns `:ok` if successful, `{:error, reason}` otherwise.
## Examples
File.cd("bin")
#=> :ok
File.cd("non_existing_dir")
#=> {:error, :enoent}
"""
@spec cd(Path.t()) :: :ok | {:error, posix}
@spec cd(Path.t()) :: :ok | {:error, posix | :badarg | :no_translation}
def cd(path) do
:file.set_cwd(IO.chardata_to_string(path))
end
@doc """
The same as `cd/1`, but raises a `File.Error` exception if it fails.
## Examples
File.cd!("bin")
#=> :ok
File.cd!("non_existing_dir")
** (File.Error) could not set current working directory to "non_existing_dir": no such file or directory
"""
@spec cd!(Path.t()) :: :ok
def cd!(path) do
@@ -1655,6 +1995,14 @@ defmodule File do
Raises an error if retrieving or changing the current
directory fails.
## Examples
File.cd!("bin", fn -> do_something() end)
#=> :result_of_do_something
File.cd!("non_existing_dir", fn -> do_something() end)
** (File.Error) could not set current working directory to "non_existing_dir": no such file or directory
"""
@spec cd!(Path.t(), (-> res)) :: res when res: var
def cd!(path, function) do
@@ -1678,8 +2026,16 @@ defmodule File do
Returns `{:ok, files}` in case of success,
`{:error, reason}` otherwise.
## Examples
File.ls("bin")
#=> {:ok, ["iex", "elixir"]}
File.ls("non_existing_dir")
#=> {:error, :enoent}
"""
@spec ls(Path.t()) :: {:ok, [binary]} | {:error, posix}
@spec ls(Path.t()) :: {:ok, [binary]} | {:error, posix | :badarg | {:no_translation, binary}}
def ls(path \\ ".") do
case :file.list_dir(IO.chardata_to_string(path)) do
{:ok, file_list} -> {:ok, Enum.map(file_list, &IO.chardata_to_string/1)}
@@ -1689,6 +2045,14 @@ defmodule File do
@doc """
The same as `ls/1` but raises a `File.Error` exception in case of an error.
## Examples
File.ls!("bin")
#=> ["iex", "elixir"]
File.ls!("non_existing_dir")
** (File.Error) could not list directory "non_existing_dir": no such file or directory
"""
@spec ls!(Path.t()) :: [binary]
def ls!(path \\ ".") do
@@ -1711,6 +2075,15 @@ defmodule File do
Note that if the option `:delayed_write` was used when opening the file,
`close/1` might return an old write error and not even try to close the file.
See `open/2` for more information.
## Examples
{:ok, file} = File.open("hello.txt")
File.close(file)
#=> :ok
File.close(:not_an_io_device)
#=> {:error, :badarg}
"""
@spec close(io_device) :: :ok | {:error, posix | :badarg | :terminated}
def close(io_device) do
@@ -1776,15 +2149,17 @@ defmodule File do
that is skipped whenever enumerating the stream (if both `:read_offset`
and `:trim_bom` are given, the offset is skipped after the BOM).
See `Stream.run/1` for an example of streaming into a file.
## Examples
# Read a utf8 text file which may include BOM
File.stream!("./test/test.txt", [:trim_bom, encoding: :utf8])
#=> %File.Stream{path: "./test/test.txt", ...}
# Read in 2048 byte chunks rather than lines
File.stream!("./test/test.data", 2048)
See `Stream.run/1` for an example of streaming into a file.
#=> %File.Stream{path: "./test/test.data", ...}
"""
@spec stream!(Path.t(), :line | pos_integer, [stream_mode]) :: File.Stream.t()
def stream!(path, line_or_bytes, modes)
@@ -1824,8 +2199,16 @@ defmodule File do
write, read and execute permission to the owner
and both read and execute permission to group
and others.
## Examples
File.chmod("hello.txt", 0o755)
#=> :ok
File.chmod("non_existing.txt", 0o755)
#=> {:error, :enoent}
"""
@spec chmod(Path.t(), non_neg_integer) :: :ok | {:error, posix}
@spec chmod(Path.t(), non_neg_integer) :: :ok | {:error, posix | :badarg}
def chmod(path, mode) do
:file.change_mode(IO.chardata_to_string(path), mode)
end
@@ -1833,6 +2216,14 @@ defmodule File do
@doc """
Same as `chmod/2`, but raises a `File.Error` exception in case of failure.
Otherwise `:ok`.
## Examples
File.chmod!("hello.txt", 0o755)
#=> :ok
File.chmod!("non_existing.txt", 0o755)
** (File.Error) could not change mode for "non_existing.txt": no such file or directory
"""
@spec chmod!(Path.t(), non_neg_integer) :: :ok
def chmod!(path, mode) do
@@ -1852,8 +2243,16 @@ defmodule File do
Changes the group given by the group ID `gid`
for a given `file`. Returns `:ok` on success, or
`{:error, reason}` on failure.
## Examples
File.chgrp("hello.txt", 10)
#=> :ok
File.chgrp("non_existing.txt", 10)
#=> {:error, :enoent}
"""
@spec chgrp(Path.t(), non_neg_integer) :: :ok | {:error, posix}
@spec chgrp(Path.t(), non_neg_integer) :: :ok | {:error, posix | :badarg}
def chgrp(path, gid) do
:file.change_group(IO.chardata_to_string(path), gid)
end
@@ -1861,6 +2260,14 @@ defmodule File do
@doc """
Same as `chgrp/2`, but raises a `File.Error` exception in case of failure.
Otherwise `:ok`.
## Examples
File.chgrp!("hello.txt", 10)
#=> :ok
File.chgrp!("non_existing.txt", 10)
** (File.Error) could not change group for "non_existing.txt": no such file or directory
"""
@spec chgrp!(Path.t(), non_neg_integer) :: :ok
def chgrp!(path, gid) do
@@ -1880,8 +2287,16 @@ defmodule File do
Changes the owner given by the user ID `uid`
for a given `file`. Returns `:ok` on success,
or `{:error, reason}` on failure.
## Examples
File.chown("hello.txt", 15)
#=> :ok
File.chown("secret.txt", 15)
#=> {:error, :eperm}
"""
@spec chown(Path.t(), non_neg_integer) :: :ok | {:error, posix}
@spec chown(Path.t(), non_neg_integer) :: :ok | {:error, posix | :badarg}
def chown(path, uid) do
:file.change_owner(IO.chardata_to_string(path), uid)
end
@@ -1889,6 +2304,14 @@ defmodule File do
@doc """
Same as `chown/2`, but raises a `File.Error` exception in case of failure.
Otherwise `:ok`.
## Examples
File.chown!("hello.txt", 15)
#=> :ok
File.chown!("secret.txt", 15)
** (File.Error) could not change owner for "secret.txt": not owner
"""
@spec chown!(Path.t(), non_neg_integer) :: :ok
def chown!(path, uid) do
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
require Record
defmodule File.Stat do
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule File.Stream do
@moduledoc """
Defines a `File.Stream` struct returned by `File.stream!/3`.
+23 -9
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
import Kernel, except: [round: 1]
defmodule Float do
@@ -164,40 +168,50 @@ defmodule Float do
end
defp parse_unsigned(<<digit, rest::binary>>) when digit in ?0..?9,
do: parse_unsigned(rest, false, false, <<digit>>)
do: parse_unsigned(rest, false, false, [digit])
defp parse_unsigned(binary) when is_binary(binary), do: :error
defp parse_unsigned(<<digit, rest::binary>>, dot?, e?, acc) when digit in ?0..?9,
do: parse_unsigned(rest, dot?, e?, <<acc::binary, digit>>)
do: parse_unsigned(rest, dot?, e?, [digit | acc])
defp parse_unsigned(<<?., digit, rest::binary>>, false, false, acc) when digit in ?0..?9,
do: parse_unsigned(rest, true, false, <<acc::binary, ?., digit>>)
do: parse_unsigned(rest, true, false, [digit, ?. | acc])
defp parse_unsigned(<<exp_marker, digit, rest::binary>>, dot?, false, acc)
when exp_marker in ~c"eE" and digit in ?0..?9,
do: parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, digit>>)
do: parse_unsigned(rest, true, true, [digit, ?e | add_dot(acc, dot?)])
defp parse_unsigned(<<exp_marker, sign, digit, rest::binary>>, dot?, false, acc)
when exp_marker in ~c"eE" and sign in ~c"-+" and digit in ?0..?9,
do: parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, sign, digit>>)
do: parse_unsigned(rest, true, true, [digit, sign, ?e | add_dot(acc, dot?)])
# When floats are expressed in scientific notation, :erlang.binary_to_float/1 can raise an
# ArgumentError if the e exponent is too big. For example, "1.0e400". Because of this, we
# rescue the ArgumentError here and return an error.
defp parse_unsigned(rest, dot?, true = _e?, acc) do
:erlang.binary_to_float(add_dot(acc, dot?))
acc
|> add_dot(dot?)
|> :lists.reverse()
|> :erlang.list_to_float()
rescue
ArgumentError -> :error
else
float -> {float, rest}
end
defp parse_unsigned(rest, dot?, false = _e?, acc),
do: {:erlang.binary_to_float(add_dot(acc, dot?)), rest}
defp parse_unsigned(rest, dot?, false = _e?, acc) do
float =
acc
|> add_dot(dot?)
|> :lists.reverse()
|> :erlang.list_to_float()
{float, rest}
end
defp add_dot(acc, true), do: acc
defp add_dot(acc, false), do: acc <> ".0"
defp add_dot(acc, false), do: [?0, ?. | acc]
@doc """
Rounds a float to the largest float less than or equal to `number`.
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Function do
@moduledoc """
A set of functions for working with functions.
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule GenEvent do
# Functions from this module are deprecated in elixir_dispatch.
+8
View File
@@ -1,5 +1,10 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule GenEvent.Stream do
@moduledoc false
@moduledoc deprecated: "This functionality is no longer supported"
defstruct manager: nil, timeout: :infinity
@type t :: %__MODULE__{manager: GenEvent.manager(), timeout: timeout}
@@ -46,6 +51,9 @@ defmodule GenEvent.Stream do
end
defimpl Enumerable, for: GenEvent.Stream do
@moduledoc false
@moduledoc deprecated: "This functionality is no longer supported"
def reduce(stream, acc, fun) do
start_fun = fn -> start(stream) end
next_fun = &next(stream, &1)
+19 -5
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule GenServer do
@moduledoc """
A behaviour module for implementing the server of a client-server relation.
@@ -203,14 +207,16 @@ defmodule GenServer do
The generated `child_spec/1` can be customized with the following options:
* `:id` - the child specification identifier, defaults to the current module
* `:restart` - when the child should be restarted, defaults to `:permanent`
* `:shutdown` - how to shut down the child, either immediately or by giving it time to shut down
* [`:restart`](`m:Supervisor#module-restart-values-restart`) - when the
child should be restarted, defaults to `:permanent`
* [`:shutdown`](`m:Supervisor#module-shutdown-values-shutdown`) - how to
shut down the child, either immediately or by giving it time to shut down
For example:
use GenServer, restart: :transient, shutdown: 10_000
See the "Child specification" section in the `Supervisor` module for more
See the ["Child specification"](`m:Supervisor#module-child_spec-1-function`) section in the `Supervisor` module for more
detailed information. The `@doc` annotation immediately preceding
`use GenServer` will be attached to the generated `child_spec/1` function.
@@ -265,6 +271,14 @@ defmodule GenServer do
generated atoms won't be garbage-collected. For such cases, you can
set up your own local registry by using the `Registry` module.
For example:
{:ok, _} = Registry.start_link(keys: :unique, name: :stacks)
name = {:via, Registry, {:stacks, "stack 1"}}
{:ok, _pid} = GenServer.start_link(Stack, "hello", name: name)
GenServer.whereis(name)
#=> #PID<0.150.0>
## Receiving "regular" messages
The goal of a `GenServer` is to abstract the "receive" loop for developers,
@@ -1098,8 +1112,8 @@ defmodule GenServer do
arrives or a timeout occurs. `c:handle_call/3` will be called on the server
to handle the request.
`server` can be any of the values described in the "Name registration"
section of the documentation for this module.
`server` can be a PID or any of the other values described in the
"Name registration" section of the documentation for this module.
## Timeouts
+12
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule HashDict do
@moduledoc """
Tuple-based HashDict implementation.
@@ -256,6 +260,9 @@ defmodule HashDict do
end
defimpl Enumerable, for: HashDict do
@moduledoc false
@moduledoc deprecated: "Use Map instead"
def reduce(dict, acc, fun) do
# Avoid warnings about HashDict being deprecated.
module = String.to_atom("HashDict")
@@ -284,6 +291,9 @@ defimpl Enumerable, for: HashDict do
end
defimpl Collectable, for: HashDict do
@moduledoc false
@moduledoc deprecated: "Use Map instead"
def into(original) do
# Avoid warnings about HashDict being deprecated.
module = String.to_atom("HashDict")
@@ -299,6 +309,8 @@ defimpl Collectable, for: HashDict do
end
defimpl Inspect, for: HashDict do
@moduledoc false
@moduledoc deprecated: "Use Map instead"
import Inspect.Algebra
def inspect(dict, opts) do
+12
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule HashSet do
@moduledoc """
Tuple-based HashSet implementation.
@@ -270,6 +274,9 @@ defmodule HashSet do
end
defimpl Enumerable, for: HashSet do
@moduledoc false
@moduledoc deprecated: "Use MapSet instead"
def reduce(set, acc, fun) do
# Avoid warnings about HashSet being deprecated.
module = String.to_atom("HashSet")
@@ -294,6 +301,9 @@ defimpl Enumerable, for: HashSet do
end
defimpl Collectable, for: HashSet do
@moduledoc false
@moduledoc deprecated: "Use MapSet instead"
def into(original) do
# Avoid warnings about HashSet being deprecated.
module = String.to_atom("HashSet")
@@ -309,6 +319,8 @@ defimpl Collectable, for: HashSet do
end
defimpl Inspect, for: HashSet do
@moduledoc false
@moduledoc deprecated: "Use MapSet instead"
import Inspect.Algebra
def inspect(set, opts) do
+119 -76
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
import Kernel, except: [inspect: 1]
import Inspect.Algebra
@@ -60,9 +64,11 @@ defprotocol Inspect do
* `:except` - remove the given fields when inspecting.
* `:optional` - (since v1.14.0) do not include a field if it
matches its default value. This can be used to simplify the
struct representation at the cost of hiding information.
* `:optional` - (since v1.14.0) a list of fields that should not be
included when they match their default value. This can be used to
simplify the struct representation at the cost of hiding
information. Since v1.19.0, the `:all` atom can be passed to
mark all fields as optional.
Whenever `:only` or `:except` are used to restrict fields,
the struct will be printed using the `#User<...>` notation,
@@ -78,15 +84,25 @@ defprotocol Inspect do
#=> #User<id: 1, name: "Jane", ...>
If you use only the `:optional` option, the struct will still be
printed as `%User{...}`.
printed as a valid struct.
defmodule Point do
@derive {Inspect, optional: [:z]}
defstruct [x: 0, y: 0, z: 0]
end
inspect(%Point{x: 1})
%Point{x: 1, y: 0}
## Custom implementation
You can also define your custom protocol implementation by
defining the `inspect/2` function. The function receives the
entity to be inspected followed by the inspecting options,
represented by the struct `Inspect.Opts`. Building of the
algebra document is done with `Inspect.Algebra`.
represented by the struct `Inspect.Opts` and it must return
an algebra document alongside the updated options (or, optionally,
just the algebra document). Building of the algebra document
is done with `Inspect.Algebra`.
Many times, inspecting a structure can be implemented in function
of existing entities. For example, here is `MapSet`'s `inspect/2`
@@ -96,16 +112,19 @@ defprotocol Inspect do
import Inspect.Algebra
def inspect(map_set, opts) do
concat(["MapSet.new(", Inspect.List.inspect(MapSet.to_list(map_set), opts), ")"])
{doc, opts} = to_doc_with_opts(MapSet.to_list(map_set), opts)
{concat(["MapSet.new(", doc, ")"]), opts}
end
end
The [`concat/1`](`Inspect.Algebra.concat/1`) function comes from
`Inspect.Algebra` and it concatenates algebra documents together.
First [`to_doc_with_opts/2`](`Inspect.Algebra.to_doc_with_opts/2`) is
used to convert another data structure into its algebra document and
then [`concat/1`](`Inspect.Algebra.concat/1`) concatenates algebra
documents together.
In the example above it is concatenating the string `"MapSet.new("`,
the document returned by `Inspect.Algebra.to_doc/2`, and the final
string `")"`. Therefore, the MapSet with the numbers 1, 2, and 3
will be printed as:
the document returned by `to_doc_with_opts/2`, and the final string `")"`.
Therefore, the MapSet with the numbers 1, 2, and 3 will be printed as:
iex> MapSet.new([1, 2, 3], fn x -> x * 2 end)
MapSet.new([2, 4, 6])
@@ -134,6 +153,9 @@ defprotocol Inspect do
Inspect.MapSet.inspect(MapSet.new(), %Inspect.Opts{})
Note that, from Elixir v1.19, the inspect protocol was augmented to
allow a two-element tuple with the document and the updated options
to be returned from the protocol.
"""
# Handle structs in Any
@@ -146,11 +168,19 @@ defprotocol Inspect do
only = Keyword.get(options, :only, fields)
except = Keyword.get(options, :except, [])
optional = Keyword.get(options, :optional, [])
:ok = validate_option(:only, only, fields, module)
:ok = validate_option(:except, except, fields, module)
:ok = validate_option(:optional, optional, fields, module)
optional =
case Keyword.get(options, :optional, []) do
:all ->
fields
optional ->
:ok = validate_option(:optional, optional, fields, module)
optional
end
inspect_module =
if fields == Enum.sort(only) and except == [] do
@@ -200,13 +230,26 @@ defprotocol Inspect do
do: var!(info)
var!(name) = Macro.inspect_atom(:literal, unquote(module))
unquote(inspect_module).inspect(var!(struct), var!(name), var!(infos), var!(opts))
unquote(inspect_module).inspect_as_struct(
var!(struct),
var!(name),
var!(infos),
var!(opts)
)
end
end
end
end
defp validate_option(option, option_list, fields, module) do
if not is_list(option_list) do
raise ArgumentError,
"invalid value #{Kernel.inspect(option_list)} in #{Kernel.inspect(option)} " <>
"when deriving the Inspect protocol for #{Kernel.inspect(module)} " <>
"(expected a list)"
end
case option_list -- fields do
[] ->
:ok
@@ -226,7 +269,8 @@ defprotocol Inspect do
`Inspect.Algebra.to_doc/2` should be preferred as it handles structs
and exceptions.
"""
@spec inspect(t, Inspect.Opts.t()) :: Inspect.Algebra.t()
@spec inspect(t, Inspect.Opts.t()) ::
Inspect.Algebra.t() | {Inspect.Algebra.t(), Inspect.Opts.t()}
def inspect(term, opts)
end
@@ -268,11 +312,13 @@ defimpl Inspect, for: BitString do
color_doc("<<>>", :binary, opts)
end
defp inspect_bitstring(bitstring, opts) do
defp inspect_bitstring(bitstring, %{limit: limit} = opts) do
left = color_doc("<<", :binary, opts)
right = color_doc(">>", :binary, opts)
inner = each_bit(bitstring, opts.limit, opts)
group(concat(concat(left, nest(inner, 2)), right))
inner = each_bit(bitstring, limit, opts)
doc = group(concat(concat(left, nest(inner, 2)), right))
new_limit = if limit == :infinity, do: limit, else: max(0, limit - byte_size(bitstring))
{doc, %{opts | limit: new_limit}}
end
defp each_bit(_, 0, _) do
@@ -280,7 +326,7 @@ defimpl Inspect, for: BitString do
end
defp each_bit(<<>>, _counter, _opts) do
:doc_nil
Inspect.Algebra.empty()
end
defp each_bit(<<h::8>>, _counter, opts) do
@@ -353,17 +399,21 @@ defimpl Inspect, for: List do
color_doc(IO.iodata_to_binary(inspected), :charlist, opts)
keyword?(term) ->
container_doc(open, term, close, opts, &keyword/2, separator: sep, break: :strict)
container_doc_with_opts(open, term, close, opts, &keyword/2,
separator: sep,
break: :strict
)
true ->
container_doc(open, term, close, opts, &to_doc/2, separator: sep)
container_doc_with_opts(open, term, close, opts, &to_doc_with_opts/2, separator: sep)
end
end
@doc false
def keyword({key, value}, opts) do
key = color_doc(Macro.inspect_atom(:key, key), :atom, opts)
concat(key, concat(" ", to_doc(value, opts)))
{doc, opts} = to_doc_with_opts(value, opts)
{concat(key, concat(" ", doc)), opts}
end
@doc false
@@ -384,12 +434,24 @@ defimpl Inspect, for: Tuple do
sep = color_doc(",", :tuple, opts)
close = color_doc("}", :tuple, opts)
container_opts = [separator: sep, break: :flex]
container_doc(open, Tuple.to_list(tuple), close, opts, &to_doc/2, container_opts)
container_doc_with_opts(
open,
Tuple.to_list(tuple),
close,
opts,
&to_doc_with_opts/2,
container_opts
)
end
end
defimpl Inspect, for: Map do
def inspect(map, opts) do
inspect_as_map(map, opts)
end
def inspect_as_map(map, opts) do
list =
if Keyword.get(opts.custom_options, :sort_maps) do
map |> Map.to_list() |> :lists.sort()
@@ -408,20 +470,22 @@ defimpl Inspect, for: Map do
map_container_doc(list, "", opts, fun)
end
def inspect(map, name, infos, opts) do
def inspect_as_struct(map, name, infos, opts) do
fun = fn %{field: field}, opts -> Inspect.List.keyword({field, Map.get(map, field)}, opts) end
map_container_doc(infos, name, opts, fun)
end
defp to_assoc({key, value}, opts, sep) do
concat(concat(to_doc(key, opts), sep), to_doc(value, opts))
{key_doc, opts} = to_doc_with_opts(key, opts)
{value_doc, opts} = to_doc_with_opts(value, opts)
{concat(concat(key_doc, sep), value_doc), opts}
end
defp map_container_doc(list, name, opts, fun) do
open = color_doc("%" <> name <> "{", :map, opts)
sep = color_doc(",", :map, opts)
close = color_doc("}", :map, opts)
container_doc(open, list, close, opts, fun, separator: sep, break: :strict)
container_doc_with_opts(open, list, close, opts, fun, separator: sep, break: :strict)
end
end
@@ -479,9 +543,9 @@ defimpl Inspect, for: Regex do
:error ->
concat([
"Regex.compile!(",
Inspect.BitString.inspect(regex.source, opts),
to_doc(regex.source, opts),
", ",
Inspect.List.inspect(regex_opts, opts),
to_doc(regex_opts, opts),
")"
])
@@ -505,6 +569,7 @@ defimpl Inspect, for: Regex do
defp translate_options([:firstline | t], acc), do: translate_options(t, [?f | acc])
defp translate_options([:ungreedy | t], acc), do: translate_options(t, [?U | acc])
defp translate_options([:multiline | t], acc), do: translate_options(t, [?m | acc])
defp translate_options([:export | t], acc), do: translate_options(t, [?E | acc])
defp translate_options([], acc), do: acc
defp translate_options(_t, _acc), do: :error
@@ -577,36 +642,6 @@ defimpl Inspect, for: Function do
end
end
defimpl Inspect, for: Inspect.Error do
@impl true
def inspect(%{stacktrace: stacktrace} = inspect_error, _opts) do
message = Exception.message(inspect_error)
format_output(message, stacktrace)
end
defp format_output(message, [_ | _] = stacktrace) do
stacktrace = Exception.format_stacktrace(stacktrace)
"""
#Inspect.Error<
#{Inspect.Error.pad(message, 2)}
Stacktrace:
#{stacktrace}
>\
"""
end
defp format_output(message, []) do
"""
#Inspect.Error<
#{Inspect.Error.pad(message, 2)}
>\
"""
end
end
defimpl Inspect, for: PID do
def inspect(pid, _opts) do
"#PID" <> IO.iodata_to_binary(:erlang.pid_to_list(pid))
@@ -628,26 +663,15 @@ end
defimpl Inspect, for: Any do
def inspect(%module{} = struct, opts) do
try do
{module.__struct__(), module.__info__(:struct)}
rescue
_ -> Inspect.Map.inspect(struct, opts)
else
{dunder, fields} ->
if Map.keys(dunder) == Map.keys(struct) do
infos =
for %{field: field} = info <- fields,
field not in [:__struct__, :__exception__],
do: info
info =
for %{field: field} = map <- module.__info__(:struct),
field != :__exception__,
do: map
Inspect.Map.inspect(struct, Macro.inspect_atom(:literal, module), infos, opts)
else
Inspect.Map.inspect(struct, opts)
end
end
Inspect.Map.inspect_as_struct(struct, Macro.inspect_atom(:literal, module), info, opts)
end
def inspect(map, name, infos, opts) do
def inspect_as_struct(map, name, infos, opts) do
open = color_doc("#" <> name <> "<", :map, opts)
sep = color_doc(",", :map, opts)
close = color_doc(">", :map, opts)
@@ -661,6 +685,25 @@ defimpl Inspect, for: Any do
end
end
defimpl Inspect, for: Range do
import Inspect.Algebra
import Kernel, except: [inspect: 2]
def inspect(first..last//1, opts) when last >= first do
concat([to_doc(first, opts), "..", to_doc(last, opts)])
end
def inspect(first..last//step, opts) do
concat([to_doc(first, opts), "..", to_doc(last, opts), "//", to_doc(step, opts)])
end
# TODO: Remove me on v2.0
def inspect(%{__struct__: Range, first: first, last: last} = range, opts) do
step = if first <= last, do: 1, else: -1
inspect(Map.put(range, :step, step), opts)
end
end
require Protocol
Protocol.derive(
File diff suppressed because it is too large Load Diff
+90
View File
@@ -0,0 +1,90 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
defmodule Inspect.Error do
@moduledoc """
Raised when a struct cannot be inspected.
"""
@enforce_keys [:exception_module, :exception_message, :stacktrace, :inspected_struct]
defexception @enforce_keys
@impl true
def exception(arguments) when is_list(arguments) do
exception = Keyword.fetch!(arguments, :exception)
exception_module = exception.__struct__
exception_message = Exception.message(exception) |> String.trim_trailing("\n")
stacktrace = Keyword.fetch!(arguments, :stacktrace)
inspected_struct = Keyword.fetch!(arguments, :inspected_struct)
%Inspect.Error{
exception_module: exception_module,
exception_message: exception_message,
stacktrace: stacktrace,
inspected_struct: inspected_struct
}
end
@impl true
def message(%__MODULE__{
exception_module: exception_module,
exception_message: exception_message,
inspected_struct: inspected_struct
}) do
~s'''
got #{inspect(exception_module)} with message:
"""
#{pad(exception_message, 4)}
"""
while inspecting:
#{pad(inspected_struct, 4)}
'''
end
@doc false
def pad(message, padding_length)
when is_binary(message) and is_integer(padding_length) and padding_length >= 0 do
padding = String.duplicate(" ", padding_length)
message
|> String.split("\n")
|> Enum.map(fn
"" -> "\n"
line -> [padding, line, ?\n]
end)
|> IO.iodata_to_binary()
|> String.trim_trailing("\n")
end
end
defimpl Inspect, for: Inspect.Error do
@impl true
def inspect(%{stacktrace: stacktrace} = inspect_error, _opts) do
message = Exception.message(inspect_error)
format_output(message, stacktrace)
end
defp format_output(message, [_ | _] = stacktrace) do
stacktrace = Exception.format_stacktrace(stacktrace)
"""
#Inspect.Error<
#{Inspect.Error.pad(message, 2)}
Stacktrace:
#{stacktrace}
>\
"""
end
defp format_output(message, []) do
"""
#Inspect.Error<
#{Inspect.Error.pad(message, 2)}
>\
"""
end
end
+4
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Integer do
@moduledoc """
Functions for working with integers.
+51 -23
View File
@@ -1,3 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2021 The Elixir Team
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule IO do
@moduledoc ~S"""
Functions handling input/output (IO).
@@ -124,10 +128,26 @@ defmodule IO do
@type nodata :: {:error, term} | :eof
@type chardata :: String.t() | maybe_improper_list(char | chardata, String.t() | [])
@type inspect_opts :: [Inspect.Opts.new_opt() | {:label, term}]
@typedoc """
Stacktrace information as keyword options for `warn/2`.
At least `:file` is required. Other options are optional and used
to provide more precise location information.
"""
@type warn_stacktrace_opts :: [
file: String.t(),
line: pos_integer(),
column: pos_integer(),
module: module(),
function: {atom(), arity()}
]
defguardp is_device(term) when is_atom(term) or is_pid(term)
defguardp is_iodata(data) when is_list(data) or is_binary(data)
@doc """
@doc ~S"""
Reads from the IO `device`.
The `device` is iterated as specified by the `line_or_chars` argument:
@@ -137,7 +157,7 @@ defmodule IO do
non-textual inputs.
* if `line_or_chars` is `:line`, the device is iterated line by line.
CRFL newlines ("\r\n") are automatically normalized to "\n".
CRLF newlines ("\r\n") are automatically normalized to "\n".
* if `line_or_chars` is `:eof` (since v1.13), the device is iterated until `:eof`.
If the device is already at the end, it returns `:eof` itself.
@@ -182,7 +202,7 @@ defmodule IO do
:io.get_chars(map_dev(device), ~c"", count)
end
@doc """
@doc ~S"""
Reads from the IO `device`. The operation is Unicode unsafe.
The `device` is iterated as specified by the `line_or_chars` argument:
@@ -192,7 +212,7 @@ defmodule IO do
non-textual inputs.
* if `line_or_chars` is `:line`, the device is iterated line by line.
CRFL newlines ("\r\n") are automatically normalized to "\n".
CRLF newlines ("\r\n") are automatically normalized to "\n".
* if `line_or_chars` is `:eof` (since v1.13), the device is iterated until `:eof`.
If the device is already at the end, it returns `:eof` itself.
@@ -342,7 +362,10 @@ defmodule IO do
#=> my_app.ex:4: MyApp.main/1
"""
@spec warn(chardata | String.Chars.t(), Exception.stacktrace() | keyword() | Macro.Env.t()) ::
@spec warn(
chardata | String.Chars.t(),
Exception.stacktrace() | warn_stacktrace_opts() | Macro.Env.t()
) ::
:ok
def warn(message, stacktrace_info)
@@ -444,13 +467,15 @@ defmodule IO do
## Examples
The following code:
IO.inspect(<<0, 1, 2>>, width: 40)
Prints:
<<0, 1, 2>>
We can use the `:label` option to decorate the output:
You can use the `:label` option to decorate the output:
IO.inspect(1..100, label: "a wonderful range")
@@ -458,21 +483,23 @@ defmodule IO do
a wonderful range: 1..100
The `:label` option is especially useful with pipelines:
Inspect truncates large inputs by default. The `:printable_limit` controls
the limit for strings and other string-like constructs (such as charlists):
[1, 2, 3]
|> IO.inspect(label: "before")
|> Enum.map(&(&1 * 2))
|> IO.inspect(label: "after")
|> Enum.sum()
"abc"
|> String.duplicate(9001)
|> IO.inspect(printable_limit: :infinity)
Prints:
For containers such as lists, maps, and tuples, the number of entries
is managed by the `:limit` option:
before: [1, 2, 3]
after: [2, 4, 6]
1..100
|> Enum.map(& {&1, &1})
|> Enum.into(%{})
|> IO.inspect(limit: :infinity)
"""
@spec inspect(item, keyword) :: item when item: var
@spec inspect(item, inspect_opts) :: item when item: var
def inspect(item, opts \\ []) do
inspect(:stdio, item, opts)
end
@@ -482,9 +509,10 @@ defmodule IO do
See `inspect/2` for a full list of options.
"""
@spec inspect(device, item, keyword) :: item when item: var
@spec inspect(device, item, inspect_opts) :: item when item: var
def inspect(device, item, opts) when is_device(device) and is_list(opts) do
label = if label = opts[:label], do: [to_chardata(label), ": "], else: []
{label, opts} = Keyword.pop(opts, :label)
label = if label, do: [to_chardata(label), ": "], else: []
opts = Inspect.Opts.new(opts)
doc = Inspect.Algebra.group(Inspect.Algebra.to_doc(item, opts))
chardata = Inspect.Algebra.format(doc, opts.width)
@@ -599,7 +627,7 @@ defmodule IO do
@spec stream() :: Enumerable.t(String.t())
def stream, do: stream(:stdio, :line)
@doc """
@doc ~S"""
Converts the IO `device` into an `IO.Stream`.
An `IO.Stream` implements both `Enumerable` and
@@ -628,11 +656,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()
@@ -654,7 +682,7 @@ defmodule IO do
@spec binstream() :: Enumerable.t(binary)
def binstream, do: binstream(:stdio, :line)
@doc """
@doc ~S"""
Converts the IO `device` into an `IO.Stream`. The operation is Unicode unsafe.
An `IO.Stream` implements both `Enumerable` and

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