Compare commits

..
Author SHA1 Message Date
José Valim 42fb7d2be3 Back to take 1 2026-06-25 21:31:19 +02:00
José Valim 332f2ca791 Take 2 2026-06-25 21:00:54 +02:00
José Valim 082e08664e Take 1 2026-06-25 20:35:34 +02:00
234 changed files with 3881 additions and 7731 deletions
-4
View File
@@ -1,4 +0,0 @@
* Describe here the reasons behind the pull request.
* Make sure you have read the CONTRIBUTING.md file.
* Make sure any relevant documentation and tests have been added/updated.
* Do not submit Draft pull requests unless previously asked/agreed.
+4 -4
View File
@@ -42,11 +42,11 @@ jobs:
env:
ERLC_OPTS: "warnings_as_errors"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.24.1
- uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.24.0
with:
otp-version: ${{ matrix.otp_version }}
@@ -123,11 +123,11 @@ jobs:
- name: Configure Git
run: git config --global core.autocrlf input
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.24.1
- uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.24.0
with:
otp-version: ${{ matrix.otp_version }}
+4 -4
View File
@@ -28,11 +28,11 @@ jobs:
build-mode: none
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -48,8 +48,8 @@ jobs:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Run zizmor
uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
- name: Checkout project
id: checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
+2 -2
View File
@@ -33,9 +33,9 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Run markdownlint-cli2
uses: DavidAnson/markdownlint-cli2-action@21c1be1b93ad9ed58fa840aacc3f279cde2a72ff # v24.2.0
uses: DavidAnson/markdownlint-cli2-action@ded1f9488f68a970bc66ea5619e13e9b52e601cd # v23.2.0
+1 -1
View File
@@ -74,6 +74,6 @@ unless System.get_env("DRYRUN") do
"api-username" => "Elixir"
}
resp = Req.post!("https://forum.elixirforum.com/posts.json", {:json, post}, headers: headers)
resp = Req.post!("https://elixirforum.com/posts.json", {:json, post}, headers: headers)
IO.puts("#{resp.status} Elixir Forum\n#{inspect(resp.body)}")
end
+2 -4
View File
@@ -89,9 +89,9 @@ runs:
- name: Run OSS Review Toolkit
id: ort
uses: oss-review-toolkit/ort-ci-github-action@086d928d24ef1653dc0777296b312fda5faaaf52 # v1.2.0
uses: oss-review-toolkit/ort-ci-github-action@1805edcf1f4f55f35ae6e4d2d9795ccfb29b6021 # v1.1.0
with:
image: ghcr.io/oss-review-toolkit/ort:92.2.0
image: ghcr.io/oss-review-toolkit/ort-minimal:65.0.0
run: >-
labels,
cache-dependencies,
@@ -107,6 +107,4 @@ runs:
ort-cli-report-args: >-
-O CycloneDX=output.file.formats=json,xml
-O SpdxDocument=outputFileFormats=JSON,YAML
ort-cli-scan-args: >-
--scanners Provenant
sw-version: "${{ inputs.version }}"
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
+4 -4
View File
@@ -43,7 +43,7 @@ jobs:
--draft \
"$GITHUB_REF_NAME"
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
# zizmor: ignore[artipacked]
if: github.ref_type == 'branch'
@@ -82,7 +82,7 @@ jobs:
otp_version: "29.0"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
@@ -145,7 +145,7 @@ jobs:
- name: Log in to Azure
if: ${{ matrix.flavor == 'windows' && vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
uses: azure/login@f5d393ae46f8fde4be8b75f32e3fc50e654ad0ca # v3.0.1
uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
@@ -205,7 +205,7 @@ jobs:
- name: Checkout project
id: checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
+2 -2
View File
@@ -17,11 +17,11 @@ jobs:
name: Notify
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.24.1
- uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.24.0
with:
otp-version: "27.3"
elixir-version: "1.18.3"
+1 -2
View File
@@ -5,8 +5,7 @@
"**/*.md"
],
"ignores": [
".git/**",
".github/**"
".git/**"
],
"gitignore": true,
"config": {
-5
View File
@@ -37,11 +37,6 @@ curations:
comment: "Apply Trademark Policy to VERSION file"
detected_license: "NONE"
concluded_license: "Apache-2.0"
- path: ".github/pull_request_template.md"
reason: "NOT_DETECTED"
comment: "Apply default license to GitHub pull request template"
detected_license: "NONE"
concluded_license: "Apache-2.0"
# Wrongly Identified
- path: ".gitignore"
+1 -45
View File
@@ -18,14 +18,10 @@
* [Access] Add support for keyword lists in `Access.key/2` and `Access.key!/1`
* [Code] Add support for the `:erlc_options` compiler option
* [Code.Formatter] Add a `:migrate_atom_interpolations` option
* [Kernel] Improve performance of type constructors and complex intersections
* [Kernel] Warn on binary patterns with segments that are not byte-aligned
* [Kernel.ParallelCompiler] Add a hint when spawned processes cannot load modules defined during compilation
* [Keyword] Optimize `Keyword.pop/3`, `Keyword.pop!/2`, and `Keyword.pop_lazy/3`
* [List] Add `List.to_existing_atom/2` and `List.to_unsafe_atom/1`
* [MapSet] Optimize `MapSet.symmetric_difference/2` when set sizes differ
* [Path] Add `Path.safe_join/2`
* [Registry] Optimize exact key matching in lookups
* [String] Optimize `String.bag_distance/2`
* [String] Add `String.to_existing_atom/2` and `String.to_unsafe_atom/1`
@@ -39,58 +35,18 @@
#### Elixir
* [Calendar] Fix `Calendar.strftime/3` formatting of negative years with `%y`
* [Calendar] Fix rounding for `:day`, `:hour`, and `:minute` units in `DateTime.diff/3`, `NaiveDateTime.diff/3`, and `Time.diff/3`
* [Calendar.ISO] Fix `Calendar.ISO.valid_time?/4` to reject non-integer microsecond precision
* [Calendar.ISO] Reject negative zero UTC offsets in basic formats
* [Code.Formatter] Fix rendering calls where `do` is followed by non-block keyword arguments
* [Code.Fragment] Fix cursor completion when operator keywords such as `in`, `when`, `and`, `or`, and `not` follow another operator
* [Date] Preserve the `:format` option in `Date.to_iso8601/2` with custom calendars
* [Date.Range] Fix slicing date ranges with stepped ranges
* [Duration] Reject duplicate seconds in `Duration.from_iso8601/1`
* [Enum] Fix `Enum.min/2,3` and `Enum.max/2,3` with custom sorters on ranges
* [IO.ANSI.Docs] Recognize additional punctuation delimiters when rendering Markdown
* [Kernel] Fix expansion of rebound variables in bitstring size expressions
* [Kernel] Expand `defguard` macros separately in guard and body contexts, preserving `and`/`or` error semantics outside guards
* [Kernel] Fix inferred stacktrace types to allow arbitrary keyword metadata
* [Kernel] Fix inferred types for functions with non-returning clauses
* [Kernel] Fix map field type inference in the presence of empty map types
* [Kernel] Fix tuple fetch and deletion type operations across equivalent tuple types
* [Kernel] Fix variables defined in one default argument leaking into subsequent default arguments
* [Kernel] Improve the error message for non-atom struct keys
* [Kernel] Raise when `|` is used in guards
* [Kernel.Typespec] Preserve metadata when proxying to Elixir typespecs
* [Keyword] Delete duplicate keys when `Keyword.get_and_update/3` and `Keyword.get_and_update!/3` return `:pop`
* [Macro] Properly escape C1 control characters and Unicode noncharacters
* [NaiveDateTime] Fix `NaiveDateTime.diff/3` over-counting incomplete units
* [Range] Fix `Range.disjoint?/2` for ranges beyond floating-point precision
* [Range] Fix `Range.disjoint?/2` for single-element ranges with a negative step
* [String] Fix `String.reverse/1` grapheme ordering around invalid UTF-8 bytes
* [String] Return `1.0` from `String.bag_distance/2` for two empty strings
* [Time] Validate microseconds in `Time.from_seconds_after_midnight/3`
#### ExUnit
* [ExUnit.Assertions] Fix `refute_in_delta/4` at the delta boundary and with negative deltas
* [ExUnit.CaptureIO] Stop `StringIO` processes when capturing a named device fails
#### IEx
* [IEx.Autocomplete] Fix completion crashes on maps with non-atom keys
* [IEx.Evaluator] Recognize `**` and `not in` as continuation operators
* [IEx.Helpers] Fix `r/1` when multiple modules are defined in the same file
* [IEx.Helpers] Fix heap and stack memory calculations in `process_info/1`
* [ExUnit.CaptureLog] Fix the typespec for capture log options
#### Mix
* [Mix] Prevent synchronization lock files from being overwritten with empty contents
* [Mix.Release] Accept chardata paths in `Mix.Release.make_boot_script/4`
* [Mix.SCM.Git] Raise if Git refspecs start with `-`
* [mix deps] Recompile path and fetchable dependencies when one of the dependencies they were compiled with is removed
* [mix deps] Mark fetchable dependencies for compilation when their build exists but their SCM manifest is missing
* [mix deps.compile] Preserve code paths and compiler options across OS partitions
* [mix format] Pass `:sigils` to plugins invoked for sigils, allowing nested sigils to be formatted
* [mix new] Avoid trailing whitespace in generated files
### 3. Hard deprecations
+28 -24
View File
@@ -55,7 +55,7 @@ To recompile all (including Erlang modules):
make compile
```
After your changes are done, run `make format` to guarantee
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`.
@@ -71,39 +71,44 @@ 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).
## Sending a pull request
We encourage contributors to write tests that capture both existing and newly
introduced behavior, especially for bug fixes and major changes:
Contributions are done [via pull request](https://help.github.com/articles/using-pull-requests/)
and must include tests and other relevant proof of work:
* **Bug Fixes:** If you are fixing a bug, 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.
* **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 assocaited tests. Aim to
have the best code coverage possible.
major changes to existing functionality, please add tests that cover the
major parts of that functionality. Aim to have the best code coverage possible.
* **Performance improvements:** For performance improvements, please include the
benchmark script, with inputs and results, in the pull request description.
We recommend using [benchee](https://github.com/bencheeorg/benchee).
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:
Here are some pull requests we have received in the past you can use as reference:
* [Implement Enum.member? - Pull request](https://github.com/elixir-lang/elixir/pull/992)
* [Implement Enum.member?](https://github.com/elixir-lang/elixir/pull/992)
* [Add String.valid? - Pull request](https://github.com/elixir-lang/elixir/pull/1058)
* [Add String.valid?](https://github.com/elixir-lang/elixir/pull/1058)
* [Implement capture_io for ExUnit](https://github.com/elixir-lang/elixir/pull/1059)
* [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 reviewed 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.
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
@@ -112,7 +117,7 @@ a comment.
## Building documentation
Building the documentation requires that [ExDoc](https://github.com/elixir-lang/ex_doc)
is cloned and compiled alongside Elixir.
is installed and built alongside Elixir.
After cloning and compiling Elixir, run:
@@ -218,11 +223,10 @@ agents in Elixir spaces:
use coding agents to implement it, as long as it follows the AI Contributions
guidelines below.
* When automating AI usage on the Elixir codebase for performance improvements,
security fixes, or correctness changes to the compiler or type system, pair it
with a separate set of agents whose job is to argue against and try to invalidate
any proposed change. And treat their approval as advisory: a human must still
validate it before opening issues or pull requests.
* When automating AI usage on the Elixir codebase for performance improvements
or security fixes, pair it with a separate set of agents whose job is to argue
against and try to invalidate any proposed change. And treat their approval as
advisory: a human must still validate it before opening issues or pull requests.
If any code is written by AI, then you must follow the guidelines below.
+2 -2
View File
@@ -7,14 +7,14 @@
<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">
<img alt="Elixir logo" src="https://github.com/elixir-lang/elixir-lang.github.com/raw/main/public/images/logo/logo.png" width="200">
<img alt="Elixir logo" src="https://github.com/elixir-lang/elixir-lang.github.com/raw/main/images/logo/logo.png" width="200">
</picture>
</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 programming language designed for building scalable
Elixir is a dynamic, functional language designed for building scalable
and maintainable applications.
For more about Elixir, installation and documentation,
+2 -3
View File
@@ -293,7 +293,7 @@ defmodule EEx do
"foo baz"
"""
@spec eval_string(String.t(), Code.binding(), [compile_opt]) :: term()
@spec eval_string(String.t(), keyword, [compile_opt]) :: term()
def eval_string(source, bindings \\ [], options \\ [])
when is_binary(source) and is_list(bindings) and is_list(options) do
compiled = compile_string(source, options)
@@ -315,7 +315,7 @@ defmodule EEx do
#=> "foo baz"
"""
@spec eval_file(Path.t(), Code.binding(), [compile_opt]) :: 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)
@@ -344,7 +344,6 @@ defmodule EEx do
It returns `{:ok, [token]}` where a token is one of:
* `{:comment, content, %{column: column, line: line}}`
* `{:text, content, %{column: column, line: line}}`
* `{:expr, marker, content, %{column: column, line: line}}`
* `{:start_expr, marker, content, %{column: column, line: line}}`
+5 -7
View File
@@ -209,8 +209,11 @@ defmodule EEx.Compiler do
{[{:end, _} | _], [{:stab_op, _, _} | _]} ->
{:middle_expr, expr, %{}}
{_, [{:stab_op, _, _} | rev_tokens]} ->
if fn_before_end?(rev_tokens) do
{_, [{:stab_op, _, _} | reverse_tokens]} ->
fn_index = Enum.find_index(reverse_tokens, &match?({:fn, _}, &1)) || :infinity
end_index = Enum.find_index(reverse_tokens, &match?({:end, _}, &1)) || :infinity
if end_index > fn_index do
{:start_expr, expr, %{}}
else
{:middle_expr, expr, %{}}
@@ -224,11 +227,6 @@ defmodule EEx.Compiler do
end
end
defp fn_before_end?([{:fn, _} | _]), do: true
defp fn_before_end?([{:end, _} | _]), do: false
defp fn_before_end?([_ | rev_tokens]), do: fn_before_end?(rev_tokens)
defp fn_before_end?([]), do: false
defp drop_eol([{:eol, _} | rest]), do: drop_eol(rest)
defp drop_eol(rest), do: rest
+1 -1
View File
@@ -195,7 +195,7 @@ defmodule EEx.Engine do
def handle_expr(state, "=", ast) do
check_state!(state)
%{binary: binary, dynamic: dynamic, vars_count: vars_count} = state
var = Macro.var(String.to_unsafe_atom("arg#{vars_count}"), __MODULE__)
var = Macro.var(:"arg#{vars_count}", __MODULE__)
ast =
quote do
+16 -18
View File
@@ -10,8 +10,8 @@ defmodule Access do
keys of any type in a data structure via the `data[key]` syntax.
`Access` supports keyword lists (`Keyword`) and maps (`Map`) out
of the box. Keyword lists support only atom keys, while keys for maps
can be of any type. Both return `nil` if the key does not exist:
of the box. Keywords supports only atoms keys, keys for maps can
be of any type. Both return `nil` if the key does not exist:
iex> keywords = [a: 1, b: 2]
iex> keywords[:a]
@@ -33,7 +33,7 @@ defmodule Access do
iex> keywords[:c][:unknown]
nil
This works because accessing anything on a `nil` value returns
This works because accessing anything on a `nil` value, returns
`nil` itself:
iex> nil[:a]
@@ -917,7 +917,7 @@ defmodule Access do
iex> pop_in(list, [Access.filter(&(&1.salary >= 20)), :name])
{["francine"], [%{name: "john", salary: 10}, %{salary: 30}]}
When no match is found, an empty list is returned and the update function is never called:
When no match is found, an empty list is returned and the update function is never called
iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]
iex> get_in(list, [Access.filter(&(&1.salary >= 50)), :name])
@@ -934,7 +934,7 @@ defmodule Access do
"""
@doc since: "1.6.0"
@spec filter((term -> as_boolean(term))) :: access_fun(data :: list, current_value :: list)
@spec filter((term -> boolean)) :: access_fun(data :: list, current_value :: list)
def filter(func) when is_function(func) do
fn op, data, next -> filter(op, data, func, next) end
end
@@ -1035,12 +1035,12 @@ defmodule Access do
end
defp slice(:get_and_update, data, range, next) when is_list(data) do
%Range{first: first, last: last, step: step} = normalize_range(range, data)
range = normalize_range(range, data)
if first > last do
if range.first > range.last do
{[], data}
else
get_and_update_slice(data, first, last, step, next, [], [], 0)
get_and_update_slice(data, range, next, [], [], 0)
end
end
@@ -1145,23 +1145,21 @@ defmodule Access do
defp normalize_range(range, _list), do: range
defp get_and_update_slice(rest, _first, last, _step, _next, updates, gets, index)
defp get_and_update_slice(rest, %Range{last: last}, _next, updates, gets, index)
when index > last do
{:lists.reverse(gets), :lists.reverse(updates, rest)}
end
defp get_and_update_slice([head | rest], first, last, step, next, updates, gets, index) do
if index >= first and rem(index - first, step) == 0 do
defp get_and_update_slice([head | rest], range, next, updates, gets, index) do
if index in range do
case next.(head) do
:pop ->
get_and_update_slice(rest, first, last, step, next, updates, [head | gets], index + 1)
get_and_update_slice(rest, range, next, updates, [head | gets], index + 1)
{get, update} ->
get_and_update_slice(
rest,
first,
last,
step,
range,
next,
[update | updates],
[get | gets],
@@ -1169,11 +1167,11 @@ defmodule Access do
)
end
else
get_and_update_slice(rest, first, last, step, next, [head | updates], gets, index + 1)
get_and_update_slice(rest, range, next, [head | updates], gets, index + 1)
end
end
defp get_and_update_slice([], _first, _last, _step, _next, updates, gets, _index) do
defp get_and_update_slice([], _range, _next, updates, gets, _index) do
{:lists.reverse(gets), :lists.reverse(updates)}
end
@@ -1200,7 +1198,7 @@ defmodule Access do
iex> pop_in(list, [Access.find(&(&1.salary <= 40))])
{%{name: "john", salary: 10}, [%{name: "francine", salary: 30}]}
When no match is found, nil is returned and the update function is never called:
When no match is found, nil is returned and the update function is never called
iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]
iex> get_in(list, [Access.find(&(&1.salary >= 50)), :name])
+3 -3
View File
@@ -327,7 +327,7 @@ defmodule Agent do
passing the agent state. The result of the function invocation is
returned from this function.
`timeout` is a non-negative integer which specifies how many
`timeout` is an integer greater than zero which specifies how many
milliseconds are allowed before the agent executes the function and returns
the result value, or the atom `:infinity` to wait indefinitely. If no result
is received within the specified time, the function call fails and the caller
@@ -366,7 +366,7 @@ defmodule Agent do
elements, the first being the value to return (that is, the "get" value)
and the second one being the new state of the agent.
`timeout` is a non-negative integer which specifies how many
`timeout` is an integer greater than zero which specifies how many
milliseconds are allowed before the agent executes the function and returns
the result value, or the atom `:infinity` to wait indefinitely. If no result
is received within the specified time, the function call fails and the caller
@@ -407,7 +407,7 @@ defmodule Agent do
This function always returns `:ok`.
`timeout` is a non-negative integer which specifies how many
`timeout` is an integer greater than zero which specifies how many
milliseconds are allowed before the agent executes the function and returns
the result value, or the atom `:infinity` to wait indefinitely. If no result
is received within the specified time, the function call fails and the caller
+2 -2
View File
@@ -996,7 +996,7 @@ defmodule Application do
end
@doc """
Gets the directory for `app`.
Gets the directory for app.
This information is returned based on the code path. Here is an
example:
@@ -1077,7 +1077,7 @@ defmodule Application do
@doc """
Formats the error reason returned by `start/2`,
`ensure_started/2`, `stop/1`, `load/1` and `unload/1`,
and returns a string.
returns a string.
"""
@spec format_error(any) :: String.t()
def format_error(reason) do
+36 -36
View File
@@ -187,21 +187,21 @@ defmodule Base do
# For base64 standard, '/' (0x2F) sits exactly one below '0' (0x30), so we
# extend the digit range to [0x2F, 0x39], which absorbs '/' into one range
# check — saves one singleton term. Trick lifted from
# check — saves one Mycroft singleton. Trick lifted from
# https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/
@swar_ge_slash 0x51515151515151
# base64's '+', '-' and '_' border no other range, so each is checked as the
# degenerate range [c, c]. Mycroft zero-byte detection is one operation
# cheaper per singleton but not exact per lane: subtracting 0x01..01 makes a
# zero lane borrow from the lane above it, and a lane holding 1 turns that
# borrow into 0xFF, so bytes equal to `c bxor 0x01` read as matches too.
@swar_ge_plus 0x55555555555555
@swar_gt_plus 0x54545454545454
@swar_ge_dash 0x53535353535353
@swar_gt_dash 0x52525252525252
@swar_ge_underscore 0x21212121212121
@swar_gt_underscore 0x20202020202020
# Mycroft zero-byte detection for base64 singletons (+, -, _).
# Per lane: high bit set iff `bxor(w, K*ones) - 0x01..01` has its high bit
# set, i.e. that byte's V value was 0 → original byte was K. Simplified
# (no `bnot V` term) — for ASCII-gated `w`, borrow-propagation false
# positives only occur for adjacent bytes that happen to equal `K xor 0x01`,
# which is outside the base64 alphabet, so it never matters here.
# Pattern follows https://github.com/elixir-lang/elixir/pull/15255.
@swar_mask01 0x01010101010101
@swar_plus_x7 0x2B2B2B2B2B2B2B
@swar_dash_x7 0x2D2D2D2D2D2D2D
@swar_under_x7 0x5F5F5F5F5F5F5F
# Per-byte validity checks (used in both the SWAR clauses for the 8th byte
# of each stride and in the body of the sub-8-byte tail clauses).
@@ -365,8 +365,8 @@ defmodule Base do
# base64 SWAR word validity: 3 ranges (A-Z, a-z, 0-9) OR'd with singletons.
# For base, the digit range is extended to [0x2F, 0x39] to absorb '/' as
# part of one range (Lemire merge), leaving only '+' as a singleton. For url,
# '-' and '_' are two more singletons.
# part of one range (Lemire merge), leaving only '+' as a Mycroft singleton.
# For url, the singletons '-' and '_' are detected via two Mycroft terms.
defp valid_word64base?(w) do
band(w, @swar_mask80) == 0 and
band(
@@ -378,7 +378,7 @@ defmodule Base do
),
bxor(w + @swar_ge_slash, w + @swar_gt_9)
),
bxor(w + @swar_ge_plus, w + @swar_gt_plus)
bxor(w, @swar_plus_x7) - @swar_mask01
),
@swar_mask80
) == @swar_mask80
@@ -396,8 +396,8 @@ defmodule Base do
bxor(w + @swar_ge_0, w + @swar_gt_9)
),
bor(
bxor(w + @swar_ge_dash, w + @swar_gt_dash),
bxor(w + @swar_ge_underscore, w + @swar_gt_underscore)
bxor(w, @swar_dash_x7) - @swar_mask01,
bxor(w, @swar_under_x7) - @swar_mask01
)
),
@swar_mask80
@@ -436,7 +436,7 @@ defmodule Base do
end
for {base, alphabet} <- [upper: b16_alphabet, lower: to_lower_enc.(b16_alphabet)] do
name = String.to_unsafe_atom("encode16#{base}")
name = :"encode16#{base}"
encoded = to_encode_list.(alphabet)
@compile {:inline, [{name, 1}]}
@@ -536,7 +536,7 @@ defmodule Base do
* `:lower` - only allows lower case characters
* `:mixed` - allows mixed case characters
An `ArgumentError` exception is raised if the string has an odd length or
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
## Examples
@@ -617,10 +617,10 @@ defmodule Base do
upper = Enum.with_index(b16_alphabet)
for {base, alphabet} <- [upper: upper, lower: to_lower_dec.(upper), mixed: to_mixed_dec.(upper)] do
decode_name = String.to_unsafe_atom("decode16#{base}!")
validate_name = String.to_unsafe_atom("validate16#{base}?")
valid_char_name = String.to_unsafe_atom("valid_char16#{base}?")
valid_word_name = String.to_unsafe_atom("valid_word16#{base}?")
decode_name = :"decode16#{base}!"
validate_name = :"validate16#{base}?"
valid_char_name = :"valid_char16#{base}?"
valid_word_name = :"valid_word16#{base}?"
{min, decoded} = to_decode_list.(alphabet)
@@ -735,7 +735,7 @@ defmodule Base do
end
for {base, alphabet} <- [base: b64_alphabet, url: b64url_alphabet] do
name = String.to_unsafe_atom("encode64#{base}")
name = :"encode64#{base}"
encoded = to_encode_list.(alphabet)
@compile {:inline, [{name, 1}]}
@@ -1001,12 +1001,12 @@ defmodule Base do
end
for {base, alphabet} <- [base: b64_alphabet, url: b64url_alphabet] do
decode_name = String.to_unsafe_atom("decode64#{base}!")
decode_name = :"decode64#{base}!"
validate_name = String.to_unsafe_atom("validate64#{base}?")
validate_main_name = String.to_unsafe_atom("validate_main64#{validate_name}?")
valid_char_name = String.to_unsafe_atom("valid_char64#{base}?")
valid_word_name = String.to_unsafe_atom("valid_word64#{base}?")
validate_name = :"validate64#{base}?"
validate_main_name = :"validate_main64#{validate_name}?"
valid_char_name = :"valid_char64#{base}?"
valid_word_name = :"valid_word64#{base}?"
{min, decoded} = alphabet |> Enum.with_index() |> to_decode_list.()
# SWAR fast path: 7 bytes per stride, validated via `valid_word64<base>?`
@@ -1314,7 +1314,7 @@ defmodule Base do
hexupper: b32hex_alphabet,
hexlower: to_lower_enc.(b32hex_alphabet)
] do
name = String.to_unsafe_atom("encode32#{base}")
name = :"encode32#{base}"
encoded = to_encode_list.(alphabet)
@compile {:inline, [{name, 1}]}
@@ -1388,7 +1388,7 @@ defmodule Base do
The values for `:case` can be:
* `:upper` - only allows upper case characters (default)
* `:upper` - only allows upper case characters (default)
* `:lower` - only allows lower case characters
* `:mixed` - allows mixed case characters
@@ -1653,16 +1653,16 @@ defmodule Base do
hexlower: to_lower_dec.(hexupper),
hexmixed: to_mixed_dec.(hexupper)
] do
decode_name = String.to_unsafe_atom("decode32#{base}!")
validate_name = String.to_unsafe_atom("validate32#{base}?")
validate_main_name = String.to_unsafe_atom("validate_main32#{validate_name}?")
valid_char_name = String.to_unsafe_atom("valid_char32#{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)
# SWAR fast path: 7 bytes per stride, validated via `valid_word32<base>?`
# in the body. Tail leftover (1-6 bytes after a 7-byte stride hits an
# 8-byte-multiple `main`) recurses through the single-byte clause.
valid_word_name = String.to_unsafe_atom("valid_word32#{base}?")
valid_word_name = :"valid_word32#{base}?"
defp unquote(validate_main_name)(<<w::56, rest::binary>>),
do: unquote(valid_word_name)(w) and unquote(validate_main_name)(rest)
+27 -47
View File
@@ -106,7 +106,6 @@ defmodule Calendar do
@typedoc "Any map or struct that contains the time fields."
@type time :: %{
optional(any) => any,
calendar: calendar,
hour: hour,
minute: minute,
second: second,
@@ -296,7 +295,7 @@ defmodule Calendar do
@callback time_from_day_fraction(day_fraction) :: {hour, minute, second, microsecond}
@doc """
Defines the rollover moment for the calendar.
Define the rollover moment for the calendar.
This is the moment, in your calendar, when the current day ends
and the next day starts.
@@ -382,13 +381,13 @@ defmodule Calendar do
@callback iso_days_to_end_of_day(iso_days) :: iso_days
@doc """
Shifts date by the given duration according to its calendar.
Shifts date by given duration according to its calendar.
"""
@doc since: "1.17.0"
@callback shift_date(year, month, day, Duration.t()) :: {year, month, day}
@doc """
Shifts naive datetime by the given duration according to its calendar.
Shifts naive datetime by given duration according to its calendar.
"""
@doc since: "1.17.0"
@callback shift_naive_datetime(
@@ -403,7 +402,7 @@ defmodule Calendar do
) :: {year, month, day, hour, minute, second, microsecond}
@doc """
Shifts time by the given duration according to its calendar.
Shifts time by given duration according to its calendar.
"""
@doc since: "1.17.0"
@callback shift_time(hour, minute, second, microsecond, Duration.t()) ::
@@ -563,11 +562,11 @@ defmodule Calendar do
P | "am" or "pm" (noon is "pm", midnight as "am") | am, pm
q | Quarter | 1, 2, 3, 4
s | Number of seconds since the Epoch, 1970-01-01 00:00:00+0000 (UTC) | 1565888877
S | Second | 00, 59
S | Second | 00, 59, 60
u | Day of the week | 1 (Monday), 7 (Sunday)
x | Preferred date (without time) representation | 2018-10-17
X | Preferred time (without date) representation | 12:34:56
y | Year as 2-digits | -01, 01, 86, 18
y | Year as 2-digits | 01, 01, 86, 18
Y | Year | -0001, 0001, 1986
z | +hhmm/-hhmm time zone offset from UTC (empty string if naive) | +0300, -0530
Z | Time zone abbreviation (empty string if naive) | CET, BRST
@@ -624,7 +623,7 @@ defmodule Calendar do
...>)
"серпень"
Microsecond formatting:
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"
@@ -712,7 +711,7 @@ defmodule Calendar do
# Literally just %
defp format_modifiers("%" <> rest, width, pad, datetime, format_options, acc) do
parse(rest, datetime, format_options, [pad_leading_ascii("%", width, pad) | acc])
parse(rest, datetime, format_options, [pad_leading("%", width, pad) | acc])
end
# Abbreviated name of day
@@ -781,7 +780,7 @@ defmodule Calendar do
# Day of the month
defp format_modifiers("d" <> rest, width, pad, datetime, format_options, acc) do
result = datetime.day |> Integer.to_string() |> pad_leading_ascii(width, pad)
result = datetime.day |> Integer.to_string() |> pad_leading(width, pad)
parse(rest, datetime, format_options, [result | acc])
end
@@ -800,35 +799,31 @@ defmodule Calendar do
# Hour using a 24-hour clock
defp format_modifiers("H" <> rest, width, pad, datetime, format_options, acc) do
result = datetime.hour |> Integer.to_string() |> pad_leading_ascii(width, pad)
result = datetime.hour |> Integer.to_string() |> pad_leading(width, pad)
parse(rest, datetime, format_options, [result | acc])
end
# Hour using a 12-hour clock
defp format_modifiers("I" <> rest, width, pad, datetime, format_options, acc) do
result =
(rem(datetime.hour + 23, 12) + 1) |> Integer.to_string() |> pad_leading_ascii(width, pad)
result = (rem(datetime.hour + 23, 12) + 1) |> Integer.to_string() |> pad_leading(width, pad)
parse(rest, datetime, format_options, [result | acc])
end
# Day of the year
defp format_modifiers("j" <> rest, width, pad, datetime, format_options, acc) do
result =
datetime |> Date.day_of_year() |> Integer.to_string() |> pad_leading_ascii(width, pad)
result = datetime |> Date.day_of_year() |> Integer.to_string() |> pad_leading(width, pad)
parse(rest, datetime, format_options, [result | acc])
end
# Month
defp format_modifiers("m" <> rest, width, pad, datetime, format_options, acc) do
result = datetime.month |> Integer.to_string() |> pad_leading_ascii(width, pad)
result = datetime.month |> Integer.to_string() |> pad_leading(width, pad)
parse(rest, datetime, format_options, [result | acc])
end
# Minute
defp format_modifiers("M" <> rest, width, pad, datetime, format_options, acc) do
result = datetime.minute |> Integer.to_string() |> pad_leading_ascii(width, pad)
result = datetime.minute |> Integer.to_string() |> pad_leading(width, pad)
parse(rest, datetime, format_options, [result | acc])
end
@@ -856,23 +851,19 @@ defmodule Calendar do
# Quarter
defp format_modifiers("q" <> rest, width, pad, datetime, format_options, acc) do
result =
datetime |> Date.quarter_of_year() |> Integer.to_string() |> pad_leading_ascii(width, pad)
result = datetime |> Date.quarter_of_year() |> Integer.to_string() |> pad_leading(width, pad)
parse(rest, datetime, format_options, [result | acc])
end
# Second
defp format_modifiers("S" <> rest, width, pad, datetime, format_options, acc) do
result = datetime.second |> Integer.to_string() |> pad_leading_ascii(width, pad)
result = datetime.second |> Integer.to_string() |> pad_leading(width, pad)
parse(rest, datetime, format_options, [result | acc])
end
# Day of the week
defp format_modifiers("u" <> rest, width, pad, datetime, format_options, acc) do
result =
datetime |> Date.day_of_week() |> Integer.to_string() |> pad_leading_ascii(width, pad)
result = datetime |> Date.day_of_week() |> Integer.to_string() |> pad_leading(width, pad)
parse(rest, datetime, format_options, [result | acc])
end
@@ -922,25 +913,20 @@ defmodule Calendar do
# Year as 2-digits
defp format_modifiers("y" <> rest, width, pad, datetime, format_options, acc) do
result =
if datetime.year < 0 do
[?- | -datetime.year |> rem(100) |> Integer.to_string() |> pad_leading_ascii(width, pad)]
else
datetime.year |> rem(100) |> Integer.to_string() |> pad_leading_ascii(width, pad)
end
result = datetime.year |> rem(100) |> Integer.to_string() |> pad_leading(width, pad)
parse(rest, datetime, format_options, [result | acc])
end
# Year
defp format_modifiers("Y" <> rest, width, pad, datetime, format_options, acc) do
result =
{sign, year} =
if datetime.year < 0 do
[?- | -datetime.year |> Integer.to_string() |> pad_leading_ascii(width, pad)]
{?-, -datetime.year}
else
datetime.year |> Integer.to_string() |> pad_leading_ascii(width, pad)
{[], datetime.year}
end
result = [sign | year |> Integer.to_string() |> pad_leading(width, pad)]
parse(rest, datetime, format_options, [result | acc])
end
@@ -987,7 +973,7 @@ defmodule Calendar do
Integer.to_string(div(absolute_offset, 3600) * 100 + rem(div(absolute_offset, 60), 60))
sign = if utc_offset + std_offset >= 0, do: "+", else: "-"
result = "#{sign}#{pad_leading_ascii(offset_number, width, pad)}"
result = "#{sign}#{pad_leading(offset_number, width, pad)}"
parse(rest, datetime, format_options, [result | acc])
end
@@ -1006,19 +992,13 @@ defmodule Calendar do
raise ArgumentError, "invalid strftime format: %#{next}"
end
defp pad_preferred(result, width, pad) do
result
|> IO.iodata_to_binary()
|> pad_leading(width, pad)
defp pad_preferred(result, width, pad) when length(result) < width do
pad_preferred([pad | result], width, pad)
end
defp pad_preferred(result, _width, _pad), do: result
defp pad_leading(string, count, padding) do
to_pad = count - String.length(string)
if to_pad > 0, do: do_pad_leading(to_pad, padding, string), else: string
end
# Similar to `pad_leading/3`, but only for strings that always ASCII-only
defp pad_leading_ascii(string, count, padding) do
to_pad = count - byte_size(string)
if to_pad > 0, do: do_pad_leading(to_pad, padding, string), else: string
end
+12 -16
View File
@@ -81,7 +81,7 @@ defmodule Date do
Ranges of dates can be increasing (`first <= last`) and are
always inclusive. For a decreasing range, use `range/3` with
a step of -1 as third argument.
a step of -1 as first argument.
## Examples
@@ -160,7 +160,7 @@ defmodule Date do
) do
raise ArgumentError,
"both dates must have matching calendar and the step must be a " <>
"non-zero integer, got: #{inspect(first)}, #{inspect(last)}, #{inspect(step)}"
"non-zero integer, got: #{inspect(first)}, #{inspect(last)}, #{step}"
end
defp range(first, first_days, last, last_days, calendar, step) do
@@ -422,7 +422,7 @@ defmodule Date do
def to_iso8601(%{calendar: _} = date, format) when format in [:basic, :extended] do
date
|> convert!(Calendar.ISO)
|> to_iso8601(format)
|> to_iso8601()
end
@doc """
@@ -556,18 +556,14 @@ defmodule Date do
"""
@doc since: "1.4.0"
@spec compare(Calendar.date(), Calendar.date()) :: :lt | :eq | :gt
def compare(
%{year: year1, month: month1, day: day1, calendar: calendar},
%{year: year2, month: month2, day: day2, calendar: calendar}
) do
cond do
year1 > year2 -> :gt
year1 < year2 -> :lt
month1 > month2 -> :gt
month1 < month2 -> :lt
day1 > day2 -> :gt
day1 < day2 -> :lt
true -> :eq
def compare(%{calendar: calendar} = date1, %{calendar: calendar} = date2) do
%{year: year1, month: month1, day: day1} = date1
%{year: year2, month: month2, day: day2} = date2
case {{year1, month1, day1}, {year2, month2, day2}} do
{first, second} when first > second -> :gt
{first, second} when first < second -> :lt
_ -> :eq
end
end
@@ -1051,7 +1047,7 @@ defmodule Date do
@doc """
Calculates the quarter of the year of a given `date`.
Returns the quarter of the year as an integer. For the ISO 8601
Returns the day of the year as an integer. For the ISO 8601
calendar (the default), it is an integer from 1 to 4.
## Examples
+39 -10
View File
@@ -37,19 +37,21 @@ defmodule Date.Range do
first_in_iso_days: first_days,
last_in_iso_days: last_days,
step: step
},
} = range,
%Date{calendar: calendar} = date
) do
{days, _} = Date.to_iso_days(date)
in_range? =
if step > 0 do
first_days <= days and days <= last_days and rem(days - first_days, step) == 0
else
last_days <= days and days <= first_days and rem(days - first_days, step) == 0
end
cond do
empty?(range) ->
{:ok, false}
{:ok, in_range?}
first_days <= last_days ->
{:ok, first_days <= days and days <= last_days and rem(days - first_days, step) == 0}
true ->
{:ok, last_days <= days and days <= first_days and rem(days - first_days, step) == 0}
end
end
def member?(%Date.Range{step: _}, _) do
@@ -86,7 +88,7 @@ defmodule Date.Range do
step: step
} = range
) do
{:ok, size(range), &slice(first + &1 * step, step * &3, &2, calendar)}
{:ok, size(range), &slice(first + &1 * step, step + &3 - 1, &2, calendar)}
end
# TODO: Remove me on v2.0
@@ -183,7 +185,7 @@ defmodule Date.Range do
last_in_iso_days: last_days,
step: step
}),
do: div(last_days - first_days, step) + 1
do: abs(div(last_days - first_days, step)) + 1
# TODO: Remove me on v2.0
defp size(
@@ -193,6 +195,33 @@ defmodule Date.Range do
step = if first_days <= last_days, do: 1, else: -1
size(Map.put(date_range, :step, step))
end
defp empty?(%Date.Range{
first_in_iso_days: first_days,
last_in_iso_days: last_days,
step: step
})
when step > 0 and first_days > last_days,
do: true
defp empty?(%Date.Range{
first_in_iso_days: first_days,
last_in_iso_days: last_days,
step: step
})
when step < 0 and first_days < last_days,
do: true
defp empty?(%Date.Range{step: _}), do: false
# TODO: Remove me on v2.0
defp empty?(
%{__struct__: Date.Range, first_in_iso_days: first_days, last_in_iso_days: last_days} =
date_range
) do
step = if first_days <= last_days, do: 1, else: -1
empty?(Map.put(date_range, :step, step))
end
end
defimpl Inspect do
+47 -41
View File
@@ -180,7 +180,7 @@ defmodule DateTime do
since v1.15.0.
The default unit if none gets passed is `:native`,
which results in a default resolution of microseconds.
which results on a default resolution of microseconds.
## Examples
@@ -720,9 +720,6 @@ defmodule DateTime do
Other time zone databases can be passed as argument or set globally.
See the "Time zone database" section in the module docs.
Shifting to the `"Etc/UTC"` time zone always succeeds without
consulting the `time_zone_database`.
## Examples
iex> {:ok, pacific_datetime} = DateTime.shift_zone(~U[2018-07-16 10:00:00Z], "America/Los_Angeles", FakeTimeZoneDatabase)
@@ -756,28 +753,6 @@ defmodule DateTime do
|> shift_zone_for_iso_days_utc(calendar, precision, time_zone, time_zone_database)
end
defp shift_zone_for_iso_days_utc(iso_days_utc, calendar, precision, "Etc/UTC", _time_zone_db) do
{year, month, day, hour, minute, second, {microsecond, _}} =
calendar.naive_datetime_from_iso_days(iso_days_utc)
datetime = %DateTime{
calendar: calendar,
year: year,
month: month,
day: day,
hour: hour,
minute: minute,
second: second,
microsecond: {microsecond, precision},
std_offset: 0,
utc_offset: 0,
zone_abbr: "UTC",
time_zone: "Etc/UTC"
}
{:ok, datetime}
end
defp shift_zone_for_iso_days_utc(iso_days_utc, calendar, precision, time_zone, time_zone_db) do
case time_zone_db.time_zone_period_from_utc_iso_days(iso_days_utc, time_zone) do
{:ok, %{std_offset: std_offset, utc_offset: utc_offset, zone_abbr: zone_abbr}} ->
@@ -1202,7 +1177,7 @@ defmodule DateTime do
datetime
|> to_iso_days()
# Subtract total original offset in order to get UTC and add the new offset
|> Calendar.ISO.add_time_unit_to_iso_days(offset - total_offset, :second)
|> Calendar.ISO.add_day_fraction_to_iso_days(offset - total_offset, 86400)
|> calendar.naive_datetime_from_iso_days()
end
@@ -1321,9 +1296,9 @@ defmodule DateTime do
end
@doc """
Converts a number of Gregorian seconds to a `DateTime` struct.
Converts a number of gregorian seconds to a `DateTime` struct.
The returned `DateTime` will have `UTC` timezone, if you want another timezone, please use
The returned `DateTime` will have `UTC` timezone, if you want other timezone, please use
`DateTime.shift_zone/3`.
## Examples
@@ -1366,7 +1341,7 @@ defmodule DateTime do
end
@doc """
Converts a `DateTime` struct to a number of Gregorian seconds and microseconds.
Converts a `DateTime` struct to a number of gregorian seconds and microseconds.
## Examples
@@ -1601,15 +1576,15 @@ defmodule DateTime do
def diff(datetime1, datetime2, unit \\ :second)
def diff(datetime1, datetime2, :day) do
diff(datetime1, datetime2, :microsecond) |> div(86_400_000_000)
diff(datetime1, datetime2, :second) |> div(86400)
end
def diff(datetime1, datetime2, :hour) do
diff(datetime1, datetime2, :microsecond) |> div(3_600_000_000)
diff(datetime1, datetime2, :second) |> div(3600)
end
def diff(datetime1, datetime2, :minute) do
diff(datetime1, datetime2, :microsecond) |> div(60_000_000)
diff(datetime1, datetime2, :second) |> div(60)
end
def diff(
@@ -1651,9 +1626,6 @@ defmodule DateTime do
all converted to microseconds. Negative values will move backwards
in time and the default precision is `:second`.
If the datetime is in the `"Etc/UTC"` time zone, this function
always succeeds without consulting the `time_zone_database`.
This function relies on a contiguous representation of time,
ignoring timezone changes. For example, if you add one day when there
are summer time/daylight saving time changes, it will also change the
@@ -1772,9 +1744,6 @@ defmodule DateTime do
Allowed units are: `:year`, `:month`, `:week`, `:day`, `:hour`, `:minute`, `:second`, `:microsecond`.
If the datetime is in the `"Etc/UTC"` time zone, this function
always succeeds without consulting the `time_zone_database`.
This operation is equivalent to shifting the datetime wall clock
(in other words, the value as someone in that timezone would see
on their watch), then applying the time zone offset to convert it
@@ -1843,6 +1812,44 @@ defmodule DateTime do
@spec shift(Calendar.datetime(), Duration.duration(), Calendar.time_zone_database()) :: t
def shift(datetime, duration, time_zone_database \\ Calendar.get_time_zone_database())
def shift(%{calendar: calendar, time_zone: "Etc/UTC"} = datetime, duration, _time_zone_database) do
%{
year: year,
month: month,
day: day,
hour: hour,
minute: minute,
second: second,
microsecond: microsecond
} = datetime
{year, month, day, hour, minute, second, microsecond} =
calendar.shift_naive_datetime(
year,
month,
day,
hour,
minute,
second,
microsecond,
__duration__!(duration)
)
%DateTime{
year: year,
month: month,
day: day,
hour: hour,
minute: minute,
second: second,
microsecond: microsecond,
time_zone: "Etc/UTC",
zone_abbr: "UTC",
std_offset: 0,
utc_offset: 0
}
end
def shift(%{calendar: calendar} = datetime, duration, time_zone_database) do
%{
year: year,
@@ -2053,12 +2060,11 @@ defmodule DateTime do
end
defp apply_tz_offset(iso_days, offset) do
Calendar.ISO.add_time_unit_to_iso_days(iso_days, -offset, :second)
Calendar.ISO.add_day_fraction_to_iso_days(iso_days, -offset, 86400)
end
defp from_map(%{} = datetime_map) do
%DateTime{
calendar: datetime_map.calendar,
year: datetime_map.year,
month: datetime_map.month,
day: datetime_map.day,
+3 -13
View File
@@ -72,7 +72,7 @@ defmodule Duration do
However, once again, it is important to remember that shifting a duration is not
arithmetic, so you may want to use the functions in this module depending on what
you want to achieve. Compare the results of both examples below:
you to achieve. Compare the results of both examples below:
# Adding one month after the other
iex> date = ~D[2016-01-31]
@@ -129,16 +129,6 @@ defmodule Duration do
second: 0,
microsecond: {0, 0}
@typedoc """
The microsecond component of a duration.
Unlike `t:Calendar.microsecond/0`, the value may be negative, as
durations may represent negative amounts of time. The precision is
an integer from 0 to 6 holding the number of significant digits,
as in the calendar types.
"""
@type microsecond :: {value :: integer, precision :: 0..6}
@typedoc """
The duration struct type.
"""
@@ -150,7 +140,7 @@ defmodule Duration do
hour: integer,
minute: integer,
second: integer,
microsecond: microsecond()
microsecond: Calendar.microsecond()
}
@typedoc """
@@ -164,7 +154,7 @@ defmodule Duration do
| {:hour, integer}
| {:minute, integer}
| {:second, integer}
| {:microsecond, microsecond()}
| {:microsecond, Calendar.microsecond()}
@typedoc """
The duration type specifies a `%Duration{}` struct or a keyword list of valid duration unit pairs.
+97 -133
View File
@@ -33,9 +33,8 @@ defmodule Calendar.ISO do
you to format datetimes however else you desire.
Elixir does not support reduced accuracy formats (for example, a date without
the day component) nor decimal precisions in components other than seconds
(such as `10:01,5`). Fractional seconds are supported and truncated to
microsecond precision.
the day component) nor decimal precisions in the lowest component (such as
`10:01:25,5`).
#### Examples
@@ -82,11 +81,6 @@ defmodule Calendar.ISO do
iex> Calendar.ISO.parse_time("23")
{:error, :invalid_format}
A decimal fraction is accepted on seconds, but not on any other component:
iex> Calendar.ISO.parse_time("10:01:25,5")
{:ok, {10, 1, 25, {500000, 1}}}
### Extensions
The parser and formatter adopt one ISO 8601 extension: extended year notation.
@@ -423,13 +417,16 @@ defmodule Calendar.ISO do
@spec parse_date(String.t(), format) ::
{:ok, {year, month, day}}
| {:error, atom}
def parse_date("-" <> string, format) when is_format(format),
def parse_date(string, format) when is_binary(string) and is_format(format),
do: parse_date_guarded(string, format)
defp parse_date_guarded("-" <> string, format),
do: do_parse_date(string, -1, format)
def parse_date("+" <> string, format) when is_format(format),
defp parse_date_guarded("+" <> string, format),
do: do_parse_date(string, 1, format)
def parse_date(string, format) when is_binary(string) and is_format(format),
defp parse_date_guarded(string, format),
do: do_parse_date(string, 1, format)
defp do_parse_date(unquote(match_basic_date), multiplier, :basic) when unquote(guard_date) do
@@ -505,13 +502,16 @@ defmodule Calendar.ISO do
@spec parse_naive_datetime(String.t(), format) ::
{:ok, {year, month, day, hour, minute, second, microsecond}}
| {:error, atom}
def parse_naive_datetime("-" <> string, format) when is_format(format),
def parse_naive_datetime(string, format) when is_binary(string) and is_format(format),
do: parse_naive_datetime_guarded(string, format)
defp parse_naive_datetime_guarded("-" <> string, format),
do: do_parse_naive_datetime(string, -1, format)
def parse_naive_datetime("+" <> string, format) when is_format(format),
defp parse_naive_datetime_guarded("+" <> string, format),
do: do_parse_naive_datetime(string, 1, format)
def parse_naive_datetime(string, format) when is_binary(string) and is_format(format),
defp parse_naive_datetime_guarded(string, format),
do: do_parse_naive_datetime(string, 1, format)
defp do_parse_naive_datetime(
@@ -606,13 +606,16 @@ defmodule Calendar.ISO do
@spec parse_utc_datetime(String.t(), format) ::
{:ok, {year, month, day, hour, minute, second, microsecond}, utc_offset}
| {:error, atom}
def parse_utc_datetime("-" <> string, format) when is_format(format),
def parse_utc_datetime(string, format) when is_binary(string) and is_format(format),
do: parse_utc_datetime_guarded(string, format)
defp parse_utc_datetime_guarded("-" <> string, format),
do: do_parse_utc_datetime(string, -1, format)
def parse_utc_datetime("+" <> string, format) when is_format(format),
defp parse_utc_datetime_guarded("+" <> string, format),
do: do_parse_utc_datetime(string, 1, format)
def parse_utc_datetime(string, format) when is_binary(string) and is_format(format),
defp parse_utc_datetime_guarded(string, format),
do: do_parse_utc_datetime(string, 1, format)
defp do_parse_utc_datetime(
@@ -663,7 +666,7 @@ defmodule Calendar.ISO do
day_fraction = time_to_day_fraction(hour, minute, second, {0, 0})
{{year, month, day}, {hour, minute, second, _}} =
case add_time_unit_to_iso_days({0, day_fraction}, -offset, :second) do
case add_day_fraction_to_iso_days({0, day_fraction}, -offset, 86_400) do
{0, day_fraction} ->
{{year, month, day}, time_from_day_fraction(day_fraction)}
@@ -680,73 +683,75 @@ defmodule Calendar.ISO do
end
@doc """
Parses an ISO 8601 formatted duration string to a list of `Duration` compatible unit pairs.
Parses an ISO 8601 formatted duration string to a list of `Duration` compabitble unit pairs.
See `Duration.from_iso8601/1`.
"""
@doc since: "1.17.0"
@spec parse_duration(String.t()) :: {:ok, [Duration.unit_pair()]} | {:error, atom}
def parse_duration("P" <> string) when byte_size(string) > 0 do
parse_duration_date(string, 1, [], 0)
parse_duration_date(string, [], year: ?Y, month: ?M, week: ?W, day: ?D)
end
def parse_duration("+P" <> string) when byte_size(string) > 0 do
parse_duration_date(string, 1, [], 0)
parse_duration_date(string, [], year: ?Y, month: ?M, week: ?W, day: ?D)
end
def parse_duration("-P" <> string) when byte_size(string) > 0 do
parse_duration_date(string, -1, [], 0)
with {:ok, fields} <- parse_duration_date(string, [], year: ?Y, month: ?M, week: ?W, day: ?D) do
{:ok,
Enum.map(fields, fn
{:microsecond, {value, precision}} -> {:microsecond, {-value, precision}}
{unit, value} -> {unit, -value}
end)}
end
end
def parse_duration(_) do
{:error, :invalid_duration}
end
defp parse_duration_date("", _sign, acc, _min_position), do: {:ok, acc}
defp parse_duration_date("", acc, _allowed), do: {:ok, acc}
defp parse_duration_date("T" <> string, sign, acc, _min_position)
when byte_size(string) > 0 do
parse_duration_time(string, sign, acc, 0)
defp parse_duration_date("T" <> string, acc, _allowed) when byte_size(string) > 0 do
parse_duration_time(string, acc, hour: ?H, minute: ?M, second: ?S)
end
defp parse_duration_date(string, sign, acc, min_position) do
with {integer, <<unit, rest::binary>>} <- Integer.parse(string),
{key, next_min_position} <- find_date_unit(min_position, unit) do
parse_duration_date(rest, sign, [{key, integer * sign} | acc], next_min_position)
defp parse_duration_date(string, acc, allowed) do
with {integer, <<next, rest::binary>>} <- Integer.parse(string),
{key, allowed} <- find_unit(allowed, next) do
parse_duration_date(rest, [{key, integer} | acc], allowed)
else
_ -> {:error, :invalid_date_component}
end
end
defp parse_duration_time("", _sign, acc, _min_position), do: {:ok, acc}
defp parse_duration_time("", acc, _allowed), do: {:ok, acc}
defp parse_duration_time(string, sign, acc, min_position) do
defp parse_duration_time(string, acc, allowed) do
case Integer.parse(string) do
{second, <<delimiter, _::binary>> = rest} when delimiter in [?., ?,] ->
with {:second, _next_min_position} <- find_time_unit(min_position, ?S),
{{ms, precision}, "S"} <- parse_microsecond(rest) do
ms =
case string do
"-" <> _ ->
-ms
case parse_microsecond(rest) do
{{ms, precision}, "S"} ->
ms =
case string do
"-" <> _ ->
-ms
_ ->
ms
end
_ ->
ms
end
{:ok, [second: second, microsecond: {ms, precision}] ++ acc}
{:ok, [second: second * sign, microsecond: {ms * sign, precision}] ++ acc}
else
_ ->
{:error, :invalid_time_component}
end
{integer, <<unit, rest::binary>>} ->
case find_time_unit(min_position, unit) do
{key, next_min_position} ->
parse_duration_time(rest, sign, [{key, integer * sign} | acc], next_min_position)
false ->
{:error, :invalid_time_component}
{integer, <<next, rest::binary>>} ->
case find_unit(allowed, next) do
{key, allowed} -> parse_duration_time(rest, [{key, integer} | acc], allowed)
false -> {:error, :invalid_time_component}
end
_ ->
@@ -754,17 +759,9 @@ defmodule Calendar.ISO do
end
end
# The minimum position is the earliest unit still allowed, or one past the end.
defp find_date_unit(min_position, ?Y) when min_position <= 0, do: {:year, 1}
defp find_date_unit(min_position, ?M) when min_position <= 1, do: {:month, 2}
defp find_date_unit(min_position, ?W) when min_position <= 2, do: {:week, 3}
defp find_date_unit(min_position, ?D) when min_position <= 3, do: {:day, 4}
defp find_date_unit(_min_position, _unit), do: false
defp find_time_unit(min_position, ?H) when min_position <= 0, do: {:hour, 1}
defp find_time_unit(min_position, ?M) when min_position <= 1, do: {:minute, 2}
defp find_time_unit(min_position, ?S) when min_position <= 2, do: {:second, 3}
defp find_time_unit(_min_position, _unit), do: false
defp find_unit([{key, unit} | rest], unit), do: {key, rest}
defp find_unit([_ | rest], unit), do: find_unit(rest, unit)
defp find_unit([], _unit), do: false
@doc """
Returns the `t:Calendar.iso_days/0` format of the specified date.
@@ -1137,11 +1134,6 @@ defmodule Calendar.ISO do
It is an integer from 1 to 4.
In the ISO calendar, the quarter is determined solely by the month, so the `year`
and `day` arguments are ignored. Combination of `year`, `month`, and `day` is not
validated as a valid date, unlike in `day_of_year/3`. Use `valid_date?/3` when
full date validation is required.
## Examples
iex> Calendar.ISO.quarter_of_year(2016, 1, 31)
@@ -1184,7 +1176,7 @@ defmodule Calendar.ISO do
@doc since: "1.8.0"
@spec year_of_era(year) :: {1..10_000, era}
def year_of_era(year) when is_year_CE(year), do: {year, 1}
def year_of_era(year) when is_year_BCE(year), do: {1 - year, 0}
def year_of_era(year) when is_year_BCE(year), do: {abs(year) + 1, 0}
@doc """
Calendar callback to compute the year and era from the
@@ -1237,7 +1229,7 @@ defmodule Calendar.ISO do
end
def day_of_era(year, month, day) when is_year_BCE(year) do
day = @iso_epoch - date_to_iso_days(year, month, day)
day = abs(date_to_iso_days(year, month, day) - @iso_epoch)
{day, 0}
end
@@ -1464,7 +1456,7 @@ defmodule Calendar.ISO do
@doc """
Converts the given naive_datetime into a iodata.
See `naive_datetime_to_string/8` for more information.
See `naive_datetime_to_iodata/8` for more information.
## Examples
@@ -1589,7 +1581,7 @@ defmodule Calendar.ISO do
@doc """
Converts the given datetime into a iodata.
See `datetime_to_string/12` for more information.
See `datetime_to_iodata/12` for more information.
## Examples
@@ -1693,7 +1685,7 @@ defmodule Calendar.ISO do
end
@doc """
Determines if the time given is valid.
Determines if the date given is valid according to the proleptic Gregorian calendar.
Leap seconds are not supported by the built-in Calendar.ISO.
@@ -1713,7 +1705,7 @@ defmodule Calendar.ISO do
boolean
def valid_time?(hour, minute, second, {ms_value, ms_precision} = _microsecond)
when is_integer(hour) and is_integer(minute) and is_integer(second) and is_integer(ms_value) and
is_integer(ms_precision) do
is_integer(ms_value) do
is_hour(hour) and is_minute(minute) and is_second(second) and
is_microsecond(ms_value, ms_precision)
end
@@ -1846,9 +1838,6 @@ defmodule Calendar.ISO do
shift_options = shift_datetime_options(duration)
Enum.reduce(shift_options, {year, month, day, hour, minute, second, microsecond}, fn
{:microsecond, {0, _}}, naive_datetime ->
naive_datetime
{_, 0}, naive_datetime ->
naive_datetime
@@ -1878,9 +1867,6 @@ defmodule Calendar.ISO do
shift_options = shift_time_options(duration)
Enum.reduce(shift_options, {hour, minute, second, microsecond}, fn
{:microsecond, {0, _}}, time ->
time
{_, 0}, time ->
time
@@ -1943,7 +1929,8 @@ defmodule Calendar.ISO do
def shift_time_unit({_days, _day_fraction} = iso_days, value, unit)
when unit in [:second, :millisecond, :microsecond, :nanosecond] or is_integer(unit) do
add_time_unit_to_iso_days(iso_days, value, unit)
ppd = System.convert_time_unit(86_400, :second, unit)
add_day_fraction_to_iso_days(iso_days, value, ppd)
end
defp shift_time_unit_values({0, _}, {_, original_precision}) do
@@ -2024,8 +2011,8 @@ defmodule Calendar.ISO do
total = System.convert_time_unit(integer, unit, :microsecond)
if total in @unix_range_microseconds do
{seconds, microseconds} = div_rem(total, @microseconds_per_second)
seconds = @unix_epoch + seconds
microseconds = Integer.mod(total, @microseconds_per_second)
seconds = @unix_epoch + floor_div_positive_divisor(total, @microseconds_per_second)
precision = precision_for_unit(unit)
{date, time} = iso_seconds_to_datetime(seconds)
{:ok, date, time, {microseconds, precision}}
@@ -2046,27 +2033,38 @@ defmodule Calendar.ISO do
end
end
defp parse_microsecond("." <> rest), do: parse_microsecond(rest, rest, 0)
defp parse_microsecond("," <> rest), do: parse_microsecond(rest, rest, 0)
defp parse_microsecond(rest), do: {{0, 0}, rest}
defp parse_microsecond("." <> rest) do
case parse_microsecond(rest, 0, []) do
{[], 0, _} ->
:error
# Digits past the sixth are consumed but do not contribute to the value.
defp parse_microsecond(<<head, tail::binary>>, digits, 6) when head in ?0..?9,
do: parse_microsecond(tail, digits, 6)
{microsecond, precision, rest} ->
scale = scale_factor(precision)
{{:erlang.list_to_integer(microsecond) * scale, precision}, rest}
end
end
defp parse_microsecond(<<head, tail::binary>>, digits, precision) when head in ?0..?9,
do: parse_microsecond(tail, digits, precision + 1)
defp parse_microsecond("," <> rest) do
parse_microsecond("." <> rest)
end
defp parse_microsecond(_rest, _digits, 0), do: :error
defp parse_microsecond(rest) do
{{0, 0}, rest}
end
defp parse_microsecond(rest, digits, precision) do
scale = scale_factor(precision)
microsecond = :erlang.binary_to_integer(:binary.part(digits, 0, precision)) * scale
{{microsecond, precision}, rest}
defp parse_microsecond(<<head, tail::binary>>, 6, acc) when head in ?0..?9,
do: parse_microsecond(tail, 6, acc)
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(<<?+, h1, h2, ?:, m1, m2, rest::binary>>),
do: parse_offset(1, h1, h2, m1, m2, rest)
@@ -2089,8 +2087,7 @@ defmodule Calendar.ISO do
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,
true <- sign == 1 or hour != 0 or min != 0 do
true <- hour < 24 do
{(hour * 60 + min) * 60 * sign, rest}
else
_ -> :error
@@ -2106,43 +2103,10 @@ defmodule Calendar.ISO do
end
@doc false
def iso_days_to_unit(iso_days, :second) do
floor_div_positive_divisor(iso_days_to_microseconds(iso_days), @microseconds_per_second)
end
def iso_days_to_unit(iso_days, :millisecond) do
floor_div_positive_divisor(iso_days_to_microseconds(iso_days), 1_000)
end
def iso_days_to_unit(iso_days, :microsecond) do
iso_days_to_microseconds(iso_days)
end
def iso_days_to_unit(iso_days, unit) do
System.convert_time_unit(iso_days_to_microseconds(iso_days), :microsecond, unit)
end
defp iso_days_to_microseconds({days, {parts, ppd}}) do
days * @parts_per_day + divide_by_parts_per_day(parts, ppd)
end
@doc false
def add_time_unit_to_iso_days(iso_days, add, :second) do
add_day_fraction_to_iso_days(iso_days, add * @microseconds_per_second, @parts_per_day)
end
def add_time_unit_to_iso_days(iso_days, add, :millisecond) do
add_day_fraction_to_iso_days(iso_days, add * 1_000, @parts_per_day)
end
def add_time_unit_to_iso_days(iso_days, add, :microsecond) do
add_day_fraction_to_iso_days(iso_days, add, @parts_per_day)
end
def add_time_unit_to_iso_days(iso_days, add, unit)
when unit == :nanosecond or is_integer(unit) do
ppd = System.convert_time_unit(@seconds_per_day, :second, unit)
add_day_fraction_to_iso_days(iso_days, add, ppd)
def iso_days_to_unit({days, {parts, ppd}}, unit) do
day_microseconds = days * @parts_per_day
microseconds = divide_by_parts_per_day(parts, ppd)
System.convert_time_unit(day_microseconds + microseconds, :microsecond, unit)
end
@doc false
+8 -10
View File
@@ -164,7 +164,7 @@ defmodule NaiveDateTime do
Returns the "local time" for the machine the Elixir program is running on.
WARNING: This function can cause insidious bugs. It depends on the time zone
configuration at run time. This can change and be set to a time zone that has
configuration at run time. This can changed and be set to a time zone that has
daylight saving jumps (spring forward or fall back).
This function can be used to display what the time is right now for the time
@@ -541,15 +541,15 @@ defmodule NaiveDateTime do
def diff(naive_datetime1, naive_datetime2, unit \\ :second)
def diff(naive_datetime1, naive_datetime2, :day) do
diff(naive_datetime1, naive_datetime2, :microsecond) |> div(86_400_000_000)
diff(naive_datetime1, naive_datetime2, :second) |> div(86400)
end
def diff(naive_datetime1, naive_datetime2, :hour) do
diff(naive_datetime1, naive_datetime2, :microsecond) |> div(3_600_000_000)
diff(naive_datetime1, naive_datetime2, :second) |> div(3600)
end
def diff(naive_datetime1, naive_datetime2, :minute) do
diff(naive_datetime1, naive_datetime2, :microsecond) |> div(60_000_000)
diff(naive_datetime1, naive_datetime2, :second) |> div(60)
end
def diff(
@@ -570,11 +570,9 @@ defmodule NaiveDateTime do
"unsupported time unit. Expected :day, :hour, :minute, :second, :millisecond, :microsecond, :nanosecond, or a positive integer, got #{inspect(unit)}"
end
diff_microsecond =
(naive_datetime1 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(:microsecond)) -
(naive_datetime2 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(:microsecond))
System.convert_time_unit(diff_microsecond, :microsecond, unit)
units1 = naive_datetime1 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(unit)
units2 = naive_datetime2 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(unit)
units1 - units2
end
@doc """
@@ -674,7 +672,7 @@ defmodule NaiveDateTime do
"""
@doc since: "1.6.0"
@spec truncate(Calendar.naive_datetime(), :microsecond | :millisecond | :second) :: t()
@spec truncate(t(), :microsecond | :millisecond | :second) :: t()
def truncate(%NaiveDateTime{microsecond: microsecond} = naive_datetime, precision) do
%{naive_datetime | microsecond: Calendar.truncate(microsecond, precision)}
end
+18 -41
View File
@@ -62,7 +62,7 @@ defmodule Time do
You can pass a time unit to automatically truncate the resulting time.
The default unit if none gets passed is `:native` which results in a default resolution of microseconds.
The default unit if none gets passed is `:native` which results on a default resolution of microseconds.
## Examples
@@ -146,9 +146,8 @@ defmodule Time do
iex> Time.new(23, 59, 59, 1_000_000)
{:error, :invalid_time}
Invalid precision:
iex> Time.new(23, 59, 59, {999_999, 10})
# Invalid precision
Time.new(23, 59, 59, {999_999, 10})
{:error, :invalid_time}
"""
@@ -467,12 +466,8 @@ defmodule Time do
Calendar.microsecond(),
Calendar.calendar()
) :: t
def from_seconds_after_midnight(
seconds,
{microsecond, precision} \\ {0, 0},
calendar \\ Calendar.ISO
)
when is_integer(seconds) and microsecond in 0..999_999 and precision in 0..6 do
def from_seconds_after_midnight(seconds, microsecond \\ {0, 0}, calendar \\ Calendar.ISO)
when is_integer(seconds) do
seconds_in_day = Integer.mod(seconds, @seconds_per_day)
{hour, minute, second, {_, _}} =
@@ -483,7 +478,7 @@ defmodule Time do
hour: hour,
minute: minute,
second: second,
microsecond: {microsecond, precision}
microsecond: microsecond
}
end
@@ -501,7 +496,7 @@ defmodule Time do
"""
@doc since: "1.11.0"
@spec to_seconds_after_midnight(Calendar.time()) :: {non_neg_integer(), non_neg_integer()}
@spec to_seconds_after_midnight(Calendar.time()) :: {integer(), non_neg_integer()}
def to_seconds_after_midnight(%{microsecond: {microsecond, _precision}} = time) do
iso_days = {0, to_day_fraction(time)}
{Calendar.ISO.iso_days_to_unit(iso_days, :second), microsecond}
@@ -694,7 +689,7 @@ defmodule Time do
@doc """
Compares two time structs.
Returns `:gt` if the first time is later than the second
Returns `:gt` if first time is later than the second
and `:lt` for vice versa. If the two times are equal
`:eq` is returned.
@@ -720,32 +715,14 @@ defmodule Time do
"""
@doc since: "1.4.0"
@spec compare(Calendar.time(), Calendar.time()) :: :lt | :eq | :gt
def compare(
%{
hour: hour1,
minute: minute1,
second: second1,
microsecond: {microsecond1, _},
calendar: calendar
},
%{
hour: hour2,
minute: minute2,
second: second2,
microsecond: {microsecond2, _},
calendar: calendar
}
) do
cond do
hour1 > hour2 -> :gt
hour1 < hour2 -> :lt
minute1 > minute2 -> :gt
minute1 < minute2 -> :lt
second1 > second2 -> :gt
second1 < second2 -> :lt
microsecond1 > microsecond2 -> :gt
microsecond1 < microsecond2 -> :lt
true -> :eq
def compare(%{calendar: calendar} = time1, %{calendar: calendar} = time2) do
%{hour: hour1, minute: minute1, second: second1, microsecond: {microsecond1, _}} = time1
%{hour: hour2, minute: minute2, second: second2, microsecond: {microsecond2, _}} = time2
case {{hour1, minute1, second1, microsecond1}, {hour2, minute2, second2, microsecond2}} do
{first, second} when first > second -> :gt
{first, second} when first < second -> :lt
_ -> :eq
end
end
@@ -924,11 +901,11 @@ defmodule Time do
def diff(time1, time2, unit \\ :second)
def diff(time1, time2, :hour) do
diff(time1, time2, :microsecond) |> div(3_600_000_000)
diff(time1, time2, :second) |> div(3600)
end
def diff(time1, time2, :minute) do
diff(time1, time2, :microsecond) |> div(60_000_000)
diff(time1, time2, :second) |> div(60)
end
def diff(
+19 -35
View File
@@ -50,7 +50,7 @@ defmodule Code do
You can use `ensure_loaded/1` (as well as `ensure_loaded?/1` and
`ensure_loaded!/1`) to check if a module is loaded before using it and
act accordingly.
act.
## `ensure_compiled/1` and `ensure_compiled!/1`
@@ -258,7 +258,6 @@ defmodule Code do
| {:locals_without_parens, keyword()}
| {:force_do_end_blocks, boolean()}
| {:migrate, boolean()}
| {:migrate_atom_interpolations, boolean()}
| {:migrate_bitstring_modifiers, boolean()}
| {:migrate_call_parens_on_pipe, boolean()}
| {:migrate_charlists_as_sigils, boolean()}
@@ -273,7 +272,6 @@ defmodule Code do
| {:escape, boolean()}
| {:locals_without_parens, keyword()}
| {:comments, [term()]}
| {:syntax_colors, [{Inspect.Opts.color_key(), IO.ANSI.ansidata()}]}
@typedoc """
Options for parsing functions that convert strings to quoted expressions.
@@ -287,7 +285,7 @@ defmodule Code do
unescape: boolean(),
existing_atoms_only: boolean(),
token_metadata: boolean(),
literal_encoder: (term(), Macro.metadata() -> {:ok, Macro.t()} | {:error, binary()}),
literal_encoder: (term(), Macro.metadata() -> term()),
static_atoms_encoder: (binary(), Macro.metadata() -> {:ok, term()} | {:error, binary()}),
emit_warnings: boolean()
]
@@ -415,7 +413,7 @@ defmodule Code do
operations.
"""
@spec append_path(Path.t(), cache: boolean()) :: boolean()
@spec append_path(Path.t(), cache: boolean()) :: true | false
def append_path(path, opts \\ []) do
apply(:code, :add_pathz, [to_charlist(Path.expand(path)) | cache(opts)]) == true
end
@@ -554,7 +552,8 @@ defmodule Code do
This is the list of directories the Erlang VM uses for finding
module code. The list of files is managed per Erlang VM node.
All paths are expanded with `Path.expand/1` before being deleted.
The path is expanded with `Path.expand/1` before being deleted. If the
path does not exist, this function returns `false`.
"""
@doc since: "1.15.0"
@spec delete_paths([Path.t()]) :: :ok
@@ -768,12 +767,6 @@ defmodule Code do
* `:migrate` (since v1.18.0) - when `true`, sets all other migration options
to `true` by default. Defaults to `false`.
* `:migrate_atom_interpolations` (since v1.21.0) - when `true`, rewrites
deprecated atom interpolations to explicit calls to `String.to_unsafe_atom/1`.
For example, `:"foo_#{bar}"` becomes `String.to_unsafe_atom("foo_#{bar}")`.
Interpolated keywords like `["foo_#{bar}": 1]` are **not** migrated.
Defaults to the value of the `:migrate` option. This option changes the AST.
* `:migrate_bitstring_modifiers` (since v1.18.0) - when `true`,
removes unnecessary parentheses in known bitstring
[modifiers](`<<>>/1`), for example `<<foo::binary()>>`
@@ -1298,9 +1291,9 @@ defmodule Code do
* `:literal_encoder` (since v1.10.0) - how to encode literals in the AST.
It must be a function that receives two arguments, the literal and its
metadata, and it must return `{:ok, ast :: Macro.t}` or
`{:error, reason :: binary}`. If you return anything other than the literal
`{:error, reason :: binary}`. If you return anything than the literal
itself as the `term`, then the AST is no longer valid. This option
may still be useful for textual analysis of the source code.
may still useful for textual analysis of the source code.
* `:static_atoms_encoder` - the static atom encoder function, see
"The `:static_atoms_encoder` function" section below. Note this
@@ -1394,15 +1387,12 @@ defmodule Code do
while preserving information like comments and literals position.
Returns `{:ok, quoted_form, comments}` if it succeeds,
`{:error, {location, error, token}}` otherwise, where `location`
is keyword metadata containing the line and column of the error.
`{:error, {line, error, token}}` otherwise.
Comments are maps with the following fields:
* `:line` - The line number of the source code
* `:column` - The column number of the source code
* `:text` - The full text of the comment, including the leading `#`
* `:previous_eol_count` - How many end of lines there are between the comment and the previous AST node or comment
@@ -1457,9 +1447,7 @@ defmodule Code do
Returns the AST and a list of comments if it succeeds, raises an exception
otherwise. The exception is a `TokenMissingError` in case a token is missing
(usually because the expression is incomplete), `MismatchedDelimiterError`
(in case of mismatched opening and closing delimiters) and `SyntaxError`
otherwise.
(usually because the expression is incomplete), `SyntaxError` otherwise.
Check `string_to_quoted/2` for options information.
"""
@@ -1562,13 +1550,9 @@ defmodule Code do
`string_to_quoted/2`, setting this option to `false` will prevent it from
escaping the sequences twice. Defaults to `true`.
* `: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`, `:locals_without_parens`, `:force_do_end_blocks`, and all
migration options like `:migrate_charlists_as_sigils`. Note `:line_length`
does not apply here.
`: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(), [format_opt() | quoted_to_algebra_opt()]) ::
@@ -1688,7 +1672,7 @@ defmodule Code do
@doc """
Stores all given compilation options.
Changing the compilation options affects all processes
Changing the compilation options affect all processes
running in a given Erlang VM node. To store individual
options and for a description of all options, see
`put_compiler_option/2`.
@@ -1752,7 +1736,7 @@ defmodule Code do
@doc """
Stores a compilation option.
Changing the compilation options affects all processes running in a
Changing the compilation options affect all processes running in a
given Erlang VM node.
Available options are:
@@ -1768,7 +1752,7 @@ defmodule Code do
remove the `:debug_info` while deploying, tools like `mix release`
already do such by default.
Other environments, such as `mix test`, automatically disable this
Other environments, such as `mix test`, automatically disables this
via the `:test_elixirc_options` project configuration, as there is
typically no need to store debug chunks for test files.
@@ -1786,13 +1770,13 @@ 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) - a list of applications whose modules
should be used during type inference. 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.
Mix projects will set this option to your dependencies list in dev/prod, and
it will disable this option during test (as there is typically no need to infer
signatures for test files). Outside of Mix projects, it defaults to `[:elixir]`.
signature for test files). Outside of Mix projects, it defaults to `[:elixir]`.
* `:module_definition` (since v1.20.0) - stores if the module definition should
be `:compiled` (the default) or `:interpreted`. Note this does not affect the
@@ -1815,7 +1799,7 @@ defmodule Code do
* `:on_undefined_variable` (since v1.15.0) - either `:raise` or `:warn`.
When `:raise` (the default), undefined variables will trigger a compilation
error. You may set it to `:warn` if you want undefined variables to
error. You may be set it to `:warn` if you want undefined variables to
emit a warning and expand as to a local call to the zero-arity function
of the same name (for example, `node` would be expanded as `node()`).
This `:warn` behavior only exists for compatibility reasons when working
@@ -2150,7 +2134,7 @@ defmodule Code do
If the module being checked is currently in a compiler deadlock,
this function returns `{:error, :unavailable}`. Unavailable doesn't
necessarily mean the module doesn't exist, just that it is not currently
available, but it may (or may not) become available in the future.
available, but it (or may not) become available in the future.
Therefore, if you can only continue if the module is available, use
`ensure_compiled!/1` instead. In particular, do not do this:
+9 -17
View File
@@ -195,7 +195,6 @@ defmodule Code.Formatter do
file = Keyword.get(opts, :file, nil)
sigils = Keyword.get(opts, :sigils, [])
migrate = Keyword.get(opts, :migrate, false)
migrate_atom_interpolations = Keyword.get(opts, :migrate_atom_interpolations, migrate)
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)
@@ -224,7 +223,6 @@ defmodule Code.Formatter do
comments: comments,
sigils: sigils,
file: file,
migrate_atom_interpolations: migrate_atom_interpolations,
migrate_bitstring_modifiers: migrate_bitstring_modifiers,
migrate_call_parens_on_pipe: migrate_call_parens_on_pipe,
migrate_charlists_as_sigils: migrate_charlists_as_sigils,
@@ -336,20 +334,14 @@ defmodule Code.Formatter do
end
defp quoted_to_algebra(
{{:., _, [:erlang, :binary_to_atom]}, _, [{:<<>>, _, entries} = bitstring, :utf8]} =
quoted,
{{:., _, [:erlang, :binary_to_atom]}, _, [{:<<>>, _, entries}, :utf8]} = quoted,
context,
state
) do
cond do
not interpolated?(entries) ->
remote_to_algebra(quoted, context, state)
state.migrate_atom_interpolations ->
quoted_to_algebra(quote(do: String.to_unsafe_atom(unquote(bitstring))), context, state)
true ->
interpolation_to_algebra(entries, @double_quote, state, ":\"", @double_quote)
if interpolated?(entries) do
interpolation_to_algebra(entries, @double_quote, state, ":\"", @double_quote)
else
remote_to_algebra(quoted, context, state)
end
end
@@ -1704,7 +1696,7 @@ defmodule Code.Formatter do
iodata |> IO.iodata_to_binary() |> string() |> color_doc(:atom, inspect_opts)
end
defp integer_to_algebra(text, inspect_opts) do
defp integer_to_algebra(text, inspect_otps) do
case text do
<<?0, ?x, rest::binary>> ->
"0x" <> String.upcase(rest)
@@ -1718,15 +1710,15 @@ defmodule Code.Formatter do
decimal ->
insert_underscores(decimal)
end
|> color_doc(:number, inspect_opts)
|> color_doc(:number, inspect_otps)
end
defp float_to_algebra(text, inspect_opts) do
defp float_to_algebra(text, inspect_otps) do
[int_part, decimal_part] = :binary.split(text, ".")
decimal_part = String.downcase(decimal_part)
string = insert_underscores(int_part) <> "." <> decimal_part
color_doc(string, :number, inspect_opts)
color_doc(string, :number, inspect_otps)
end
defp insert_underscores("-" <> digits) do
+6 -11
View File
@@ -27,8 +27,7 @@ defmodule Code.Fragment do
column: pos_integer(),
columns: boolean(),
token_metadata: boolean(),
literal_encoder: (term(), Macro.metadata() -> {:ok, Macro.t()} | {:error, binary()}),
preserve_sigils: boolean(),
literal_encoder: (term(), Macro.metadata() -> term()),
trailing_fragment: String.t()
]
@@ -36,7 +35,7 @@ defmodule Code.Fragment do
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 to retrieve
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
@@ -52,7 +51,6 @@ defmodule Code.Fragment do
"""
@doc since: "1.19.0"
@spec lines(String.t()) :: [String.t()]
def lines(string) do
lines(string, <<>>)
end
@@ -142,9 +140,6 @@ defmodule Code.Fragment do
* `{:anonymous_call, inside_caller}` - the context is an anonymous
call, such as `fun.(` and `@fun.(`.
* `{:capture_arg, charlist}` - the context is a capture argument,
such as `&1`
* `{:module_attribute, charlist}` - the context is a module attribute,
such as `@hello_wor`
@@ -162,8 +157,8 @@ defmodule Code.Fragment do
* `:none` - no context possible
* `{:sigil, charlist}` - the context is a sigil. It may be either the beginning
of a sigil, such as `~` or `~s`. Operators starting with `~`, such as
`~>` and `~>>`, are returned as :operator contexts
of a sigil, such as `~` or `~s`, or an operator starting with `~`, such as
`~>` and `~>>`
* `{:struct, inside_struct}` - the context is a struct, such as `%`, `%UR` or `%URI`.
`inside_struct` can either be a `charlist` in case of a static alias or an
@@ -662,7 +657,7 @@ defmodule Code.Fragment do
iex> Code.Fragment.surround_context("foo", {1, 1})
%{begin: {1, 1}, context: {:local_or_var, ~c"foo"}, end: {1, 4}}
## Differences from `cursor_context/2`
## Differences to `cursor_context/2`
Because `surround_context/3` attempts to capture complex expressions,
it has some differences to `cursor_context/2`:
@@ -676,7 +671,7 @@ defmodule Code.Fragment do
be a local or variable
* `@` when not followed by any identifier is returned as `{:operator, ~c"@"}`
(in contrast to `{:module_attribute, ~c""}` in `cursor_context/2`)
(in contrast to `{:module_attribute, ~c""}` in `cursor_context/2`
* This function never returns empty sigils `{:sigil, ~c""}` or empty structs
`{:struct, ~c""}` as context
+3 -3
View File
@@ -150,20 +150,20 @@ defmodule Code.Identifier do
<<acc::binary, char::utf8>>
end
defp escape_char(char, acc) when char < 0x80 do
defp escape_char(char, acc) when char < 0x100 do
<<a::4, b::4>> = <<char::8>>
<<acc::binary, ?\\, ?x, to_hex(a), to_hex(b)>>
end
defp escape_char(char, acc) when char < 0x10000 do
<<a::4, b::4, c::4, d::4>> = <<char::16>>
<<acc::binary, ?\\, ?u, to_hex(a), to_hex(b), to_hex(c), to_hex(d)>>
<<acc::binary, ?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}>>
end
defp escape_char(char, acc) when char < 0x1000000 do
<<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::24>>
<<acc::binary, ?\\, ?u, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), to_hex(e), to_hex(f),
<<acc::binary, ?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), to_hex(e), to_hex(f),
?}>>
end
+5 -21
View File
@@ -4,8 +4,6 @@
defmodule Code.Normalizer do
@moduledoc false
@do_end_keywords [:rescue, :catch, :else, :after]
defguard is_literal(x)
when is_integer(x) or
is_float(x) or
@@ -70,7 +68,7 @@ defmodule Code.Normalizer do
# Bit containers
defp do_normalize({:<<>>, _, args} = quoted, state) when is_list(args) do
normalize_bitstring(quoted, state, state.escape)
normalize_bitstring(quoted, state, false)
end
# Atoms with interpolations
@@ -184,8 +182,7 @@ defmodule Code.Normalizer do
|> patch_meta_line(state.parent_meta)
|> Keyword.put_new(:delimiter, "\"")
string = normalize_bitstring(string, %{state | parent_meta: meta}, false)
{sigil, meta, [string, modifiers]}
{sigil, meta, [do_normalize(string, %{state | parent_meta: meta}), modifiers]}
else
_ ->
normalize_call(quoted, state)
@@ -349,20 +346,18 @@ defmodule Code.Normalizer do
args = normalize_args(args, %{state | parent_meta: meta})
{form, meta, args}
Keyword.has_key?(meta, :do) and kw_blocks?(last) ->
Keyword.has_key?(meta, :do) ->
# def foo do :ok end
# def foo, do: :ok
normalize_kw_blocks(form, meta, args, state)
match?([{:do, _} | _], last) and kw_blocks?(last) ->
match?([{:do, _} | _], last) and Keyword.keyword?(last) ->
# Non normalized kw blocks
line = state.parent_meta[:line] || meta[:line]
meta = meta ++ [do: [line: line], end: [line: line]]
normalize_kw_blocks(form, meta, args, state)
true ->
# The formatter renders do-end blocks from the meta alone
meta = Keyword.drop(meta, [:do, :end])
args = normalize_args(args, %{state | parent_meta: meta})
{last_arg, leading_args} = List.pop_at(args, -1, [])
@@ -401,22 +396,12 @@ defmodule Code.Normalizer do
defp block_keyword?([]), do: true
defp block_keyword?(_), do: false
# Anything after the do block that is not a block keyword makes it a keyword list
defp kw_blocks?([{:do, _} | rest] = kw) do
Keyword.keyword?(kw) and Enum.all?(rest, &match?({key, _} when key in @do_end_keywords, &1))
end
defp kw_blocks?([{{:__block__, _, [:do]}, _} | rest]) do
Enum.all?(rest, &match?({{:__block__, _, [key]}, _} when key in @do_end_keywords, &1))
end
defp kw_blocks?(_), do: false
defp allow_keyword?(:when, 2), do: true
defp allow_keyword?(:{}, _), do: false
defp allow_keyword?(op, arity), do: not is_atom(op) or not Macro.operator?(op, arity)
defp normalize_bitstring({:<<>>, meta, parts}, state, escape_interpolation) do
parts = maybe_add_trailing_newline(meta, parts, state)
meta = patch_meta_line(meta, state.parent_meta)
parts =
@@ -435,7 +420,6 @@ defmodule Code.Normalizer do
end)
end
parts = maybe_add_trailing_newline(meta, parts, state)
{:<<>>, meta, parts}
end
+7 -9
View File
@@ -80,8 +80,7 @@ defmodule Code.Typespec do
Returns all types available from the module's BEAM code.
The result is returned as a list of tuples where the first
element is the type (`:typep`, `:type`, `:opaque` and, on Erlang/OTP 28+,
`:nominal`).
element is the type (`:typep`, `:type` and `:opaque`).
The module must have a corresponding BEAM file which can be
located by the runtime system. The types will be in the Erlang
@@ -96,10 +95,9 @@ defmodule Code.Typespec do
types =
for {:attribute, _, kind, {name, _, args} = type} <- abstract_code,
kind in [:opaque, :type, :nominal] do
kind in [:opaque, :type] do
cond do
kind == :opaque -> {:opaque, type}
kind == :nominal -> {:nominal, type}
{name, length(args)} in exported_types -> {:type, type}
true -> {:typep, type}
end
@@ -119,7 +117,7 @@ defmodule Code.Typespec do
element is spec name and arity and the second is the spec.
The module must have a corresponding BEAM file which can be
located by the runtime system. The specs will be in the Erlang
located by the runtime system. The types will be in the Erlang
Abstract Format.
"""
@spec fetch_specs(module | binary) :: {:ok, [tuple]} | :error
@@ -137,10 +135,10 @@ defmodule Code.Typespec do
Returns all callbacks available from the module's BEAM code.
The result is returned as a list of tuples where the first
element is the callback name and arity and the second is the callback.
element is spec name and arity and the second is the spec.
The module must have a corresponding BEAM file
which can be located by the runtime system. The callbacks will be
which can be located by the runtime system. The types will be
in the Erlang Abstract Format.
"""
@spec fetch_callbacks(module | binary) :: {:ok, [tuple]} | :error
@@ -193,8 +191,8 @@ defmodule Code.Typespec do
## To AST conversion
defp collect_vars({:ann_type, _anno, [_var, type]}) do
collect_vars(type)
defp collect_vars({:ann_type, _anno, args}) when is_list(args) do
[]
end
defp collect_vars({:type, _anno, _kind, args}) when is_list(args) do
+3 -6
View File
@@ -152,7 +152,6 @@ defmodule Config do
"""
@doc since: "1.9.0"
@spec config(atom(), keyword()) :: keyword()
def config(root_key, opts) when is_atom(root_key) and is_list(opts) do
if not Keyword.keyword?(opts) do
raise ArgumentError, "config/2 expected a keyword list, got: #{inspect(opts)}"
@@ -199,7 +198,6 @@ defmodule Config do
"""
@doc since: "1.9.0"
@spec config(atom(), atom(), term()) :: keyword()
def config(root_key, key, opts) when is_atom(root_key) and is_atom(key) do
get_config!()
|> __merge__([{root_key, [{key, opts}]}])
@@ -227,7 +225,6 @@ defmodule Config do
"""
@doc since: "1.18.0"
@spec read_config(atom()) :: keyword() | nil
def read_config(root_key) when is_atom(root_key) do
get_config!()[root_key]
end
@@ -236,7 +233,7 @@ 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.
file is executed on.
In releases, returns the `MIX_ENV` specified when running `mix release`.
This is most often used to execute conditional code:
@@ -287,8 +284,8 @@ defmodule Config do
In case the file doesn't exist, an error is raised.
If the file is relative, it will be expanded relative to the
directory of the current configuration file.
If file is a relative, it will be expanded relatively to the
directory the current configuration file is in.
## Examples
+1 -1
View File
@@ -25,7 +25,7 @@ defmodule Config.Provider do
For example, imagine you want to list some basic configuration
on Mix's built-in `config/runtime.exs` file, but you also want
to support additional configuration files. To do so, you can add
this inside the `def project` portion of your `mix.exs`:
this inside the `def project` portion of your `mix.exs`:
releases: [
demo: [
+1 -1
View File
@@ -16,7 +16,7 @@ defmodule Config.Reader do
For example, if you expect the target system to have a config file
in an absolute path, you can add this inside the `def project` portion
of your `mix.exs`:
of your `mix.exs`:
releases: [
demo: [
+33 -75
View File
@@ -618,7 +618,7 @@ defmodule Enum do
acc,
(element, acc -> {:cont, chunk, acc} | {:cont, acc} | {:halt, acc}),
(acc -> {:cont, chunk, acc} | {:cont, acc})
) :: [chunk]
) :: Enumerable.t()
when chunk: any
def chunk_while(enumerable, acc, chunk_fun, after_fun) do
{_, {res, acc}} =
@@ -666,7 +666,7 @@ defmodule Enum do
[1, [2], 3, 4, 5, 6]
"""
@spec concat(Enumerable.t(Enumerable.t(elem))) :: [elem] when elem: term
@spec concat(t) :: t
def concat(enumerables)
def concat(list) when is_list(list) do
@@ -681,8 +681,8 @@ defmodule Enum do
Concatenates the enumerable on the `right` with the enumerable on the
`left`.
This function behaves similarly to the `++/2` operator with proper
lists, but applied to enumerables.
This function produces the same result as the `++/2` operator
for lists.
## Examples
@@ -693,7 +693,7 @@ defmodule Enum do
[1, 2, 3, 4, 5, 6]
"""
@spec concat(Enumerable.t(elem), Enumerable.t(elem)) :: [elem] when elem: term
@spec concat(t, t) :: t
def concat(left, right) when is_list(left) and is_list(right) do
left ++ right
end
@@ -859,10 +859,6 @@ defmodule Enum do
"""
@spec dedup_by(t, (element -> term)) :: list
def dedup_by([head | tail], fun) do
dedup_by_list(tail, fun, fun.(head), [head])
end
def dedup_by(enumerable, fun) do
{list, _} = reduce(enumerable, {[], []}, R.dedup(fun))
:lists.reverse(list)
@@ -907,7 +903,7 @@ defmodule Enum do
def drop(enumerable, amount) when is_integer(amount) and amount < 0 do
{count, fun} = slice_count_and_fun(enumerable, 1)
amount = amount + count
amount = Kernel.min(amount + count, count)
if amount > 0 do
fun.(0, amount, 1)
@@ -2233,19 +2229,14 @@ defmodule Enum do
nil
"""
@spec min_max(t, (element, element -> boolean) | module()) :: {min :: element, max :: element}
@spec min_max(t, (-> empty_result)) :: {min :: element, max :: element} | empty_result
when empty_result: any
@spec min_max(t, (element, element -> boolean) | module()) :: {element, element}
@spec min_max(t, (-> empty_result)) :: {element, element} | empty_result when empty_result: any
@spec min_max(t, (element, element -> boolean) | module(), (-> empty_result)) ::
{min :: element, max :: element} | empty_result
{element, element} | empty_result
when empty_result: any
def min_max(enumerable, sorter_or_empty_fallback \\ fn -> raise Enum.EmptyError end)
def min_max(list = [_ | _], empty_fallback) when is_function(empty_fallback, 0) do
min_max_list(list)
end
def min_max(first..last//step = range, empty_fallback)
when is_function(empty_fallback, 0) do
case Range.size(range) do
@@ -2352,14 +2343,14 @@ defmodule Enum do
"""
@spec min_max_by(t, (element -> any), (element, element -> boolean) | module()) ::
{min :: element, max :: element} | empty_result
{element, element} | empty_result
when empty_result: any
@spec min_max_by(
t,
(element -> any),
(element, element -> boolean) | module(),
(-> empty_result)
) :: {min :: element, max :: element} | empty_result
) :: {element, element} | empty_result
when empty_result: any
def min_max_by(
enumerable,
@@ -2403,18 +2394,6 @@ defmodule Enum do
defp min_max_sort_fun(module) when is_atom(module), do: &(module.compare(&1, &2) == :lt)
defp min_max_list([h | t]), do: min_max_list(t, h, h)
defp min_max_list([h | t], min, max) do
cond do
h < min -> min_max_list(t, h, max)
max < h -> min_max_list(t, min, h)
true -> min_max_list(t, min, max)
end
end
defp min_max_list([], min, max), do: {min, max}
@doc """
Splits the `enumerable` in two lists according to the given function `fun`.
@@ -2692,7 +2671,7 @@ defmodule Enum do
5050
"""
@spec reduce_while(t, acc, (element, acc -> {:cont, acc} | {:halt, acc})) :: acc
@spec reduce_while(t, any, (element, any -> {:cont, any} | {:halt, any})) :: any
def reduce_while(enumerable, acc, fun) do
Enumerable.reduce(enumerable, {:cont, acc}, fun) |> elem(1)
end
@@ -2935,7 +2914,8 @@ defmodule Enum do
end
defp slide_list_middle(list, 0, last, start_to_middle) do
slide_list_last(list, last + 1, [], start_to_middle)
{slid_range, tail} = slide_list_last(list, last + 1, [])
slid_range ++ :lists.reverse(start_to_middle, tail)
end
# You asked for a middle index off the end of the list... you get what we've got
@@ -2943,16 +2923,16 @@ defmodule Enum do
:lists.reverse(acc)
end
defp slide_list_last([h | t], last, acc, start_to_middle) when last > 0 do
slide_list_last(t, last - 1, [h | acc], start_to_middle)
defp slide_list_last([h | t], last, acc) when last > 0 do
slide_list_last(t, last - 1, [h | acc])
end
defp slide_list_last(rest, 0, acc, start_to_middle) do
:lists.reverse(acc, :lists.reverse(start_to_middle, rest))
defp slide_list_last(rest, 0, acc) do
{:lists.reverse(acc), rest}
end
defp slide_list_last([], _, acc, start_to_middle) do
:lists.reverse(acc, :lists.reverse(start_to_middle))
defp slide_list_last([], _, acc) do
{:lists.reverse(acc), []}
end
@doc """
@@ -4218,7 +4198,7 @@ defmodule Enum do
`zip_with/2` can be used to transpose lists of lists:
iex> Enum.zip_with([[1, 2], [3, 4]], & &1)
iex> Enum.zip_with([[1, 2,], [3, 4]], & &1)
[[1, 3], [2, 4]]
"""
@@ -4329,24 +4309,11 @@ defmodule Enum do
empty.()
_ ->
# The endpoint shortcut is only valid for sorters consistent with
# the natural integer order of the range elements, which is known
# to hold for the default sorters; any other sorter traverses the
# elements, seeded with the first one since the range is not empty
if fun == (&<=/2) or fun == (&>=/2) do
last = last - rem(last - first, step)
last = last - rem(last - first, step)
case fun.(first, last) do
true -> first
false -> last
end
else
reduce_range(first + step, last, step, first, fn element, acc ->
case fun.(acc, element) do
true -> acc
false -> element
end
end)
case fun.(first, last) do
true -> first
false -> last
end
end
end
@@ -4449,9 +4416,10 @@ defmodule Enum do
## any?/2 all?/2
defp predicate_list([h | t], initial, fun) do
case !!fun.(h) do
^initial -> predicate_list(t, initial, fun)
_ -> not initial
if !!fun.(h) == initial do
predicate_list(t, initial, fun)
else
not initial
end
end
@@ -4462,9 +4430,10 @@ defmodule Enum do
defp predicate_range(first, last, step, initial, fun)
when step > 0 and first <= last
when step < 0 and first >= last do
case !!fun.(first) do
^initial -> predicate_range(first + step, last, step, initial, fun)
_ -> not initial
if !!fun.(first) == initial do
predicate_range(first + step, last, step, initial, fun)
else
not initial
end
end
@@ -4547,17 +4516,6 @@ defmodule Enum do
defp dedup_list([value | tail]), do: [value | dedup_list(tail)]
defp dedup_list([]), do: []
## dedup_by
defp dedup_by_list([head | tail], fun, prev, acc) do
case fun.(head) do
^prev -> dedup_by_list(tail, fun, prev, acc)
new_val -> dedup_by_list(tail, fun, new_val, [head | acc])
end
end
defp dedup_by_list([], _fun, _prev, acc), do: :lists.reverse(acc)
## drop
defp drop_list(list, 0), do: list
+3 -3
View File
@@ -2376,7 +2376,7 @@ 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 nonexistent file:
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
@@ -2409,7 +2409,7 @@ 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 a file or directory that isn't present:
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
@@ -2477,7 +2477,7 @@ 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 a file that isn't present:
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
+6 -5
View File
@@ -897,7 +897,7 @@ defmodule File do
File.copy("non_existing.txt", "copy.txt")
#=> {:error, :enoent}
"""
@spec copy(Path.t() | io_device, Path.t() | io_device, non_neg_integer | :infinity) ::
@spec copy(Path.t() | io_device, Path.t() | io_device, pos_integer | :infinity) ::
{:ok, non_neg_integer} | {:error, posix | :badarg | :terminated}
def copy(source, destination, bytes_count \\ :infinity) do
source = normalize_path_or_io_device(source)
@@ -918,7 +918,7 @@ defmodule File do
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, non_neg_integer | :infinity) ::
@spec copy!(Path.t() | io_device, Path.t() | io_device, pos_integer | :infinity) ::
non_neg_integer
def copy!(source, destination, bytes_count \\ :infinity) do
case copy(source, destination, bytes_count) do
@@ -1362,8 +1362,9 @@ defmodule File do
end
defp copy_file_mode(src, dest) do
with {:ok, src_fileinfo} <- stat(src) do
chmod(dest, src_fileinfo.mode)
with {:ok, dest_fileinfo} <- stat(dest),
{:ok, src_fileinfo} <- stat(src) do
write_stat(dest, %{dest_fileinfo | mode: src_fileinfo.mode})
end
end
@@ -2220,7 +2221,7 @@ defmodule File do
type. If you pass, for example, `[encoding: :utf8]` or
`[encoding: {:utf16, :little}]` in the modes parameter, the underlying stream
will use `IO.write/2` and the `String.Chars` protocol to convert the data.
See `IO.binwrite/2` and `IO.write/2`.
See `IO.binwrite/2` and `IO.write/2` .
One may also consider passing the `:delayed_write` option if the stream
is meant to be written to under a tight loop.
+1 -7
View File
@@ -18,13 +18,7 @@ defmodule File.Stream do
defstruct path: nil, modes: [], line_or_bytes: :line, raw: true, node: nil
@type t :: %__MODULE__{
path: Path.t(),
modes: [term()],
line_or_bytes: :line | pos_integer(),
raw: boolean(),
node: node()
}
@type t :: %__MODULE__{}
@doc false
def __build__(path, line_or_bytes, modes) do
+26 -59
View File
@@ -25,7 +25,7 @@ defmodule Float do
and arithmetic due to the fact most decimal fractions cannot be
represented by a floating-point binary and most operations are not exact,
but operate on approximations. Those issues are not specific
to Elixir, they are a property of floating-point representation itself.
to Elixir, they are a property of floating point representation itself.
For example, the numbers 0.1 and 0.01 are two of them, what means the result
of squaring 0.1 does not give 0.01 neither the closest representable. Here is
@@ -167,73 +167,40 @@ defmodule Float do
parse_unsigned(binary)
end
defp parse_unsigned(<<digit, rest::binary>> = binary) when digit in ?0..?9,
do: parse_mantissa(binary, rest, false)
defp parse_unsigned(<<digit, rest::binary>>) when digit in ?0..?9,
do: parse_unsigned(rest, false, false, [digit])
defp parse_unsigned(binary) when is_binary(binary), do: :error
defp parse_mantissa(binary, <<digit, rest::binary>>, dot?) when digit in ?0..?9,
do: parse_mantissa(binary, rest, dot?)
defp parse_unsigned(<<digit, rest::binary>>, dot?, e?, acc) when digit in ?0..?9,
do: parse_unsigned(rest, dot?, e?, [digit | acc])
defp parse_mantissa(binary, <<?., digit, rest::binary>>, false) when digit in ?0..?9,
do: parse_mantissa(binary, rest, true)
defp parse_unsigned(<<?., digit, rest::binary>>, false, false, acc) when digit in ?0..?9,
do: parse_unsigned(rest, true, false, [digit, ?. | acc])
defp parse_mantissa(binary, <<exp_marker, digit, rest::binary>> = tail, dot?)
defp parse_unsigned(<<exp_marker, digit, rest::binary>>, dot?, false, acc)
when exp_marker in ~c"eE" and digit in ?0..?9,
do: parse_exponent(binary, byte_size(binary) - byte_size(tail), rest, dot?)
do: parse_unsigned(rest, true, true, [digit, ?e | add_dot(acc, dot?)])
defp parse_mantissa(binary, <<exp_marker, sign, digit, rest::binary>> = tail, 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_exponent(binary, byte_size(binary) - byte_size(tail), rest, dot?)
do: parse_unsigned(rest, true, true, [digit, sign, ?e | add_dot(acc, dot?)])
defp parse_mantissa(binary, rest, dot?), do: finish_mantissa(binary, rest, dot?)
defp parse_exponent(binary, exp_pos, <<digit, rest::binary>>, dot?) when digit in ?0..?9,
do: parse_exponent(binary, exp_pos, rest, dot?)
defp parse_exponent(binary, exp_pos, rest, dot?),
do: finish_exponent(binary, exp_pos, rest, dot?)
defp finish_mantissa(binary, rest, _dot? = true) do
{:erlang.binary_to_float(consumed(binary, rest)), rest}
# :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?, _, acc) do
acc
|> add_dot(dot?)
|> :lists.reverse()
|> :erlang.list_to_float()
rescue
ArgumentError -> :error
else
float -> {float, rest}
end
# Bare integer: * 1.0 casts to the nearest float without building a new binary,
# and raises ArithmeticError on overflow (for example a 400-digit integer).
defp finish_mantissa(binary, rest, _dot? = false) do
{:erlang.binary_to_integer(consumed(binary, rest)) * 1.0, rest}
rescue
ArithmeticError -> :error
end
# binary_to_float/1 raises ArgumentError when the exponent is too big, e.g. "1.0e400".
defp finish_exponent(binary, _exp_pos, rest, _dot? = true) do
{:erlang.binary_to_float(consumed(binary, rest)), rest}
rescue
ArgumentError -> :error
end
# No decimal point, so ".0" is spliced in before the exponent (at exp_pos) to
# form a valid float literal.
defp finish_exponent(binary, exp_pos, rest, _dot? = false) do
len = byte_size(binary) - byte_size(rest)
literal =
IO.iodata_to_binary([
:binary.part(binary, 0, exp_pos),
".0",
:binary.part(binary, exp_pos, len - exp_pos)
])
{:erlang.binary_to_float(literal), rest}
rescue
ArgumentError -> :error
end
defp consumed(binary, ""), do: binary
defp consumed(binary, rest), do: :binary.part(binary, 0, byte_size(binary) - byte_size(rest))
defp add_dot(acc, true), do: acc
defp add_dot(acc, false), do: [?0, ?. | acc]
@doc """
Rounds a float to the largest float less than or equal to `number`.
@@ -286,7 +253,7 @@ defmodule Float do
@doc """
Rounds a float to the smallest float greater than or equal to `number`.
`ceil/2` also accepts a precision to round a floating-point value up
`ceil/2` also accepts a precision to round a floating-point value down
to an arbitrary number of fractional digits (between 0 and 15).
The operation is performed on the binary floating point, without a
@@ -355,7 +322,7 @@ defmodule Float do
and therefore the number above is internally represented as 5.567499999,
which explains the behavior above. If you want exact rounding for decimals,
you must use a decimal library. The behavior above is also in accordance
with reference implementations, such as "Correctly Rounded Binary-Decimal and
to reference implementations, such as "Correctly Rounded Binary-Decimal and
Decimal-Binary Conversions" by David M. Gay.
## Examples
@@ -379,7 +346,7 @@ defmodule Float do
@spec round(float, precision_range) :: float
def round(float, precision \\ 0)
def round(float, 0) when float === 0.0 or float === -0.0, do: float
def round(float, 0) when float == 0.0, do: float
def round(float, 0) when is_float(float) do
case :erlang.round(float) * 1.0 do
@@ -705,7 +672,7 @@ defmodule Float do
end
defp invalid_precision_message(precision) do
"precision #{inspect(precision)} is out of valid range of #{inspect(@precision_range)}"
"precision #{precision} is out of valid range of #{inspect(@precision_range)}"
end
defp expand_compact([{:compact, false} | t]), do: expand_compact(t)
+6 -1
View File
@@ -69,6 +69,7 @@ defmodule Function do
| :name
| :new_index
| :new_uniq
| :pid
| :type
| :uniq
@@ -111,6 +112,8 @@ defmodule Function do
When `fun` is an anonymous function (that is, the type is `:local`), the following
additional keys are returned:
* `:pid` - PID of the process that originally created the function.
* `:index` - (integer) an index into the module function table.
* `:new_index` - (integer) an index into the module function table.
@@ -156,7 +159,7 @@ defmodule Function do
`:module`, `:name`, `:arity`, `:env`, or `:type`.
For anonymous functions, there is also information about any of the
atoms `:index`, `:new_index`, `:new_uniq`, and `:uniq`.
atoms `:index`, `:new_index`, `:new_uniq`, `:uniq`, and `:pid`.
For a named function, the value of any of these items is always the
atom `:undefined`.
@@ -176,6 +179,8 @@ defmodule Function do
iex> fun = &String.length/1
iex> Function.info(fun, :name)
{:name, :length}
iex> Function.info(fun, :pid)
{:pid, :undefined}
"""
@doc since: "1.7.0"
+1 -1
View File
@@ -1158,7 +1158,7 @@ defmodule GenServer do
## Timeouts
`timeout` is a non-negative integer which specifies how many
`timeout` is an integer greater than zero which specifies how many
milliseconds to wait for a reply, or the atom `:infinity` to wait
indefinitely. The default value is `5000`. If no reply is received within
the specified time, the function call fails and the caller exits. If the
+8 -28
View File
@@ -92,7 +92,7 @@ defprotocol Inspect do
end
inspect(%Point{x: 1})
#=> %Point{x: 1, y: 0}
%Point{x: 1, y: 0}
## Custom implementation
@@ -387,20 +387,13 @@ defimpl Inspect, for: List do
close = color_doc("]", :list, opts)
cond do
(lists == :as_charlists and unicode_list?(term, printable_limit)) or
(lists == :infer and List.ascii_printable?(term, printable_limit)) ->
{split, tail} =
if is_integer(printable_limit) do
case Enum.split(term, printable_limit) do
{split, []} -> {split, []}
{split, _} -> {split, " ++ ..."}
end
else
{term, []}
lists == :as_charlists or (lists == :infer and List.ascii_printable?(term, printable_limit)) ->
inspected =
case Identifier.escape(IO.chardata_to_string(term), ?", printable_limit) do
{escaped, ""} -> [?~, ?c, ?", escaped, ?"]
{escaped, _} -> [?~, ?c, ?", escaped, ?", " ++ ..."]
end
{escaped, _} = Identifier.escape(IO.chardata_to_string(split), ?")
inspected = [?~, ?c, ?", escaped, ?" | tail]
color_doc(IO.iodata_to_binary(inspected), :charlist, opts)
keyword?(term) ->
@@ -414,19 +407,6 @@ defimpl Inspect, for: List do
end
end
defp unicode_list?(_, 0), do: true
defp unicode_list?([char | rest], counter)
when char in 0..0xD7FF or char in 0xE000..0x10FFFF,
do: unicode_list?(rest, decrement(counter))
defp unicode_list?([], _counter), do: true
defp unicode_list?(_, _counter), do: false
@compile {:inline, decrement: 1}
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
@doc false
def keyword({key, value}, opts) do
key = color_doc(Macro.inspect_atom(:key, key), :atom, opts)
@@ -436,8 +416,8 @@ defimpl Inspect, for: List do
@doc false
def keyword?([{key, _value} | rest]) when is_atom(key) do
case Atom.to_string(key) do
"Elixir." <> _ -> false
case Atom.to_charlist(key) do
[?E, ?l, ?i, ?x, ?i, ?r, ?.] ++ _ -> false
_ -> keyword?(rest)
end
end
+2 -2
View File
@@ -21,8 +21,8 @@ defmodule Inspect.Opts do
is `:decimal` and if it is printable, otherwise in bit syntax. See
`String.printable?/1` to learn when a string is printable.
* `:charlists` - when `:as_charlists` all charlists will be printed as charlists,
non-printable code points will be escaped. Other lists will be printed as lists.
* `:charlists` - when `:as_charlists` all lists will be printed as charlists,
non-printable elements will be escaped.
When `:as_lists` all lists will be printed as lists.
+5 -5
View File
@@ -108,7 +108,7 @@ defmodule Integer do
defguard is_even(integer) when is_integer(integer) and (integer &&& 1) == 0
@doc """
Computes `base` raised to the power of `exponent`.
Computes `base` raised to power of `exponent`.
Both `base` and `exponent` must be integers.
The exponent must be zero or positive.
@@ -545,10 +545,10 @@ defmodule Integer do
@doc since: "1.12.0"
@spec extended_gcd(integer, integer) :: {non_neg_integer, integer, integer}
def extended_gcd(0, 0), do: {0, 0, 0}
def extended_gcd(0, b) when is_integer(b) and b > 0, do: {b, 0, 1}
def extended_gcd(0, b) when is_integer(b) and b < 0, do: {-b, 0, -1}
def extended_gcd(a, 0) when is_integer(a) and a > 0, do: {a, 1, 0}
def extended_gcd(a, 0) when is_integer(a) and a < 0, do: {-a, -1, 0}
def extended_gcd(0, b) when b > 0, do: {b, 0, 1}
def extended_gcd(0, b) when b < 0, do: {-b, 0, -1}
def extended_gcd(a, 0) when a > 0, do: {a, 1, 0}
def extended_gcd(a, 0) when a < 0, do: {-a, -1, 0}
def extended_gcd(integer1, integer2) when is_integer(integer1) and is_integer(integer2) do
extended_gcd(integer2, integer1, 0, 1, 1, 0)
+6 -6
View File
@@ -128,7 +128,7 @@ 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, String.Chars.t()}]
@type inspect_opts :: [Inspect.Opts.new_opt() | {:label, term}]
@typedoc """
Stacktrace information as keyword options for `warn/2`.
@@ -152,9 +152,9 @@ defmodule IO do
The `device` is iterated as specified by the `line_or_chars` argument:
* if `line_or_chars` is an integer, it is the number of Unicode
code points to be retrieved for devices open in Unicode/utf8 mode.
Otherwise, it is the number of raw bytes to be retrieved.
* if `line_or_chars` is an integer, it represents a number of bytes. The device is
iterated by that number of bytes. This should be the preferred mode for reading
non-textual inputs.
* if `line_or_chars` is `:line`, the device is iterated line by line.
CRLF newlines ("\r\n") are automatically normalized to "\n".
@@ -521,7 +521,7 @@ defmodule IO do
end
@doc """
Gets a number of characters from IO device `:stdio`.
Gets a number of bytes from IO device `:stdio`.
If `:stdio` is a Unicode device, `count` implies
the number of Unicode code points to be retrieved.
@@ -549,7 +549,7 @@ defmodule IO do
end
@doc """
Gets a number of characters from the IO `device`.
Gets a number of bytes from the IO `device`.
If the IO `device` is a Unicode device, `count` implies
the number of Unicode code points to be retrieved.
+6 -8
View File
@@ -111,15 +111,13 @@ defmodule IO.ANSI do
end
defsequence = fn name, code, terminator ->
sequence = "\e[#{code}#{terminator}"
@spec unquote(name)() :: String.t()
def unquote(name)() do
unquote(sequence)
"\e[#{unquote(code)}#{unquote(terminator)}"
end
defp format_sequence(unquote(name)) do
unquote(sequence)
unquote(name)()
end
end
@@ -161,7 +159,7 @@ defmodule IO.ANSI do
for font_n <- [1, 2, 3, 4, 5, 6, 7, 8, 9] do
@doc "Sets alternative font #{font_n}."
defsequence.(String.to_unsafe_atom("font_#{font_n}"), font_n + 10, "m")
defsequence.(:"font_#{font_n}", font_n + 10, "m")
end
@doc "Normal color or intensity."
@@ -195,13 +193,13 @@ defmodule IO.ANSI do
defsequence.(color, code + 30, "m")
@doc "Sets foreground color to light #{color}."
defsequence.(String.to_unsafe_atom("light_#{color}"), code + 90, "m")
defsequence.(:"light_#{color}", code + 90, "m")
@doc "Sets background color to #{color}."
defsequence.(String.to_unsafe_atom("#{color}_background"), code + 40, "m")
defsequence.(:"#{color}_background", code + 40, "m")
@doc "Sets background color to light #{color}."
defsequence.(String.to_unsafe_atom("light_#{color}_background"), code + 100, "m")
defsequence.(:"light_#{color}_background", code + 100, "m")
end
@doc "Default text color."
+1 -1
View File
@@ -673,7 +673,7 @@ defmodule IO.ANSI.Docs do
# Characters that can mark the beginning or the end of a word.
# Only support the most common ones at this moment.
@delimiters [?\s, ?', ?", ?!, ??, ?,, ?:, ?;, ?/, ?@, ?#, ?$, ?%, ?^, ?&] ++
@delimiters [?\s, ?', ?", ?!, ?@, ?#, ?$, ?%, ?^, ?&] ++
[?-, ?+, ?(, ?), ?[, ?], ?{, ?}, ?<, ?>, ?.]
### Inline start
+1 -1
View File
@@ -31,7 +31,7 @@ defmodule IO.Stream do
@type t :: %__MODULE__{
device: IO.device(),
raw: boolean(),
line_or_bytes: :line | pos_integer()
line_or_bytes: :line | non_neg_integer()
}
@doc false
-1
View File
@@ -175,7 +175,6 @@ defimpl JSON.Encoder, for: Map do
# Erlang supports only numbers, binaries, and atoms as keys,
# we support anything that implements the String.Chars protocol.
@compile inline: [key: 2]
defp key(key, encoder) when is_atom(key), do: encoder.(Atom.to_string(key), encoder)
defp key(key, encoder) when is_binary(key), do: encoder.(key, encoder)
defp key(key, encoder), do: encoder.(String.Chars.to_string(key), encoder)
+5 -10
View File
@@ -142,7 +142,7 @@ defmodule Kernel do
* [Patterns and guards](patterns-and-guards.md) - an introduction to patterns,
guards, and extensions
* [Syntax reference](syntax-reference.md) - the language syntax reference
* [Typespecs reference](typespecs.md) - types and function specifications, including list of types
* [Typespecs reference](typespecs.md)- types and function specifications, including list of types
* [Unicode syntax](unicode-syntax.md) - outlines Elixir support for Unicode
## Guards
@@ -3088,7 +3088,7 @@ defmodule Kernel do
@doc """
Pops a key from the given nested structure.
Uses the `Access` behaviour to traverse the structures
Uses the `Access` protocol to traverse the structures
according to the given `keys`, unless the `key` is a
function. If the key is a function, it will be invoked
as specified in `get_and_update_in/3`.
@@ -4960,7 +4960,6 @@ defmodule Kernel do
"""
@doc since: "1.14.0"
@spec binary_slice(binary, integer, non_neg_integer) :: binary
def binary_slice(binary, start, size)
when is_binary(binary) and is_integer(start) and is_integer(size) and size >= 0 do
total = byte_size(binary)
@@ -5034,7 +5033,6 @@ defmodule Kernel do
"""
@doc since: "1.14.0"
@spec binary_slice(binary, Range.t()) :: binary
def binary_slice(binary, first..last//step)
when is_binary(binary) and step > 0 do
total = byte_size(binary)
@@ -5149,7 +5147,7 @@ defmodule Kernel do
warning saying that a module has been redefined.
There are some modules that Elixir does not currently implement but it
may implement in the future. Those modules are reserved and defining
may be implement in the future. Those modules are reserved and defining
them will result in a compilation error:
defmodule Any do
@@ -5562,7 +5560,7 @@ defmodule Kernel do
when the struct is printed:
defmodule User do
@derive {Inspect, only: [:name]}
@derive {Inspect, only: :name}
defstruct name: nil, age: nil
end
@@ -5589,9 +5587,6 @@ defmodule Kernel do
defstruct name: nil, age: 10 + 11
end
`@enforce_keys` must be set to an atom or a list of unique atoms,
all of which must name fields defined by `defstruct/1`
Now trying to build a struct without the name key will fail:
%User{age: 21}
@@ -6382,7 +6377,7 @@ defmodule Kernel do
### Passing timeouts
You can also pass timeouts directly to this function, that is, milliseconds or
You can also pass timeouts directly to this functions, that is, milliseconds or
the atom `:infinity`. In this case, this function just returns the given argument.
## Examples
+3 -3
View File
@@ -49,7 +49,7 @@ defmodule Kernel.CLI do
@doc """
Runs the given function by catching any failure
and printing them to stderr. `at_exit` hooks are
and printing them to stdout. `at_exit` hooks are
also invoked before exiting.
This function is used by Elixir's CLI and also
@@ -206,7 +206,7 @@ defmodule Kernel.CLI do
end
@elixir_internals [:elixir, :elixir_aliases, :elixir_clauses, :elixir_compiler, :elixir_def] ++
[:elixir_dispatch, :elixir_expand, :elixir_lexical] ++
[:elixir_def, :elixir_dispatch, :elixir_expand, :elixir_lexical] ++
[:elixir_map, :elixir_module] ++
[:elixir_erl, :elixir_erl_clauses, :elixir_erl_compiler, :elixir_erl_pass] ++
[Kernel.ErrorHandler, Module.ParallelChecker]
@@ -344,7 +344,7 @@ defmodule Kernel.CLI do
parse_argv(t, %{config | verbose_compile: true})
end
defp parse_argv([~c"--profile", ~c"time" | t], %{mode: :elixirc} = config) do
defp parse_argv([~c"--profile", "time" | t], %{mode: :elixirc} = config) do
parse_argv(t, %{config | profile: :time})
end
+3 -3
View File
@@ -23,7 +23,7 @@ defmodule Kernel.LexicalTracker do
@doc """
Invoked during module expansion to annotate a require
that must be warned if unused.
must be warned if unused.
"""
def warn_require(pid, meta, module, alias) do
:gen_server.cast(pid, {:warn_require, module, meta, alias})
@@ -32,7 +32,7 @@ defmodule Kernel.LexicalTracker do
@doc """
Invoked during module expansion to annotate an alias
that must be warned if unused.
must be warned if unused.
"""
def warn_alias(pid, meta, alias, module) do
:gen_server.cast(pid, {:warn_alias, alias, meta})
@@ -41,7 +41,7 @@ defmodule Kernel.LexicalTracker do
@doc """
Invoked during module expansion to annotate an import
that must be warned if unused.
must be warned if unused.
"""
def warn_import(pid, module) do
:gen_server.cast(pid, {:warn_import, module})
+10 -12
View File
@@ -25,8 +25,8 @@ defmodule Kernel.ParallelCompiler do
each_long_compilation: (Path.t() -> term()) | (Path.t(), pid() -> term()),
each_long_verification: (module() -> term()) | (module(), pid() -> term()),
each_module: (Path.t(), module(), binary() -> term()),
each_cycle: (-> {:compile, [Path.t()], [Code.diagnostic(:warning)]}
| {:runtime, [{module(), Path.t()}], [Code.diagnostic(:warning)]}),
each_cycle: (-> {:compile, [module()], [Code.diagnostic(:warning)]}
| {:runtime, [module()], [Code.diagnostic(:warning)]}),
long_compilation_threshold: pos_integer(),
long_verification_threshold: pos_integer(),
verification: boolean(),
@@ -100,8 +100,6 @@ defmodule Kernel.ParallelCompiler do
This function allows a developer to perform such tasks.
"""
@doc since: "1.16.0"
@spec pmap(Enumerable.t(input), (input -> output)) :: [output]
when input: term, output: term
def pmap(collection, fun) when is_function(fun, 1) do
ref = make_ref()
@@ -173,25 +171,25 @@ defmodule Kernel.ParallelCompiler do
* `:each_long_verification` (since v1.19.0) - for each file that takes more
than a given timeout (see the `:long_verification_threshold` option) to
verify, invoke this callback passing the module as its argument (and
compile, invoke this callback passing the module as its argument (and
optionally the PID of the process verifying the module)
* `:each_module` - for each module compiled, invokes the callback passing
the file, module and the module bytecode
* `:each_cycle` - invoked after each compilation cycle and should return one
of the following values:
* `:each_cycle` - after the given files are compiled, invokes this function
that should return the following values:
* `{:compile, modules, warnings}` - to continue compilation with a list of
further module files to compile
further modules to compile
* `{:runtime, modules, warnings}` - to stop compilation and verify the list
of `{module, path}` pairs because dependent modules have changed
of modules because dependent modules have changed
* `:long_compilation_threshold` - the timeout (in seconds) to check for files
taking too long to compile. For each file that exceeds the threshold, the
`:each_long_compilation` callback is invoked. Defaults to `10` seconds.
* `:long_verification_threshold` (since v1.19.0) - the timeout (in seconds) to
check for modules taking too long to verify. For each module that exceeds the
check for modules taking too long to compile. For each module that exceeds the
threshold, the `:each_long_verification` callback is invoked. Defaults to
`10` seconds.
@@ -214,7 +212,7 @@ defmodule Kernel.ParallelCompiler do
* `:return_diagnostics` (since v1.15.0) - returns maps with information instead of
a list of warnings and returns diagnostics as maps instead of tuples.
This option must be set to true, except for backward compatibility reasons.
This option must be set to true, except for backwards compatibibility reasons.
* `:max_concurrency` - the maximum number of files to compile in parallel.
Setting this option to 1 will compile files sequentially.
@@ -271,7 +269,7 @@ defmodule Kernel.ParallelCompiler do
* `:return_diagnostics` (since v1.15.0) - returns maps with information instead of
a list of warnings and returns diagnostics as maps instead of tuples.
This option must be set to true, except for backward compatibility reasons.
This option must be set to true, except for backwards compatibibility reasons.
"""
@doc since: "1.6.0"
+7 -7
View File
@@ -768,8 +768,8 @@ defmodule Kernel.SpecialForms do
It is used in typespecs to specify the type of a variable,
function or of a type itself:
@type num :: integer | float
@spec add(num, num) :: num
@type number :: integer | float
@spec add(number, number) :: number
It may also be used in bit strings to specify the type
of a given bit segment:
@@ -1333,9 +1333,9 @@ defmodule Kernel.SpecialForms do
sum(1, value, 3)
end
However, the code above does not work as expected, because this
injects the representation of the `value` variable,
not its contents:
Which the argument for the `:sum` function call is not the
expected result:
{:sum, [], [1, {:value, [], Elixir}, 3]}
@@ -2077,9 +2077,9 @@ defmodule Kernel.SpecialForms do
iex> try do
...> 1 / 0
...> rescue
...> x in [ArithmeticError] -> {:rescued, Exception.message(x)}
...> x in [ArithmeticError] -> [:rescued, is_exception(x)]
...> end
{:rescued, "bad argument in arithmetic expression"}
[:rescued, true]
Rescue different errors with separate clauses:
-7
View File
@@ -686,13 +686,6 @@ defmodule Kernel.Typespec do
{{:type, location(meta), :range, [left, right]}, state}
end
defp typespec({:..//, _meta, [_first, _last, _step]} = range, _vars, caller, _state) do
compile_error(
caller,
"ranges with steps are not supported in typespecs, got: #{Macro.to_string(range)}"
)
end
# Handle special forms
defp typespec({:__MODULE__, _, atom}, vars, caller, state) when is_atom(atom) do
typespec(caller.module, vars, caller, state)
+14 -5
View File
@@ -162,8 +162,16 @@ defmodule Kernel.Utils do
# TODO: Make it raise on v2.0
warn_on_duplicate_struct_key(:lists.keysort(1, fields), env)
field_map = :maps.from_list(fields)
struct = :maps.put(:__struct__, module, field_map)
foreach = fn
key when is_atom(key) ->
:ok
key ->
raise ArgumentError, "keys given to @enforce_keys must be atoms, got: #{inspect(key)}"
end
:lists.foreach(foreach, enforce_keys)
struct = :maps.from_list([__struct__: module] ++ fields)
escaped_struct = :elixir_quote.escape(struct, {:struct, module}, false)
body =
@@ -207,7 +215,7 @@ defmodule Kernel.Utils do
end
end
case enforce_keys -- :maps.keys(field_map) do
case enforce_keys -- :maps.keys(struct) do
[] ->
mapper = fn {key, val} ->
%{field: key, default: val, required: :lists.member(key, enforce_keys)}
@@ -217,9 +225,10 @@ defmodule Kernel.Utils do
derive = :lists.map(fn {_, value} -> value end, :ets.take(bag, {:accumulate, :derive}))
{struct, :lists.reverse(derive), escaped_struct, quote(do: kv), body}
invalid_keys ->
error_keys ->
raise ArgumentError,
"unknown or duplicate keys given to @enforce_keys, got: #{inspect(invalid_keys)}"
"@enforce_keys required keys (#{inspect(error_keys)}) that are not defined in defstruct: " <>
"#{inspect(fields)}"
end
end
+5 -6
View File
@@ -38,7 +38,7 @@ defmodule Keyword do
## Duplicate keys and ordering
A keyword list may have duplicate keys so it is not strictly a key-value
A keyword may have duplicate keys so it is not strictly a key-value
data type. However, most of the functions in this module work on a
key-value structure and behave similar to the functions you would
find in the `Map` module. For example, `Keyword.get/3` will get the first
@@ -517,7 +517,7 @@ defmodule Keyword do
{get, :lists.reverse(acc, [{key, value} | delete(t, key)])}
:pop ->
{current, :lists.reverse(acc, delete(t, key))}
{current, :lists.reverse(acc, t)}
other ->
raise "the given function must return a two-element tuple or :pop, got: #{inspect(other)}"
@@ -583,7 +583,7 @@ defmodule Keyword do
{get, :lists.reverse(acc, [{key, value} | delete(t, key)])}
:pop ->
{value, :lists.reverse(acc, delete(t, key))}
{value, :lists.reverse(acc, t)}
other ->
raise "the given function must return a two-element tuple or :pop, got: #{inspect(other)}"
@@ -1323,8 +1323,7 @@ defmodule Keyword do
@doc """
Drops the given `keys` from the keyword list.
If a key occurs multiple times, all of its entries are removed when that key
is included in `keys`.
Removes duplicate keys from the new keyword list.
## Examples
@@ -1434,7 +1433,7 @@ defmodule Keyword do
do: {values, acc}
@doc """
Lazily returns the first value for `key` and removes all associated entries in the keyword list.
Lazily returns and removes all values associated with `key` in the keyword list.
This is useful if the default value is very expensive to calculate or
generally difficult to set up and tear down again.
+26 -36
View File
@@ -311,6 +311,9 @@ defmodule List do
iex> List.first!([1, 2, 3])
1
iex> List.first!([])
** (ArgumentError) attempted to get the first element of an empty list
"""
@doc since: "1.20.0"
@spec first!([elem, ...]) :: elem when elem: var
@@ -363,6 +366,9 @@ defmodule List do
iex> List.last!([1, 2, 3])
3
iex> List.last!([])
** (ArgumentError) attempted to get the last element of an empty list
"""
@doc since: "1.20.0"
@spec last!([elem, ...]) :: elem when elem: var
@@ -429,7 +435,7 @@ defmodule List do
"""
@doc since: "1.13.0"
@spec keyfind!([tuple], any, non_neg_integer) :: tuple
@spec keyfind!([tuple], any, non_neg_integer) :: any
def keyfind!(list, key, position) when is_integer(position) do
:lists.keyfind(key, position + 1, list) ||
raise KeyError,
@@ -514,8 +520,8 @@ defmodule List do
As in `Enum.sort/2`, avoid using the default sorting function to sort
structs, as by default it performs structural comparison instead of a
semantic one. In such cases, you shall pass a sorting function as the third
argument or any module that implements a `compare/2` function. For example,
semantic one. In such cases, you shall pass a sorting function as third
element or any module that implements a `compare/2` function. For example,
if you have tuples with user names and their birthday, and you want to
sort on their birthday, in both ascending and descending order, you should
do:
@@ -655,7 +661,7 @@ defmodule List do
end
@doc """
Wraps `term` in a list if it is not a list.
Wraps `term` in a list if this is not list.
If `term` is already a list, it returns the list.
If `term` is `nil`, it returns an empty list.
@@ -954,12 +960,9 @@ defmodule List do
@spec pop_at(list, integer, any) :: {any, list}
def pop_at(list, index, default \\ nil) when is_integer(index) do
if index < 0 do
case length(list) + index do
index when index < 0 -> {default, list}
index -> do_pop_at(list, index, default, [], list)
end
do_pop_at(list, length(list) + index, default, [])
else
do_pop_at(list, index, default, [], list)
do_pop_at(list, index, default, [])
end
end
@@ -1027,23 +1030,11 @@ defmodule List do
end
@doc """
Converts a charlist to an existing atom or creates a new one.
Converts a charlist to an atom.
Elixir supports conversions from charlists which contain any Unicode
code point.
> #### Dynamic Atom Creation {: .warning}
>
> This function creates atoms dynamically and atoms are
> not garbage-collected. Therefore, `charlist` should not be an
> untrusted value, such as input received from a socket or during
> a web request. Consider using `to_existing_atom/1` instead.
By default, the maximum number of atoms is `1_048_576`. This limit
can be raised or lowered using the VM option `+t`.
The maximum atom size is 255 Unicode code points.
Inlined by the compiler.
## Examples
@@ -1148,7 +1139,7 @@ defmodule List do
2.2017764
"""
@spec to_float(nonempty_charlist) :: float
@spec to_float(charlist) :: float
def to_float(charlist) do
:erlang.list_to_float(charlist)
end
@@ -1164,7 +1155,7 @@ defmodule List do
123
"""
@spec to_integer(nonempty_charlist) :: integer
@spec to_integer(charlist) :: integer
def to_integer(charlist) do
:erlang.list_to_integer(charlist)
end
@@ -1182,7 +1173,7 @@ defmodule List do
1023
"""
@spec to_integer(nonempty_charlist, 2..36) :: integer
@spec to_integer(charlist, 2..36) :: integer
def to_integer(charlist, base) do
:erlang.list_to_integer(charlist, base)
end
@@ -1251,7 +1242,7 @@ defmodule List do
Please check the given list or call inspect/1 to get the list representation, got:
#{inspect(list, charlists: :as_lists)}
#{inspect(list)}
"""
else
result when is_binary(result) ->
@@ -1303,7 +1294,7 @@ defmodule List do
Please check the given list or call inspect/1 to get the list representation, got:
#{inspect(list, charlists: :as_lists)}
#{inspect(list)}
"""
else
result when is_list(result) ->
@@ -1368,13 +1359,13 @@ defmodule List do
defp myers_difference_with_diff_script(list1, list2, diff_script) do
path = {0, list1, list2, []}
find_script(0, [path], diff_script)
find_script(0, length(list1) + length(list2), [path], diff_script)
end
defp find_script(envelope, paths, diff_script) do
defp find_script(envelope, max, paths, diff_script) do
case each_diagonal(-envelope, envelope, paths, [], diff_script) do
{:done, edits} -> compact_reverse(edits, [])
{:next, paths} -> find_script(envelope + 1, paths, diff_script)
{:next, paths} -> find_script(envelope + 1, max, paths, diff_script)
end
end
@@ -1519,16 +1510,15 @@ defmodule List do
# pop_at
# The original list is returned when the index is out of bounds
defp do_pop_at([], _index, default, _acc, original) do
{default, original}
defp do_pop_at([], _index, default, acc) do
{default, :lists.reverse(acc)}
end
defp do_pop_at([head | tail], 0, _default, acc, _original) do
defp do_pop_at([head | tail], 0, _default, acc) do
{head, :lists.reverse(acc, tail)}
end
defp do_pop_at([head | tail], index, default, acc, original) do
do_pop_at(tail, index - 1, default, [head | acc], original)
defp do_pop_at([head | tail], index, default, acc) do
do_pop_at(tail, index - 1, default, [head | acc])
end
end
+53 -145
View File
@@ -166,7 +166,7 @@ defmodule Macro do
of a `__block__` or the right side of `->`. The last expression of the
block does not have metadata if it is not followed by an end of line
character (either a newline or `;`). This entry may appear multiple times
in the same metadata if the expression is surrounded by parens
in the same metadata if the expression is surround by parens
* `:format` - set to `:keyword` when an atom is defined as a keyword.
It may also be set to `:atom` to distinguish `nil`, `false`, and `true`
@@ -235,7 +235,7 @@ defmodule Macro do
`div/2` function, so that the AST for that function will become `{:div, [],
[100, 5]}` (`div(100, 5)`).
"""
@spec unpipe(t()) :: [{t(), non_neg_integer}]
@spec unpipe(t()) :: [t()]
def unpipe(expr) do
:lists.reverse(unpipe(expr, []))
end
@@ -518,7 +518,7 @@ defmodule Macro do
@doc since: "1.11.3"
@spec generate_unique_arguments(0, context :: atom) :: []
@spec generate_unique_arguments(pos_integer, context) ::
[{atom, metadata(), context}, ...]
[{atom, [counter: integer], context}, ...]
when context: atom
def generate_unique_arguments(amount, context),
do: generate_arguments(amount, context, &unique_var/2)
@@ -576,7 +576,7 @@ defmodule Macro do
"""
@doc since: "1.11.3"
@spec unique_var(var, context) :: {var, metadata(), context}
@spec unique_var(var, context) :: {var, [counter: integer], context}
when var: atom, context: atom
def unique_var(var, context) when is_atom(var) and is_atom(context) do
{var, [counter: :elixir_module.next_counter(context)], context}
@@ -690,39 +690,7 @@ defmodule Macro do
"""
@spec prewalk(t, (t -> t)) :: t
def prewalk(ast, fun) when is_function(fun, 1) do
do_prewalk(fun.(ast), fun)
end
# Mirrors do_traverse/4 with an always-pre fun and no accumulator,
# avoiding the wrapper closures and tuple threading of traverse/4.
# Each clause dispatches on the already-transformed node, so fun's
# rewrites are descended into, exactly as in traverse/4.
defp do_prewalk({form, meta, args}, fun) when is_atom(form) do
{form, meta, do_prewalk_args(args, fun)}
end
defp do_prewalk({form, meta, args}, fun) do
form = do_prewalk(fun.(form), fun)
{form, meta, do_prewalk_args(args, fun)}
end
defp do_prewalk({left, right}, fun) do
left = do_prewalk(fun.(left), fun)
{left, do_prewalk(fun.(right), fun)}
end
defp do_prewalk(list, fun) when is_list(list) do
do_prewalk_args(list, fun)
end
defp do_prewalk(x, _fun) do
x
end
defp do_prewalk_args(args, _fun) when is_atom(args), do: args
defp do_prewalk_args(args, fun) when is_list(args) do
:lists.map(fn x -> do_prewalk(fun.(x), fun) end, args)
elem(prewalk(ast, nil, fn x, nil -> {fun.(x), nil} end), 0)
end
@doc """
@@ -760,41 +728,11 @@ defmodule Macro do
"""
@spec postwalk(t, (t -> t)) :: t
def postwalk(ast, fun) when is_function(fun, 1) do
do_postwalk(ast, fun)
end
# Mirrors do_traverse/4 with an always-post fun and no accumulator,
# avoiding the wrapper closures and tuple threading of traverse/4
defp do_postwalk({form, meta, args}, fun) when is_atom(form) do
fun.({form, meta, do_postwalk_args(args, fun)})
end
defp do_postwalk({form, meta, args}, fun) do
form = do_postwalk(form, fun)
fun.({form, meta, do_postwalk_args(args, fun)})
end
defp do_postwalk({left, right}, fun) do
left = do_postwalk(left, fun)
fun.({left, do_postwalk(right, fun)})
end
defp do_postwalk(list, fun) when is_list(list) do
fun.(do_postwalk_args(list, fun))
end
defp do_postwalk(x, fun) do
fun.(x)
end
defp do_postwalk_args(args, _fun) when is_atom(args), do: args
defp do_postwalk_args(args, fun) when is_list(args) do
:lists.map(fn x -> do_postwalk(x, fun) end, args)
elem(postwalk(ast, nil, fn x, nil -> {fun.(x), nil} end), 0)
end
@doc """
This function behaves like `prewalk/3`, but performs a depth-first,
This functions behaves like `prewalk/3`, but performs a depth-first,
post-order traversal of quoted expressions using an accumulator.
"""
@spec postwalk(t, any, (t, any -> {t, any})) :: {t, any}
@@ -1002,7 +940,7 @@ defmodule Macro do
This is useful when a struct needs to be expanded at
compilation time and the struct being expanded may or may
not have been compiled (including structs defined
not have been compiled (including structs in the defined
under the module being compiled). For compiled modules,
it will invoke `module.__info__(:struct)`.
@@ -1096,7 +1034,7 @@ defmodule Macro do
defp find_invalid(other), do: {:error, other}
@doc """
Returns an enumerable that traverses the `ast` in depth-first,
Returns an enumerable that traverses the `ast` in depth-first,
pre-order traversal.
## Examples
@@ -1154,7 +1092,7 @@ defmodule Macro do
end
@doc """
Returns an enumerable that traverses the `ast` in depth-first,
Returns an enumerable that traverses the `ast` in depth-first,
post-order traversal.
## Examples
@@ -1257,15 +1195,14 @@ defmodule Macro do
The mapping function receives an integer representing the code point
of the character it wants to unescape. There are also the special atoms
`:newline`, `:unicode`, and `:hex`, which control newline, unicode,
and escaping respectively, and for which the mapping function must return
a boolean.
and escaping respectively.
Here is the default mapping function implemented by Elixir:
def unescape_map(:newline), do: true
def unescape_map(:unicode), do: true
def unescape_map(:hex), do: true
def unescape_map(?0), do: 0
def unescape_map(?0), do: ?0
def unescape_map(?a), do: ?\a
def unescape_map(?b), do: ?\b
def unescape_map(?d), do: ?\d
@@ -1288,11 +1225,7 @@ defmodule Macro do
Macro.unescape_string("example\\n", &unescape_map(&1))
"""
@spec unescape_string(
String.t(),
(non_neg_integer | :newline | :unicode | :hex ->
non_neg_integer | boolean)
) :: String.t()
@spec unescape_string(String.t(), (non_neg_integer -> non_neg_integer | false)) :: String.t()
def unescape_string(string, map) do
:elixir_interpolation.unescape_string(string, map)
end
@@ -1807,9 +1740,9 @@ defmodule Macro do
defp kw_blocks_to_string(kw, fun) do
Enum.reduce(unquote(kw_keywords), " ", fn x, acc ->
case Keyword.fetch(kw, x) do
{:ok, value} -> acc <> kw_block_to_string(x, value, fun)
:error -> acc
case Keyword.has_key?(kw, x) do
true -> acc <> kw_block_to_string(x, Keyword.get(kw, x), fun)
false -> acc
end
end) <> "end"
end
@@ -1923,7 +1856,6 @@ defmodule Macro do
definition compile-time, and you can use `Macro.expand/2`.
"""
@doc since: "1.16.0"
@spec compile_apply(module(), atom(), [term()], Macro.Env.t()) :: term()
def compile_apply(mod, fun, args, caller) do
:elixir_env.trace({:remote_function, [], mod, fun, length(args)}, %{caller | function: nil})
Kernel.apply(mod, fun, args)
@@ -2048,9 +1980,10 @@ defmodule Macro do
defp do_expand_once({{:., _, [{:__ENV__, _, atom}, field]}, _, []} = original, env)
when is_atom(atom) and is_atom(field) and env.context != :match do
case Map.fetch(env, field) do
{:ok, value} -> {maybe_escape_map(value), true}
:error -> {original, false}
if Map.has_key?(env, field) do
{maybe_escape_map(Map.get(env, field)), true}
else
{original, false}
end
end
@@ -2188,9 +2121,6 @@ defmodule Macro do
def quoted_literal?({:__aliases__, _, args}),
do: quoted_literal?(args)
def quoted_literal?({:__block__, _, [wrapped]}),
do: quoted_literal?(wrapped)
def quoted_literal?({:%, _, [left, right]}),
do: quoted_literal?(left) and quoted_literal?(right)
@@ -2570,7 +2500,7 @@ defmodule Macro do
### As a remote call
Inspect an atom as the function name of a remote call.
Inspect an atom the function name of a remote call.
iex> Macro.inspect_atom(:remote_call, :foo)
"foo"
@@ -2691,76 +2621,54 @@ defmodule Macro do
:unquoted_operator
true ->
classify_binary(Atom.to_string(atom), atom)
end
end
charlist = Atom.to_charlist(atom)
# ASCII identifiers and aliases are recognized on the binary to avoid building a
# charlist and running the (unicode aware) tokenizer, which dominates the cost of
# classifying keyword list, map and struct keys.
defp classify_binary(<<char, rest::binary>>, atom)
when char >= ?a and char <= ?z
when char == ?_ do
if valid_identifier_rest?(rest), do: :identifier, else: classify_with_tokenizer(atom)
end
if valid_alias?(charlist) do
:alias
else
case :elixir_config.identifier_tokenizer().tokenize(charlist) do
{kind, _acc, [], _, _, special} ->
cond do
kind != :identifier or :lists.member(:at, special) ->
:not_callable
defp classify_binary("Elixir" <> rest, atom) do
if valid_alias_piece?(rest), do: :alias, else: classify_with_tokenizer(atom)
end
# identifier_tokenizer used to return errors for non-nfc, but
# now it nfc-normalizes everything. However, lack of nfc is
# still a good reason to quote an atom when printing.
:lists.member(:nfkc, special) ->
:other
defp classify_binary(_binary, atom), do: classify_with_tokenizer(atom)
true ->
:identifier
end
defp classify_with_tokenizer(atom) do
case :elixir_config.identifier_tokenizer().tokenize(Atom.to_charlist(atom)) do
{kind, _acc, [], _, _, special} ->
cond do
kind != :identifier or :lists.member(:at, special) ->
:not_callable
# identifier_tokenizer used to return errors for non-nfc, but
# now it nfc-normalizes everything. However, lack of nfc is
# still a good reason to quote an atom when printing.
:lists.member(:nfkc, special) ->
:other
true ->
:identifier
_ ->
:other
end
end
_ ->
:other
end
end
defp valid_identifier_rest?(<<char, rest::binary>>)
when char >= ?a and char <= ?z
when char >= ?A and char <= ?Z
when char >= ?0 and char <= ?9
when char == ?_ do
valid_identifier_rest?(rest)
end
defp valid_alias?([?E, ?l, ?i, ?x, ?i, ?r] ++ rest), do: valid_alias_piece?(rest)
defp valid_alias?(_other), do: false
defp valid_identifier_rest?(<<char>>) when char == ?? when char == ?!, do: true
defp valid_identifier_rest?(<<>>), do: true
defp valid_identifier_rest?(_other), do: false
defp valid_alias_piece?([?., char | rest]) when char >= ?A and char <= ?Z,
do: valid_alias_piece?(trim_leading_while_valid_identifier(rest))
defp valid_alias_piece?(<<?., char, rest::binary>>) when char >= ?A and char <= ?Z,
do: valid_alias_piece_rest?(rest)
defp valid_alias_piece?(<<>>), do: true
defp valid_alias_piece?([]), do: true
defp valid_alias_piece?(_other), do: false
# A helper returning the rest of the binary would build a sub binary per piece,
# so branch back into valid_alias_piece?/1 to keep the match context.
defp valid_alias_piece_rest?(<<char, rest::binary>>)
defp trim_leading_while_valid_identifier([char | rest])
when char >= ?a and char <= ?z
when char >= ?A and char <= ?Z
when char >= ?0 and char <= ?9
when char == ?_ do
valid_alias_piece_rest?(rest)
trim_leading_while_valid_identifier(rest)
end
defp valid_alias_piece_rest?(other), do: valid_alias_piece?(other)
defp trim_leading_while_valid_identifier(other) do
other
end
@doc """
Default backend for `Kernel.dbg/2`.
@@ -3046,8 +2954,6 @@ defmodule Macro do
@doc false
def __dbg__(to_debug, header, options) do
{print_location?, options} = Keyword.pop(options, :print_location, true)
syntax_colors = if IO.ANSI.enabled?(), do: IO.ANSI.syntax_colors(), else: []
options = Keyword.merge([width: 80, pretty: true, syntax_colors: syntax_colors], options)
ansi_enabled? = options[:syntax_colors] != []
if print_location? and is_binary(header) do
@@ -3055,6 +2961,8 @@ defmodule Macro do
:ok = IO.write(IO.ANSI.format(formatted, ansi_enabled?))
end
syntax_colors = if IO.ANSI.enabled?(), do: IO.ANSI.syntax_colors(), else: []
options = Keyword.merge([width: 80, pretty: true, syntax_colors: syntax_colors], options)
{formatted, result} = dbg_format_ast_to_debug(to_debug, options)
:ok = IO.write(IO.ANSI.format([formatted, ?\n], ansi_enabled?))
result
+10 -10
View File
@@ -203,7 +203,7 @@ defmodule Macro.Env do
Returns a keyword list containing the file and line
information as keys.
"""
@spec location(t) :: [file: file, line: line]
@spec location(t) :: keyword
def location(env)
def location(%{__struct__: Macro.Env, file: file, line: line}) do
@@ -213,13 +213,13 @@ defmodule Macro.Env do
@doc false
@deprecated "Use Macro.Env.expand_alias/4 instead"
def fetch_alias(%{__struct__: Macro.Env, aliases: aliases}, atom) when is_atom(atom),
do: Keyword.fetch(aliases, String.to_unsafe_atom("Elixir.#{atom}"))
do: Keyword.fetch(aliases, :"Elixir.#{atom}")
@doc false
@deprecated "Use Macro.Env.expand_alias/4 instead"
def fetch_macro_alias(%{__struct__: Macro.Env, macro_aliases: aliases}, atom)
when is_atom(atom),
do: Keyword.fetch(aliases, String.to_unsafe_atom("Elixir.#{atom}"))
do: Keyword.fetch(aliases, :"Elixir.#{atom}")
@doc """
Returns the modules from which the given `{name, arity}` was
@@ -234,7 +234,7 @@ defmodule Macro.Env do
> This function does not emit compiler tracing events,
> which may block the compiler from correctly tracking
> dependencies. Use this function for reflection purposes
> but do not use it to expand imports into qualified
> but to do not use it to expand imports into qualified
> calls. Instead, use `expand_import/5`.
## Examples
@@ -345,7 +345,7 @@ defmodule Macro.Env do
## Additional options
It accepts the same options as `Kernel.SpecialForms.require/2` plus:
It accepts the same options as `Kernel.SpecialForm.require/2` plus:
* #{trace_option}
@@ -386,7 +386,7 @@ defmodule Macro.Env do
## Additional options
It accepts the same options as `Kernel.SpecialForms.import/2` plus:
It accepts the same options as `Kernel.SpecialForm.import/2` plus:
* `:emit_warnings` - emit warnings found when defining imports
@@ -394,7 +394,7 @@ defmodule Macro.Env do
* `:info_callback` - a function to use instead of `c:Module.__info__/1`.
The function will be invoked with `:functions` or `:macros` argument.
It has to return a list of `{function, arity}` key-value pairs.
It has to return a list of `{function, arity}` key value pairs.
If it fails, it defaults to using module metadata based on `module_info/1`.
## Examples
@@ -406,7 +406,7 @@ defmodule Macro.Env do
iex> Macro.Env.lookup_import(env, {:flatten, 1})
[{:function, List}]
It accepts the same options as `Kernel.SpecialForms.import/2`:
It accepts the same options as `Kernel.SpecialForm.import/2`:
iex> env = __ENV__
iex> Macro.Env.lookup_import(env, {:is_odd, 1})
@@ -448,7 +448,7 @@ defmodule Macro.Env do
## Additional options
It accepts the same options as `Kernel.SpecialForms.alias/2` plus:
It accepts the same options as `Kernel.SpecialForm.alias/2` plus:
* #{trace_option}
@@ -700,7 +700,7 @@ defmodule Macro.Env do
@doc """
Returns the environment stacktrace.
"""
@spec stacktrace(t) :: [{module, atom, arity, keyword}]
@spec stacktrace(t) :: list
def stacktrace(%{__struct__: Macro.Env} = env) do
cond do
is_nil(env.module) ->
+1 -1
View File
@@ -201,7 +201,7 @@ defmodule Map do
%{}
"""
@spec new :: %{}
@spec new :: map
def new, do: %{}
@doc """
+6 -15
View File
@@ -51,15 +51,6 @@ defmodule MapSet do
that they share many properties, including logarithmic time complexity. Erlang
`:sets` (version 2) are implemented on top of maps, so see the documentation
for `Map` for more information on its execution time complexity.
> #### Dialyzer opaqueness warnings {: .warning}
>
> `MapSet` internally relies on the `:sets` module which uses
> opaque types. This might cause Dialyzer to report opaqueness violations.
> These can be silenced by setting the following module attribute:
>
> @dialyzer :no_opaque
"""
@type value :: term
@@ -210,7 +201,7 @@ defmodule MapSet do
Checks if two sets are equal.
The comparison between elements is done using `===/2`,
which means a set with `1` is not equivalent to a set with
which a set with `1` is not equivalent to a set with
`1.0`.
## Examples
@@ -349,7 +340,7 @@ defmodule MapSet do
> If you find yourself doing multiple calls to `MapSet.filter/2`
> and `MapSet.reject/2` in a pipeline, it is likely more efficient
> to use `Enum.map/2` and `Enum.filter/2` instead and convert to
> a set at the end using `MapSet.new/1`.
> a map at the end using `MapSet.new/1`.
## Examples
@@ -405,15 +396,15 @@ defmodule MapSet do
iex> while_false
MapSet.new([1, 3])
iex> {while_true, while_false} = MapSet.split_with(MapSet.new([10, 20, 60, 70]), fn v -> v > 50 end)
iex> {while_true, while_false} = MapSet.split_with(MapSet.new(), fn {_k, v} -> v > 50 end)
iex> while_true
MapSet.new([60, 70])
MapSet.new([])
iex> while_false
MapSet.new([10, 20])
MapSet.new([])
"""
@doc since: "1.15.0"
@spec split_with(t(a), (a -> as_boolean(term))) :: {t(a), t(a)} when a: value
@spec split_with(MapSet.t(), (term() -> as_boolean(term))) :: {MapSet.t(), MapSet.t()}
def split_with(%MapSet{map: map}, fun) when is_function(fun, 1) do
{while_true, while_false} = Map.split_with(map, fn {key, _} -> fun.(key) end)
{%MapSet{map: while_true}, %MapSet{map: while_false}}
+3 -4
View File
@@ -754,7 +754,7 @@ defmodule Module do
"""
@doc since: "1.12.0"
@spec reserved_attributes() :: %{optional(atom()) => %{doc: binary()}}
@spec reserved_attributes() :: map
def reserved_attributes() do
%{
after_compile: %{
@@ -1192,7 +1192,7 @@ defmodule Module do
defp expand_key(key, counters) do
case counters do
%{^key => count} when is_integer(count) and count >= 1 ->
{{String.to_unsafe_atom("#{key}#{count}"), [], Elixir}, Map.put(counters, key, count - 1)}
{{:"#{key}#{count}", [], Elixir}, Map.put(counters, key, count - 1)}
_ ->
{{key, [], Elixir}, counters}
@@ -1225,8 +1225,7 @@ defmodule Module do
defp merge_signature({var, _, _} = older, {var, _, _}, _), do: older
# Otherwise, returns a generic guess
defp merge_signature({_, meta, _}, _newer, i),
do: {String.to_unsafe_atom("arg#{i}"), meta, Elixir}
defp merge_signature({_, meta, _}, _newer, i), do: {:"arg#{i}", meta, Elixir}
@doc """
Checks if the module defines the given function or macro.
+76 -111
View File
@@ -68,37 +68,17 @@ defmodule Module.ParallelChecker do
def spawn({pid, {checker, table}}, module, module_map, signatures, beam_location, log?) do
# Protocols may have been consolidated. So if we know their beam location,
# we discard their module map on purpose and start from file.
if beam_location != [] and Keyword.has_key?(module_map.attributes, :__protocol__) do
spawn_and_register_cache(pid, checker, table, module, List.to_string(beam_location), log?)
else
{mode, module_tuple} = cache_from_module_map(table, module_map, signatures)
ref = make_ref()
spawned = spawn_checker(ref, pid, checker, table, module, module_tuple, log?)
register_cache_and_checker(checker, mode, module, spawned, ref)
:ok
end
end
defp spawn_checker(ref, pid, checker, table, module, module_tuple, log?) do
spawn(fn ->
mon_ref = Process.monitor(pid)
receive do
{^ref, :check, profile} ->
# Set the compiler info so we can collect warnings
Process.link(pid)
:erlang.put(:elixir_compiler_info, {pid, self()})
{warnings, errors} = check_module(module_tuple, {checker, table}, log?, profile)
send(pid, {__MODULE__, module, warnings, errors})
send(checker, {__MODULE__, :checked, ref})
{:DOWN, ^mon_ref, _, _, _} ->
:ok
info =
if beam_location != [] and Keyword.has_key?(module_map.attributes, :__protocol__) do
List.to_string(beam_location)
else
cache_from_module_map(table, module_map, signatures)
end
end)
inner_spawn(pid, checker, table, module, info, log?)
end
defp spawn_and_register_cache(pid, checker, table, module, info, log?) do
defp inner_spawn(pid, checker, table, module, info, log?) do
ref = make_ref()
spawned =
@@ -109,28 +89,48 @@ defmodule Module.ParallelChecker do
{^ref, :cache} ->
Process.link(pid)
location =
case :code.which(module) do
[_ | _] = path -> path
_ -> info
{mode, module_tuple} =
cond do
is_binary(info) ->
location =
case :code.which(module) do
[_ | _] = path -> path
_ -> info
end
with {:ok, binary} <- File.read(location),
{:ok,
{_, [{:debug_info, {:debug_info_v1, backend, data}}, {~c"ExCk", checker}]}} <-
:beam_lib.chunks(binary, [:debug_info, ~c"ExCk"]),
{:ok, module_map} <- backend.debug_info(:elixir_v1, module, data, []),
{@elixir_checker_version, contents} <- :erlang.binary_to_term(checker) do
{cache_chunk(table, module, contents), module_map_to_module_tuple(module_map)}
else
_ -> {:uncached, nil}
end
is_tuple(info) ->
info
end
with {:ok, binary} <- File.read(location),
{:ok, {_, [{:debug_info, {:debug_info_v1, backend, data}}, {~c"ExCk", exck}]}} <-
:beam_lib.chunks(binary, [:debug_info, ~c"ExCk"]),
{:ok, module_map} <- backend.debug_info(:elixir_v1, module, data, []),
{@elixir_checker_version, contents} <- :erlang.binary_to_term(exck) do
mode = cache_chunk(table, module, contents)
module_tuple = module_map_to_module_tuple(module_map)
spawned = spawn_checker(ref, pid, checker, table, module, module_tuple, log?)
:ets.insert(table, {module, mode})
send(checker, {__MODULE__, :cached, module, spawned, ref})
else
_ ->
# Nothing to check, so we notify everyone we are done
:ets.insert(table, {module, :uncached})
send(checker, {__MODULE__, :cached, module, nil, ref})
send(pid, {__MODULE__, module, [], []})
# We only make the module available now, so they are not visible during inference
:ets.insert(table, {module, mode})
send(checker, {ref, :cached})
receive do
{^ref, :check, profile} ->
# Set the compiler info so we can collect warnings
:erlang.put(:elixir_compiler_info, {pid, self()})
{warnings, errors} =
if module_tuple do
check_module(module_tuple, {checker, table}, log?, profile)
else
{[], []}
end
send(pid, {__MODULE__, module, warnings, errors})
send(checker, {__MODULE__, :done, module})
end
{:DOWN, ^mon_ref, _, _, _} ->
@@ -138,7 +138,7 @@ defmodule Module.ParallelChecker do
end
end)
register_cache(checker, module, spawned, ref)
register(checker, module, spawned, ref)
:ok
end
@@ -192,7 +192,7 @@ defmodule Module.ParallelChecker do
log? = not match?({_, false}, value)
for {module, file} <- runtime_files do
spawn_and_register_cache(self(), checker, table, module, file, log?)
inner_spawn(self(), checker, table, module, file, log?)
end
count = :gen_server.call(checker, :start, :infinity)
@@ -533,12 +533,8 @@ defmodule Module.ParallelChecker do
:gen_server.call(server, {:unlock, module, mode}, :infinity)
end
defp register_cache_and_checker(server, mode, module, pid, ref) do
:gen_server.cast(server, {:register_cache_and_checker, mode, module, pid, ref})
end
defp register_cache(server, module, pid, ref) do
:gen_server.cast(server, {:register_cache, module, pid, ref})
defp register(server, module, pid, ref) do
:gen_server.cast(server, {:register, module, pid, ref})
end
## Server callbacks
@@ -581,8 +577,7 @@ defmodule Module.ParallelChecker do
state = %{
waiting: %{},
caches: [],
checkers: [],
modules: [],
spawned: %{},
schedulers: schedulers,
threshold: threshold,
@@ -595,9 +590,20 @@ defmodule Module.ParallelChecker do
:gen_server.enter_loop(__MODULE__, [], state)
end
def handle_call(:start, _from, %{caches: caches, protocols: protocols, table: table} = state) do
def handle_call(:start, _from, %{modules: modules, protocols: protocols, table: table} = state) do
:ets.insert(table, Enum.map(protocols, &{&1, :uncached}))
{:reply, length(caches), run_caches(%{state | protocols: []})}
for {_module, pid, ref} <- modules do
send(pid, {ref, :cache})
end
for {_module, _pid, ref} <- modules do
receive do
{^ref, :cached} -> :ok
end
end
{:reply, length(modules), run_checkers(%{state | protocols: []})}
end
def handle_call({:lock, module}, from, %{waiting: waiting} = state) do
@@ -627,21 +633,10 @@ defmodule Module.ParallelChecker do
{:noreply, state}
end
def handle_info({__MODULE__, :cached, module, pid, ref}, state) do
{_nil, spawned} = Map.pop(state.spawned, ref)
state =
if pid do
%{state | spawned: spawned, checkers: [{module, pid, ref} | state.checkers]}
else
%{state | spawned: spawned}
end
{:noreply, run_caches(state)}
end
def handle_info({__MODULE__, :checked, ref}, state) do
{timer, spawned} = Map.pop(state.spawned, ref)
def handle_info({__MODULE__, :done, module}, state) do
# Unfortunately we cannot assume uniqueness because the same module
# may be defined by mistake several times
{timer, spawned} = Map.pop(state.spawned, module)
timer && Process.cancel_timer(timer)
{:noreply, run_checkers(%{state | spawned: spawned})}
end
@@ -650,41 +645,11 @@ defmodule Module.ParallelChecker do
{:stop, :normal, state}
end
def handle_cast({:register_cache, module, pid, ref}, %{caches: caches} = state) do
{:noreply, %{state | caches: [{module, pid, ref} | caches]}}
def handle_cast({:register, module, pid, ref}, %{modules: modules} = state) do
{:noreply, %{state | modules: [{module, pid, ref} | modules]}}
end
def handle_cast(
{:register_cache_and_checker, mode, module, pid, ref},
%{caches: caches, checkers: checkers} = state
) do
{:noreply,
%{state | caches: [{module, mode} | caches], checkers: [{module, pid, ref} | checkers]}}
end
defp run_caches(%{caches: [], spawned: spawned} = state) do
if spawned == %{}, do: run_checkers(state), else: state
end
defp run_caches(%{spawned: spawned, schedulers: schedulers} = state)
when map_size(spawned) >= schedulers do
state
end
defp run_caches(%{caches: [cache | caches]} = state) do
case cache do
{_module, pid, ref} ->
send(pid, {ref, :cache})
spawned = Map.put(state.spawned, ref, nil)
run_caches(%{state | caches: caches, spawned: spawned})
{module, mode} ->
:ets.insert(state.table, {module, mode})
run_caches(%{state | caches: caches})
end
end
defp run_checkers(%{checkers: []} = state) do
defp run_checkers(%{modules: []} = state) do
state
end
@@ -693,11 +658,11 @@ defmodule Module.ParallelChecker do
state
end
defp run_checkers(%{checkers: [{module, pid, ref} | checkers]} = state) do
defp run_checkers(%{modules: [{module, pid, ref} | modules]} = state) do
send(pid, {ref, :check, state.profile})
timer = :erlang.send_after(state.threshold, self(), {__MODULE__, :timeout, module, pid})
spawned = Map.put(state.spawned, ref, timer)
run_checkers(%{state | checkers: checkers, spawned: spawned})
spawned = Map.put(state.spawned, module, timer)
run_checkers(%{state | modules: modules, spawned: spawned})
end
defp profile(module, :time, fun) do
+45 -86
View File
@@ -232,7 +232,7 @@ defmodule Module.Types do
context =
Enum.reduce(defs, context(), fn {fun_arity, _kind, meta, _clauses} = def, context ->
# Optimized version of finder, since we already have the definition
# Optimized version of finder, since we already the definition
finder = fn _ -> default_domain(:dynamic, def, fun_arity, impl) end
{_kind, _inferred, context} = local_handler(meta, fun_arity, stack, context, finder)
context
@@ -251,23 +251,27 @@ defmodule Module.Types do
context ->
{_kind, info, mapping} = Map.fetch!(context.local_sigs, fun_arity)
{used_indexes, unused_indexes} =
Enum.reduce(mapping, {[], []}, fn {clause_index, type_index},
{used_indexes, unused_indexes} ->
if type_index in pending and not skip_unused_clause?(info, type_index) do
{used_indexes, [clause_index | unused_indexes]}
else
{[clause_index | used_indexes], unused_indexes}
end
if pending != [] do
{used_indexes, unused_indexes} =
Enum.reduce(mapping, {[], []}, fn {clause_index, type_index},
{used_indexes, unused_indexes} ->
if type_index in pending and not skip_unused_clause?(info, type_index) do
{used_indexes, [clause_index | unused_indexes]}
else
{[clause_index | used_indexes], unused_indexes}
end
end)
unused_indexes = Enum.uniq(unused_indexes) -- used_indexes
Enum.reduce(unused_indexes, context, fn clause_index, context ->
{meta, _args, _guards, _body} = Enum.fetch!(clauses, clause_index)
stack = %{stack | function: fun_arity} |> with_file_meta(meta)
Helpers.warn(__MODULE__, {:unused_clause, kind, fun_arity}, meta, stack, context)
end)
unused_indexes = Enum.uniq(unused_indexes) -- used_indexes
Enum.reduce(unused_indexes, context, fn clause_index, context ->
{meta, _args, _guards, _body} = Enum.fetch!(clauses, clause_index)
stack = %{stack | function: fun_arity} |> with_file_meta(meta)
Helpers.warn(__MODULE__, {:unused_clause, kind, fun_arity}, meta, stack, context)
end)
else
context
end
end
end
@@ -394,15 +398,16 @@ defmodule Module.Types do
end
defp infer_local_handler(clauses, base_info, kind, fun, expected, stack, context) do
{_, clauses_types, clauses_context} =
Enum.reduce(clauses, {Pattern.init_previous(), [], context}, fn
{meta, args, guards, body}, {previous, inferred, acc_context} ->
{_, _, _, domain, mapping, clauses_types, clauses_context} =
Enum.reduce(clauses, {0, 0, Pattern.init_previous(), [], [], [], context}, fn
{meta, args, guards, body},
{index, total, previous, domain, mapping, inferred, acc_context} ->
stack = with_file_meta(stack, meta)
fresh_context = fresh_context(acc_context)
info = {base_info, args, guards}
try do
{trees, precise?, head_no_previous_args_types, previous, head_context} =
{trees, _precise?, head_no_previous_args_types, previous, head_context} =
Pattern.of_head(args, guards, expected, previous, info, meta, stack, fresh_context)
{return_type, context} =
@@ -410,88 +415,42 @@ defmodule Module.Types do
args_types = Pattern.of_domain(trees, stack, context)
head_args_types =
case inferred do
[] -> nil
_ -> Pattern.of_domain(trees, stack, head_context)
{type_index, inferred} =
add_inferred(inferred, args_types, return_type, total - 1, [])
domain =
case domain do
[] ->
args_types
_ ->
head_args_types = Pattern.of_domain(trees, stack, head_context)
compute_domain(args_types, head_args_types, head_no_previous_args_types, domain)
end
args_triplet = {args_types, head_args_types, head_no_previous_args_types}
inferred = [{args_triplet, return_type, precise?} | inferred]
{previous, inferred, context}
if type_index == -1 do
mapping = [{index, total} | mapping]
{index + 1, total + 1, previous, domain, mapping, inferred, context}
else
mapping = [{index, type_index} | mapping]
{index + 1, total, previous, domain, mapping, inferred, context}
end
rescue
e ->
internal_error!(e, __STACKTRACE__, kind, meta, fun, args, guards, body, stack)
end
end)
{clauses_types, mapping, domain} =
clauses_types
|> Enum.reverse()
|> group_clauses()
domain =
case clauses_types do
[_] -> nil
_ -> domain
end
inferred = {:infer, domain, clauses_types}
inferred = {:infer, domain, Enum.reverse(clauses_types)}
{inferred, mapping, restore_context(clauses_context, context)}
end
defp group_clauses(clauses) do
{_, all_clauses, filtered_clauses, non_empty?} =
Enum.reduce(clauses, {0, [], [], false}, fn
{_args_triplet, return, precise?} = clause,
{index, all_clauses, filtered_clauses, non_empty?} ->
empty? = Descr.empty?(return)
indexed_clause = {clause, index}
filtered_clauses =
if precise? and empty? do
filtered_clauses
else
[indexed_clause | filtered_clauses]
end
{index + 1, [indexed_clause | all_clauses], filtered_clauses, non_empty? or not empty?}
end)
clauses =
if non_empty? do
Enum.reverse(filtered_clauses)
else
Enum.reverse(all_clauses)
end
[
{{{args, _head_args, _head_no_previous_args}, _return, _precise?}, _index}
| clauses_tail
] = clauses
domain =
Enum.reduce(clauses_tail, args, fn
{{{args, head_args, head_no_previous_args}, _return, _precise?}, _index}, domain ->
compute_domain(args, head_args, head_no_previous_args, domain)
end)
{_, mapping, inferred} =
Enum.reduce(clauses, {0, [], []}, fn
{{{args, _head_args, _head_no_previous_args}, return, _precise?}, index},
{total, mapping, inferred} ->
{type_index, inferred} = add_inferred(inferred, args, return, total - 1, [])
if type_index == -1 do
{total + 1, [{index, total} | mapping], inferred}
else
{total, [{index, type_index} | mapping], inferred}
end
end)
{Enum.reverse(inferred), mapping, domain}
end
defp compute_domain(
[arg | args_types],
[head_arg | head_args_types],
+55 -169
View File
@@ -40,13 +40,7 @@ defmodule Module.Types.Apply do
fas = list(tuple([atom(), integer()]))
struct_info =
list(
closed_map(
default: {term(), true},
field: {atom(), false},
required: {boolean(), true}
)
)
list(closed_map(default: if_set(term()), field: atom(), required: if_set(boolean())))
shared_info = [
attributes: list(tuple([atom(), list(term())])),
@@ -118,15 +112,7 @@ defmodule Module.Types.Apply do
args_or_arity = opt_union(list(term()), integer())
args_or_none = opt_union(list(term()), atom([:none]))
custom_info_key = opt_difference(atom(), atom([:file, :line, :error_info]))
extra_info =
list(
tuple([atom([:file]), opt_union(list(integer()), binary())])
|> opt_union(tuple([atom([:line]), integer()]))
|> opt_union(tuple([atom([:error_info]), open_map()]))
|> opt_union(tuple([custom_info_key, term()]))
)
extra_info = kw.(file: list(integer()), line: integer(), error_info: open_map())
raise_stacktrace =
list(
@@ -151,8 +137,6 @@ defmodule Module.Types.Apply do
{[atom([left]), atom([right])], atom([left or right])}
end
non_empty_tuple = opt_difference(open_tuple([]), tuple([]))
for {mod, fun, clauses} <- [
# :binary
{:binary, :copy, [{[binary(), integer()], binary()}]},
@@ -230,38 +214,35 @@ defmodule Module.Types.Apply do
{:erlang, :spawn_link, [{mfargs, pid()}]},
{:erlang, :spawn_monitor, [{[fun(0)], tuple([pid(), reference()])}]},
{:erlang, :spawn_monitor, [{mfargs, tuple([pid(), reference()])}]},
{:erlang, :split_binary,
[
{[binary(), integer()], tuple([binary(), binary()])},
{[bitstring_no_binary(), integer()], tuple([binary(), bitstring_no_binary()])}
]},
{:erlang, :split_binary, [{[binary(), integer()], tuple([binary(), binary()])}]},
{:erlang, :tuple_size, [{[open_tuple([])], integer()}]},
{:erlang, :trunc, [{[opt_union(integer(), float())], integer()}]},
# TODO: Replace term()/dynamic() by parametric types
{:erlang, :++,
[
{[empty_list(), term()], dynamic(term())},
{[non_empty_list(term()), term()], dynamic(non_empty_list(term(), term()))}
]},
{:erlang, :--, [{[list(term()), list(term())], dynamic(list(term()))}]},
{:erlang, :delete_element, [{[integer(), non_empty_tuple], dynamic(open_tuple([]))}]},
{:erlang, :delete_element, [{[integer(), open_tuple([])], dynamic(open_tuple([]))}]},
{:erlang, :hd, [{[non_empty_list(term(), term())], dynamic()}]},
{:erlang, :element, [{[integer(), non_empty_tuple], dynamic()}]},
{:erlang, :element, [{[integer(), open_tuple([])], dynamic()}]},
{:erlang, :insert_element,
[{[integer(), open_tuple([]), term()], dynamic(non_empty_tuple)}]},
[{[integer(), open_tuple([]), term()], dynamic(open_tuple([]))}]},
{:erlang, :list_to_tuple, [{[list(term())], dynamic(open_tuple([]))}]},
{:erlang, :map_get, [{[term(), open_map()], term()}]},
{:erlang, :max, [{[term(), term()], dynamic()}]},
{:erlang, :min, [{[term(), term()], dynamic()}]},
{:erlang, :send, [{[send_destination, term()], dynamic()}]},
{:erlang, :setelement,
[{[integer(), non_empty_tuple, term()], dynamic(non_empty_tuple)}]},
{:erlang, :setelement, [{[integer(), open_tuple([]), term()], dynamic(open_tuple([]))}]},
{:erlang, :tl, [{[non_empty_list(term(), term())], dynamic()}]},
{:erlang, :tuple_to_list, [{[open_tuple([])], dynamic(list(term()))}]},
## Kernel
{Kernel, :elem, [{[non_empty_tuple, integer()], dynamic()}]},
{Kernel, :elem, [{[open_tuple([]), integer()], dynamic()}]},
{Kernel, :is_map_key, [{[open_map(), term()], boolean()}]},
{Kernel, :put_elem, [{[non_empty_tuple, integer(), term()], dynamic(non_empty_tuple)}]},
{Kernel, :put_elem, [{[open_tuple([]), integer(), term()], dynamic(open_tuple([]))}]},
## Lists
{:lists, :member,
@@ -275,8 +256,7 @@ defmodule Module.Types.Apply do
{Map, :fetch,
[{[open_map(), term()], tuple([atom([:ok]), term()]) |> opt_union(atom([:error]))}]},
{Map, :fetch!, [{[open_map(), term()], term()}]},
{Map, :from_struct,
[{[open_map(__struct__: {atom(), false})], open_map(__struct__: {none(), true})}]},
{Map, :from_struct, [{[open_map()], open_map(__struct__: not_set())}]},
{Map, :get, [{[open_map(), term()], term()}]},
{Map, :get, [{[open_map(), term(), term()], term()}]},
{Map, :get_lazy, [{[open_map(), term(), fun(0)], term()}]},
@@ -293,9 +273,9 @@ defmodule Module.Types.Apply do
{Map, :replace_lazy, [{[open_map(), term(), fun(1)], open_map()}]},
{Map, :update, [{[open_map(), term(), term(), fun(1)], open_map()}]},
{Map, :update!, [{[open_map(), term(), fun(1)], open_map()}]},
{Tuple, :delete_at, [{[non_empty_tuple, integer()], dynamic(open_tuple([]))}]},
{Tuple, :delete_at, [{[open_tuple([]), integer()], dynamic(open_tuple([]))}]},
{Tuple, :duplicate, [{[term(), integer()], tuple()}]},
{Tuple, :insert_at, [{[open_tuple([]), integer(), term()], dynamic(non_empty_tuple)}]},
{Tuple, :insert_at, [{[open_tuple([]), integer(), term()], dynamic(open_tuple([]))}]},
{:maps, :from_keys, [{[list(term()), term()], open_map()}]},
{:maps, :find,
[{[term(), open_map()], tuple([atom([:ok]), term()]) |> opt_union(atom([:error]))}]},
@@ -466,14 +446,9 @@ defmodule Module.Types.Apply do
# a and not number(), b and not number() -> a and b
#
# However, during inference, we type it as `a, b -> a and b` only.
# Either argument may be discarded based on term ordering, so the result's
# expected type cannot be used to refine either argument. The result itself
# flows directly to the consumer, so it can still be contextually refined.
{left_type, context} = of_fun.(left, term(), expr, stack, context)
{right_type, context} = of_fun.(right, term(), expr, stack, context)
{left_type, context} = of_fun.(left, expected, expr, stack, context)
{right_type, context} = of_fun.(right, expected, expr, stack, context)
result = opt_union(left_type, right_type)
refined_result = opt_intersection(result, expected)
result = if empty?(refined_result), do: result, else: refined_result
if error = mismatched_ordered_comparison(left_type, right_type, stack) do
remote_error(error, :erlang, name, 2, expr, stack, context)
@@ -1017,71 +992,61 @@ defmodule Module.Types.Apply do
info =
{:strong, [term(), open_map()],
[
{[term(), open_map([{key, {term(), false}}])], atom([true])},
{[term(), open_map([{key, {none(), true}}])], atom([false])}
{[term(), open_map([{key, term()}])], atom([true])},
{[term(), open_map([{key, not_set()}])], atom([false])}
]}
{info, filter_domain(info, expected, 2), context}
end
def remote_domain(:maps, :is_key, [key, map], expected, meta, stack, context)
when is_atom(key) do
remote_domain(:erlang, :is_map_key, [key, map], expected, meta, stack, context)
end
def remote_domain(Kernel, :is_map_key, [_map, key], expected, _meta, _stack, context)
when is_atom(key) do
info =
{:strong, [open_map(), term()],
[
{[open_map([{key, {term(), false}}]), term()], atom([true])},
{[open_map([{key, {none(), true}}]), term()], atom([false])}
{[open_map([{key, term()}]), term()], atom([true])},
{[open_map([{key, not_set()}]), term()], atom([false])}
]}
{info, filter_domain(info, expected, 2), context}
end
def remote_domain(Map, :has_key?, [map, key], expected, meta, stack, context)
when is_atom(key) do
remote_domain(Kernel, :is_map_key, [map, key], expected, meta, stack, context)
end
def remote_domain(:erlang, :map_get, [key, _], expected, _meta, _stack, context)
when is_atom(key) do
domain = [term(), open_map([{key, {expected, false}}])]
domain = [term(), open_map([{key, expected}])]
{{:strong, nil, [{domain, term()}]}, domain, context}
end
def remote_domain(Map, :fetch!, [_, key], expected, _meta, _stack, context) when is_atom(key) do
domain = [open_map([{key, {expected, false}}]), term()]
domain = [open_map([{key, expected}]), term()]
{{:strong, nil, [{domain, term()}]}, domain, context}
end
def remote_domain(:maps, :get, [key, _], expected, _meta, _stack, context) when is_atom(key) do
domain = [term(), open_map([{key, {expected, false}}])]
domain = [term(), open_map([{key, expected}])]
{{:strong, nil, [{domain, term()}]}, domain, context}
end
def remote_domain(Map, :replace!, [_, key, _], _expected, _meta, _stack, context)
when is_atom(key) do
domain = [open_map([{key, {term(), false}}]), term(), term()]
domain = [open_map([{key, term()}]), term(), term()]
{{:strong, nil, [{domain, open_map()}]}, domain, context}
end
def remote_domain(:maps, :update, [key, _, _], _expected, _meta, _stack, context)
when is_atom(key) do
domain = [term(), term(), open_map([{key, {term(), false}}])]
domain = [term(), term(), open_map([{key, term()}])]
{{:strong, nil, [{domain, open_map()}]}, domain, context}
end
def remote_domain(Map, :pop!, [_, key], _expected, _meta, _stack, context) when is_atom(key) do
domain = [open_map([{key, {term(), false}}]), term()]
domain = [open_map([{key, term()}]), term()]
{{:strong, nil, [{domain, tuple([term(), open_map()])}]}, domain, context}
end
def remote_domain(Map, :update!, [_, key, _], _expected, _meta, _stack, context)
when is_atom(key) do
domain = [open_map([{key, {term(), false}}]), term(), fun(1)]
domain = [open_map([{key, term()}]), term(), fun(1)]
{{:strong, nil, [{domain, open_map()}]}, domain, context}
end
@@ -1121,10 +1086,6 @@ defmodule Module.Types.Apply do
end
end
defp remote_apply(:erlang, :element, info, [_index, tuple] = args_types, stack) do
remote_apply_tuple_element(info, args_types, tuple, stack)
end
defp remote_apply(:erlang, :map_get, _info, [key, map] = args_types, stack) do
case map_get(map, key) do
{:ok, value} -> {:ok, return(value, args_types, stack)}
@@ -1133,13 +1094,6 @@ defmodule Module.Types.Apply do
end
end
defp remote_apply(:erlang, :send, info, [_dest, message] = args_types, stack) do
case remote_apply(info, args_types, stack) do
{:ok, _type} -> {:ok, return(message, args_types, stack)}
other -> other
end
end
defp remote_apply(:erlang, :tl, _info, [list], stack) do
case list_tl(list) do
{:ok, value_type} -> {:ok, return(value_type, [list], stack)}
@@ -1147,58 +1101,19 @@ defmodule Module.Types.Apply do
end
end
defp remote_apply(:erlang, :++, _info, [left, right], stack) do
# TODO: remove once we add parametric types, this will just be:
# empty_list(), a -> a
# non_empty_list(elem), a -> non_empty_list(elem, a)
case list_of(left) do
{empty_list?, list_of} ->
left_result =
if empty_list? do
right
else
none()
end
right_result =
if list_of do
non_empty_list(list_of, right)
else
none()
end
{:ok, return(opt_union(left_result, right_result), [left, right], stack)}
:badproperlist ->
{:error, badremote(:erlang, :++, [left, right])}
end
end
defp remote_apply(Kernel, :elem, info, [tuple, _index] = args_types, stack) do
remote_apply_tuple_element(info, args_types, tuple, stack)
end
@struct_key atom([:__struct__])
@nil_atom atom([nil])
defp remote_apply(Map, :from_struct, info, [map] = args_types, stack) do
case remote_apply(info, args_types, stack) do
{:ok, _type} ->
case map_update(map, @struct_key, none(), true, false, true) do
{_value, descr, _errors} ->
{:ok, return(descr, args_types, stack)}
_ ->
{:error, badremote(Map, :from_struct, args_types)}
end
other ->
other
defp remote_apply(Map, :from_struct, _info, [map] = args_types, stack) do
case map_update(map, @struct_key, not_set(), false, true) do
{_value, descr, _errors} -> {:ok, return(descr, args_types, stack)}
:badmap -> {:error, badremote(Map, :from_struct, args_types)}
{:error, _errors} -> {:ok, map}
end
end
defp remote_apply(Map, :delete, _info, [map, key] = args_types, stack) do
case map_update(map, key, none(), true, false, true) do
case map_update(map, key, not_set(), false, true) do
{_value, descr, _errors} -> {:ok, return(descr, args_types, stack)}
:badmap -> {:error, badremote(Map, :delete, args_types)}
{:error, _errors} -> {:ok, map}
@@ -1275,7 +1190,7 @@ defmodule Module.Types.Apply do
_ -> args_types
end
case map_update(map, key, none(), true, true, false) do
case map_update(map, key, not_set(), true, false) do
{value, descr, _errors} ->
value = opt_union(value, default)
{:ok, return(tuple([value, descr]), args_types, stack)}
@@ -1291,7 +1206,7 @@ defmodule Module.Types.Apply do
defp remote_apply(Map, :pop_lazy, _info, [map, key, fun] = args_types, stack) do
case fun_apply(fun, []) do
{:ok, default} ->
case map_update(map, key, none(), true, true, false) do
case map_update(map, key, not_set(), true, false) do
{value, descr, _errors} ->
value = opt_union(value, default)
{:ok, return(tuple([value, descr]), args_types, stack)}
@@ -1309,7 +1224,7 @@ defmodule Module.Types.Apply do
end
defp remote_apply(Map, :put, _info, [map, key, value] = args_types, stack) do
case map_update(map, key, value, false, false, true) do
case map_update(map, key, value, false, true) do
{_value, descr, _errors} -> {:ok, return(descr, args_types, stack)}
:badmap -> {:error, badremote(Map, :put, args_types)}
{:error, _errors} -> {:ok, map}
@@ -1317,7 +1232,7 @@ defmodule Module.Types.Apply do
end
defp remote_apply(Map, :pop!, _info, [map, key] = args_types, stack) do
case map_update(map, key, none(), true, true, false) do
case map_update(map, key, not_set(), true, false) do
{value, descr, _errors} -> {:ok, return(tuple([value, descr]), args_types, stack)}
:badmap -> {:error, badremote(Map, :pop!, args_types)}
{:error, _errors} -> {:error, {:badkeydomain, map, key, "raise"}}
@@ -1325,7 +1240,7 @@ defmodule Module.Types.Apply do
end
defp remote_apply(Map, :replace, _info, [map, key, value] = args_types, stack) do
fun = fn _value, optional? -> {value, optional?} end
fun = fn optional?, _type -> if optional?, do: if_set(value), else: value end
case map_update_fun(map, key, fun, false, false) do
{_value, descr, _errors} -> {:ok, return(descr, args_types, stack)}
@@ -1335,7 +1250,7 @@ defmodule Module.Types.Apply do
end
defp remote_apply(Map, :replace!, _info, [map, key, value] = args_types, stack) do
fun = fn _value, optional? -> {value, optional?} end
fun = fn optional?, _type -> if optional?, do: if_set(value), else: value end
case map_update_fun(map, key, fun, false, false) do
{_value, descr, _errors} -> {:ok, return(descr, args_types, stack)}
@@ -1362,12 +1277,12 @@ defmodule Module.Types.Apply do
_ -> map
end
fun_apply = fn arg_type, optional? ->
fun_apply = fn optional?, arg_type ->
if empty?(arg_type) do
{default, false}
default
else
case fun_apply(fun, [arg_type]) do
{:ok, res} -> {if(optional?, do: opt_union(res, default), else: res), false}
{:ok, res} -> if optional?, do: opt_union(res, default), else: res
reason -> throw({:badapply, reason, [arg_type]})
end
end
@@ -1411,6 +1326,7 @@ defmodule Module.Types.Apply do
if key_type == dynamic() or key_type == term() do
{:ok, return(open_map(), args_types, stack)}
else
value_type = if_set(value_type)
domain_keys = to_domain_keys(key_type)
keys =
@@ -1419,13 +1335,7 @@ defmodule Module.Types.Apply do
_ -> [domain_keys]
end
map =
closed_map(
Enum.map(keys, fn
key when is_atom(key) -> {key, {value_type, true}}
key -> {key, value_type}
end)
)
map = closed_map(Enum.map(keys, &{&1, value_type}))
map_and_maybe_empty_map =
case empty_list? do
@@ -1457,7 +1367,7 @@ defmodule Module.Types.Apply do
end
defp remote_apply(:maps, :put, _info, [key, value, map] = args_types, stack) do
case map_update(map, key, value, false, false, true) do
case map_update(map, key, value, false, true) do
{_value, descr, _errors} -> {:ok, return(descr, args_types, stack)}
:badmap -> {:error, badremote(:maps, :put, args_types)}
{:error, _errors} -> {:ok, map}
@@ -1465,7 +1375,7 @@ defmodule Module.Types.Apply do
end
defp remote_apply(:maps, :remove, _info, [key, map] = args_types, stack) do
case map_update(map, key, none(), true, false, true) do
case map_update(map, key, not_set(), false, true) do
{_value, descr, _errors} -> {:ok, return(descr, args_types, stack)}
:badmap -> {:error, badremote(:maps, :remove, args_types)}
{:error, _errors} -> {:ok, map}
@@ -1473,7 +1383,7 @@ defmodule Module.Types.Apply do
end
defp remote_apply(:maps, :take, _info, [key, map] = args_types, stack) do
case map_update(map, key, none(), true, true, false) do
case map_update(map, key, not_set(), true, false) do
{value, descr, _errors} ->
result = opt_union(tuple([value, descr]), atom([:error]))
{:ok, return(result, args_types, stack)}
@@ -1494,7 +1404,7 @@ defmodule Module.Types.Apply do
end
defp remote_apply(:maps, :update, _info, [key, value, map] = args_types, stack) do
fun = fn _value, optional? -> {value, optional?} end
fun = fn optional?, _type -> if optional?, do: if_set(value), else: value end
case map_update_fun(map, key, fun, false, false) do
{_value, descr, _errors} -> {:ok, return(descr, args_types, stack)}
@@ -1512,22 +1422,13 @@ defmodule Module.Types.Apply do
defp remote_apply(mod, :to_existing_atom, info, [_string, list] = args_types, stack)
when mod in [String, List] do
# TODO: remove once we add parametric types, this will just be:
# TODO remove once we add parametric types, this will just be:
# binary(), non_empty_list(a) -> a when a: atom()
case remote_apply(info, args_types, stack) do
{:ok, _} ->
# If list is a dynamic type, remote_apply only guarantees compatibility,
# and therefore we may still have improper lists or lists of dynamic().
# Note we cannot have static empty lists, as those would fail on remote_apply,
# but we can have dynamic empty lists (which we should ignore)
case list_of(list) do
{_empty_list, refined_atom} when is_descr(refined_atom) ->
{:ok, return(opt_intersection(refined_atom, atom()), args_types, stack)}
:badproperlist ->
{:error, badremote(mod, :to_existing_atom, args_types)}
end
{false, refined_atom} = list_of(list)
{:ok, refined_atom}
other ->
other
@@ -1538,21 +1439,6 @@ defmodule Module.Types.Apply do
remote_apply(info, args_types, stack)
end
defp remote_apply_tuple_element(info, args_types, tuple, stack) do
# TODO: Convert this into a regular type signature {...a} and not {}, integer -> a
with {:ok, fallback} <- remote_apply(info, args_types, stack) do
case tuple_values(tuple) do
:badtuple ->
# This is not virtually possible, since remote apply validates the arguments.
# So returning the fallback is acceptable.
{:ok, fallback}
values ->
{:ok, return(values, args_types, stack)}
end
end
end
defp remote_apply(:none, _args_types, _stack) do
{:ok, dynamic()}
end
@@ -1880,8 +1766,8 @@ defmodule Module.Types.Apply do
defp map_put_new(map, key, value, name, args_types, stack) do
fun = fn
type, true -> {opt_union(type, value), false}
type, false -> {if(empty?(type), do: value, else: type), false}
true, type -> opt_union(type, value)
false, type -> if empty?(type), do: value, else: type
end
case map_update_fun(map, key, fun, false, true) do
@@ -1899,9 +1785,9 @@ defmodule Module.Types.Apply do
_ -> map
end
fun_apply = fn arg_type, optional? ->
fun_apply = fn optional?, arg_type ->
case fun_apply(fun, [arg_type]) do
{:ok, res} -> {res, optional?}
{:ok, res} -> if optional?, do: if_set(res), else: res
reason -> throw({:badapply, reason, [arg_type]})
end
end
File diff suppressed because it is too large Load Diff
+57 -79
View File
@@ -11,44 +11,40 @@ defmodule Module.Types.Expr do
14 = length(Macro.Env.__info__(:struct))
aliases = list(tuple([atom(), atom()]))
macro_aliases = list(tuple([atom(), tuple([term(), atom()])]))
functions_and_macros = list(tuple([atom(), list(tuple([atom(), integer()]))]))
list_of_modules = list(atom())
@try_catch atom([:error, :exit, :throw])
@caller closed_map(
__struct__: {atom([Macro.Env]), false},
aliases: {aliases, false},
context: {atom([:match, :guard, nil]), false},
context_modules: {list_of_modules, false},
file: {binary(), false},
function: {opt_union(tuple([atom(), integer()]), atom([nil])), false},
functions: {functions_and_macros, false},
lexical_tracker: {opt_union(pid(), atom([nil])), false},
line: {integer(), false},
macro_aliases: {macro_aliases, false},
macros: {functions_and_macros, false},
module: {atom(), false},
requires: {list_of_modules, false},
tracers: {list_of_modules, false},
versioned_vars: {open_map(), false}
__struct__: atom([Macro.Env]),
aliases: aliases,
context: atom([:match, :guard, nil]),
context_modules: list_of_modules,
file: binary(),
function: opt_union(tuple([atom(), integer()]), atom([nil])),
functions: functions_and_macros,
lexical_tracker: opt_union(pid(), atom([nil])),
line: integer(),
macro_aliases: aliases,
macros: functions_and_macros,
module: atom(),
requires: list_of_modules,
tracers: list_of_modules,
versioned_vars: open_map()
)
# We do not make exception dynamic on purpose. If you do a blank rescue,
# then we will assume you need to statically handle all possible exceptions.
@exception open_map(__struct__: {atom(), false}, __exception__: {term(), false})
@exception open_map(__struct__: atom(), __exception__: term())
args_or_arity = opt_union(list(term()), integer())
custom_info_key = opt_difference(atom(), atom([:file, :line, :error_info]))
extra_info =
list(
tuple([atom([:file]), opt_union(list(integer()), binary())])
tuple([atom([:file]), list(integer())])
|> opt_union(tuple([atom([:line]), integer()]))
|> opt_union(tuple([atom([:error_info]), open_map()]))
|> opt_union(tuple([custom_info_key, term()]))
)
@stacktrace list(
@@ -186,7 +182,7 @@ defmodule Module.Types.Expr do
expected_pairs =
Enum.flat_map(pairs_types, fn {key_type, _value_type} ->
case atom_fetch(key_type) do
{:finite, [key]} -> [{key, {term(), false}}]
{:finite, [key]} -> [{key, term()}]
_ -> []
end
end)
@@ -625,41 +621,34 @@ defmodule Module.Types.Expr do
else
# TODO: Use the collectable protocol for the output
into = Keyword.get(opts, :into, [])
{into_type, into_kind, context} = for_into(into, meta, stack, context)
{into_kinds, into_type, expected, context} =
for_into(into, meta, expected, stack, context)
{block_type, context} = of_expr(block, expected, block, stack, context)
result_type =
if :non_empty_list in into_kinds do
opt_union(into_type, non_empty_list(block_type))
else
into_type
end
{result_type, context} =
if :bitstring in into_kinds do
case into_kind do
:bitstring ->
{block_type, context} = of_expr(block, bitstring(), block, stack, context)
intersection = opt_intersection(block_type, bitstring())
# TODO: currently if :into is a binary, then we expect the body
# to return a binary, even if the binary is a gradual type. This
# is ok for now because we only check for bitstring if the type
# is a subset of empty_list() or bitstring(), but we may want to
# relax in the future.
if empty?(intersection) do
error = {:badbitbody, block_type, block, context}
{error_type(), error(__MODULE__, error, meta, stack, context)}
else
# If into_type is a binary but the block is a bitstring_no_binary(),
# then the result may be a bitstring_no_binary().
{opt_union(intersection, result_type), context}
{opt_union(into_type, intersection), context}
end
else
{result_type, context}
end
{if(gradual?(into_type), do: dynamic(result_type), else: result_type), context}
:non_empty_list ->
expected =
case list_hd(expected) do
{:ok, head} -> head
_ -> term()
end
{block_type, context} = of_expr(block, expected, block, stack, context)
{opt_union(into_type, non_empty_list(block_type)), context}
:none ->
{_, context} = of_expr(block, term(), block, stack, context)
{into_type, context}
end
|> dynamic_unless_static(stack)
end
end)
@@ -702,9 +691,7 @@ defmodule Module.Types.Expr do
def of_expr({{:., _, [callee, key_or_fun]}, meta, []} = call, expected, expr, stack, context)
when not is_atom(callee) and is_atom(key_or_fun) do
if Keyword.get(meta, :no_parens, false) do
{type, context} =
of_expr(callee, open_map([{key_or_fun, {expected, false}}]), expr, stack, context)
{type, context} = of_expr(callee, open_map([{key_or_fun, expected}]), expr, stack, context)
Of.map_fetch(call, type, key_or_fun, stack, context)
else
{type, context} = of_expr(callee, atom(), call, stack, context)
@@ -765,7 +752,7 @@ defmodule Module.Types.Expr do
defp of_tuple([elem | elems], index, acc, expected, expr, stack, context) do
expr_expected =
case tuple_fetch(expected, index) do
{_optional?, type} -> type
{_, type} -> type
_ -> term()
end
@@ -780,7 +767,7 @@ defmodule Module.Types.Expr do
## Try
defp of_rescue(var, exceptions, expr, info, meta, stack, context) do
args = [__exception__: {term(), false}]
args = [__exception__: term()]
{structs, context} =
Enum.map_reduce(exceptions, context, fn exception, context ->
@@ -846,20 +833,13 @@ defmodule Module.Types.Expr do
@into_compile opt_union(bitstring(), empty_list())
defp maybe_list_hd_or_term(expected) do
case list_hd(expected) do
{:ok, head} -> head
_ -> term()
end
end
defp for_into([], _meta, _stack, context),
do: {empty_list(), :non_empty_list, context}
defp for_into([], _meta, expected, _stack, context),
do: {[:non_empty_list], empty_list(), maybe_list_hd_or_term(expected), context}
defp for_into(binary, _meta, _stack, context) when is_binary(binary),
do: {binary(), :bitstring, context}
defp for_into(binary, _meta, _expected, _stack, context) when is_binary(binary),
do: {[:bitstring], binary(), bitstring(), context}
defp for_into(into, meta, expected, stack, context) do
defp for_into(into, meta, stack, context) do
meta =
case into do
{_, meta, _} -> meta
@@ -876,26 +856,24 @@ defmodule Module.Types.Expr do
# We use subtype? instead of compatible because we want to handle
# only bitstring/list, even if a dynamic with something else is given.
if subtype?(type, @into_compile) do
cond do
bitstring_type?(type) ->
kinds = if empty_list_type?(type), do: [:bitstring, :non_empty_list], else: [:bitstring]
# A comprehension may concatenate the block an arbitrary number of times.
# Even if both the initial value and each block are unaligned bitstrings,
# repeated concatenation may eventually produce an aligned binary.
{kinds, opt_union(binary(), type), bitstring(), context}
case {bitstring_type?(type), empty_list_type?(type)} do
# If they can be both be true, then we don't know
# what the contents of the block are for
{true, true} ->
type = opt_union(bitstring(), list(term()))
{if(gradual?(type), do: dynamic(type), else: type), :none, context}
empty_list_type?(type) ->
{[:non_empty_list], type, maybe_list_hd_or_term(expected), context}
{false, true} ->
{type, :non_empty_list, context}
# The type is empty...
true ->
{[], type, term(), context}
{true, false} ->
{type, :bitstring, context}
end
else
{_type, context} =
Apply.remote_apply(info, Collectable, :into, [type], expr, stack, context)
{[], dynamic(), term(), context}
{dynamic(), :none, context}
end
end
@@ -1068,7 +1046,7 @@ defmodule Module.Types.Expr do
do: [{args, return}]
defp literal_map_update(descr, key_descr, value_descr) do
case map_update(descr, key_descr, value_descr, false, false, false) do
case map_update(descr, key_descr, value_descr, false, false) do
{_type, descr, []} -> {:ok, descr}
{_, _, [error | _]} -> error
:badmap -> :badmap
+21 -36
View File
@@ -265,7 +265,7 @@ defmodule Module.Types.Of do
{Function, fun()},
{Integer, integer()},
{List, opt_union(empty_list(), non_empty_list(term(), term()))},
{Map, open_map(__struct__: {Module.Types.Descr.opt_negation(atom()), true})},
{Map, open_map(__struct__: if_set(Module.Types.Descr.opt_negation(atom())))},
{Port, port()},
{PID, pid()},
{Reference, reference()},
@@ -294,7 +294,7 @@ defmodule Module.Types.Of do
if info = mode == :closed && Code.ensure_loaded?(struct) && struct.__info__(:struct) do
struct_type(struct, info)
else
open_map(__struct__: {atom([struct]), false})
open_map(__struct__: atom([struct]))
end
end
@@ -365,7 +365,7 @@ defmodule Module.Types.Of do
# Because a multiple key may override single keys, we can only
# collect single keys while there are no multiples.
[key] when multiple == [] ->
{dynamic?, domain, [{key, {value_type, false}} | single], multiple}
{dynamic?, domain, [{key, value_type} | single], multiple}
_ ->
{dynamic?, domain, single, [{pos, value_type} | multiple]}
@@ -381,10 +381,8 @@ defmodule Module.Types.Of do
closed_map(non_multiple)
[{keys, type} | tail] ->
products = cartesian_map(tail)
for key <- keys, product <- products do
closed_map(non_multiple ++ [{key, {type, false}} | product])
for key <- keys, t <- cartesian_map(tail) do
closed_map(non_multiple ++ [{key, type} | t])
end
|> Enum.reduce(&opt_union/2)
end
@@ -393,11 +391,7 @@ defmodule Module.Types.Of do
end
defp union_negated([], new_type, single, multiple) do
single =
Enum.map(single, fn
{key, {old_type, optional?}} ->
{key, {opt_union(old_type, new_type), optional?}}
end)
single = Enum.map(single, fn {key, old_type} -> {key, opt_union(old_type, new_type)} end)
multiple =
Enum.map(multiple, fn {keys, old_type} -> {keys, opt_union(old_type, new_type)} end)
@@ -407,13 +401,12 @@ defmodule Module.Types.Of do
defp union_negated(negated, new_type, single, multiple) do
{single, matched} =
Enum.map_reduce(single, [], fn
{key, {old_type, optional?}}, matched ->
if key in negated do
{{key, {old_type, optional?}}, [key | matched]}
else
{{key, {opt_union(old_type, new_type), optional?}}, matched}
end
Enum.map_reduce(single, [], fn {key, old_type}, matched ->
if key in negated do
{{key, old_type}, [key | matched]}
else
{{key, opt_union(old_type, new_type)}, matched}
end
end)
multiple =
@@ -421,7 +414,7 @@ defmodule Module.Types.Of do
{keys, opt_union(old_type, new_type)}
end)
{Enum.map(negated -- matched, fn key -> {key, {none(), true}} end) ++ single, multiple}
{Enum.map(negated -- matched, fn key -> {key, not_set()} end) ++ single, multiple}
end
defp pairs(pairs, expected, stack, context, of_fun) do
@@ -430,7 +423,7 @@ defmodule Module.Types.Of do
expected_value_type =
with {[key], [], []} <- pos_neg_domain,
{_optional?, expected_value_type} <- map_fetch_key(expected, key) do
{_, expected_value_type} <- map_fetch_key(expected, key) do
expected_value_type
else
_ -> term()
@@ -465,8 +458,7 @@ defmodule Module.Types.Of do
[[]]
[{keys, type} | tail] ->
products = cartesian_map(tail)
for key <- keys, product <- products, do: [{key, {type, false}} | product]
for key <- keys, t <- cartesian_map(tail), do: [{key, type} | t]
end
end
@@ -488,15 +480,15 @@ defmodule Module.Types.Of do
Enum.map_reduce(args, context, fn {key, value}, context when is_atom(key) ->
value_type =
case map_fetch_key(expected, key) do
{_optional?, expected_value_type} -> expected_value_type
{_, expected_value_type} -> expected_value_type
_ -> term()
end
{type, context} = of_fun.(value, value_type, stack, context)
{{key, {type, false}}, context}
{{key, type}, context}
end)
{closed_map([__struct__: {atom([struct]), false}] ++ args_types), context}
{closed_map([{:__struct__, atom([struct])} | args_types]), context}
end
end
@@ -535,16 +527,9 @@ defmodule Module.Types.Of do
# we introduce typed structs. They are only used by exceptions.
def struct_type(struct, info, args_types \\ []) do
term = dynamic()
pairs = for %{field: field} <- info, do: {field, {term, false}}
pairs = [{:__struct__, {atom([struct]), false}} | pairs]
pairs =
if args_types == [] do
pairs
else
pairs ++ args_types
end
pairs = for %{field: field} <- info, do: {field, term}
pairs = [{:__struct__, atom([struct])} | pairs]
pairs = if args_types == [], do: pairs, else: pairs ++ args_types
closed_map(pairs)
end
+11 -21
View File
@@ -534,18 +534,14 @@ defmodule Module.Types.Pattern do
defp of_pattern_tree({:open_map, static, dynamic}, stack, context) do
dynamic =
Enum.map(dynamic, fn {key, value} ->
{key, {of_pattern_tree(value, stack, context), false}}
end)
Enum.map(dynamic, fn {key, value} -> {key, of_pattern_tree(value, stack, context)} end)
open_map(static ++ dynamic)
end
defp of_pattern_tree({:closed_map, static, dynamic}, stack, context) do
dynamic =
Enum.map(dynamic, fn {key, value} ->
{key, {of_pattern_tree(value, stack, context), false}}
end)
Enum.map(dynamic, fn {key, value} -> {key, of_pattern_tree(value, stack, context)} end)
closed_map(static ++ dynamic)
end
@@ -725,20 +721,20 @@ defmodule Module.Types.Pattern do
pairs = Map.new(pairs)
term = term()
static = [__struct__: {atom([struct]), false}]
static = [__struct__: atom([struct])]
dynamic = []
{static, dynamic} =
Enum.reduce(info, {static, dynamic}, fn %{field: field}, {static, dynamic} ->
case pairs do
%{^field => value_type} when is_descr(value_type) ->
{[{field, {value_type, false}} | static], dynamic}
{[{field, value_type} | static], dynamic}
%{^field => value_type} ->
{static, [{field, value_type} | dynamic]}
_ ->
{[{field, {term, false}} | static], dynamic}
{[{field, term} | static], dynamic}
end
end)
@@ -776,9 +772,7 @@ defmodule Module.Types.Pattern do
{refined, context} = of_match_var(var, atom(), expr, stack, context)
if compatible?(refined, atom()) do
fields = [__struct__: {refined, false}]
precise? = match?({:_, _, _}, var) or singleton?(refined)
of_open_map(args, precise?, fields, [], path, stack, context)
of_open_map(args, singleton?(refined), [__struct__: refined], [], path, stack, context)
else
error = {:badstruct, refined, expr, context}
{error_type(), false, error(__MODULE__, error, meta, stack, context)}
@@ -812,12 +806,8 @@ defmodule Module.Types.Pattern do
version = Keyword.fetch!(meta, :version)
case Enum.reverse(reverse_path) do
[%{root: :key} | _] ->
{Of.var(var, context), false, context}
path ->
precise? = singleton?(context.vars[version].type)
{{:var, version}, precise?, of_shared_var(var, version, true, path, context)}
[%{root: :key} | _] -> {Of.var(var, context), false, context}
path -> {{:var, version}, false, of_shared_var(var, version, true, path, context)}
end
end
@@ -871,7 +861,7 @@ defmodule Module.Types.Pattern do
precise? = precise? and value_precise?
if is_descr(value_type) do
{precise?, [{key, {value_type, false}} | static], dynamic, context}
{precise?, [{key, value_type} | static], dynamic, context}
else
{precise?, static, [{key, value_type} | dynamic], context}
end
@@ -1175,7 +1165,7 @@ defmodule Module.Types.Pattern do
# var.field
def of_guard({{:., _, [callee, key]}, _, []} = map_fetch, expected, expr, stack, context)
when not is_atom(callee) do
{type, context} = of_guard(callee, open_map([{key, {expected, false}}]), expr, stack, context)
{type, context} = of_guard(callee, open_map([{key, expected}]), expr, stack, context)
Of.map_fetch(map_fetch, type, key, stack, context)
end
@@ -1402,7 +1392,7 @@ defmodule Module.Types.Pattern do
Apply.remote(:erlang, fun, args, expected, call, stack, context, &of_guard/5)
end
defp unpack_op({{:., _, [_mod, fun]}, _, [left, right]}, fun, acc) do
defp unpack_op({{:., _, [:erlang, fun]}, _, [left, right]}, fun, acc) do
unpack_op(left, fun, unpack_op(right, fun, acc))
end
+4 -2
View File
@@ -39,7 +39,7 @@ defmodule Node do
Currently supported options are:
* `:name_domain` - determines the host name part of the node name. If `:longnames`,
fully qualified domain names will be used, which is also the default.
fully qualified domain names will be used which also is the default.
If `:shortnames`, only the short name of the host will be used.
* `:net_ticktime` - The tick time to use in seconds. Defaults to the value of the
@@ -47,7 +47,7 @@ defmodule Node do
See [the `kernel` documentation](https://www.erlang.org/doc/apps/kernel/kernel_app.html)
for more information.
* `:net_tickintensity` - The tick intensity to use. Defaults to the value of the
* `net_tickintensity` - The tick intensity to use. Defaults to the value of the
`net_tickintensity` configuration under Erlang's `kernel` application.
See [the `kernel` documentation](https://www.erlang.org/doc/apps/kernel/kernel_app.html)
for more information.
@@ -338,6 +338,8 @@ defmodule Node do
The default node is `Node.self/0`, the local node. If `node` is the local node,
the function also sets the cookie of all other unknown nodes to `cookie`.
This function will raise `FunctionClauseError` if the given `node` is not alive.
"""
@spec set_cookie(t, atom) :: true
def set_cookie(node \\ Node.self(), cookie) when is_atom(cookie) do
+3 -3
View File
@@ -43,7 +43,7 @@ defmodule OptionParser do
defmodule ParseError do
@moduledoc """
An exception raised when parsing an option fails.
An exception raised when parsing option fails.
For example, see `OptionParser.parse!/2`.
"""
@@ -387,7 +387,7 @@ defmodule OptionParser do
do_parse(rest, config, new_opts, args, invalid, all?)
{:invalid, option, value, rest} ->
# the option exists but it has wrong value
# the option exist but it has wrong value
do_parse(rest, config, opts, args, [{option, value} | invalid], all?)
{:undefined, option, _value, rest} ->
@@ -463,7 +463,7 @@ defmodule OptionParser do
defp next_with_config(["--" <> option | rest], config) do
{option, value} = split_option(option)
if String.contains?(option, "_") do
if String.contains?(option, ["_"]) do
{:undefined, "--" <> option, value, rest}
else
tagged = tag_option(option, config)
-26
View File
@@ -699,32 +699,6 @@ defmodule Path do
end
end
@doc """
Safely joins two paths.
Returns `{:ok, path}` if `right` is safe to append to `left`, or `:error`
otherwise. See `safe_relative/2` for the exact safety rules applied to `right`.
## Examples
iex> Path.safe_join("foo", "bar")
{:ok, "foo/bar"}
iex> Path.safe_join("foo", "../bar")
:error
iex> Path.safe_join("foo", "/bar")
:error
"""
@doc since: "1.21.0"
@spec safe_join(t, t) :: {:ok, t} | :error
def safe_join(left, right) do
with {:ok, right} <- safe_relative(right, left) do
{:ok, join(left, right)}
end
end
@doc ~S"""
Splits the path into a list at the path separator.
+6 -6
View File
@@ -61,7 +61,7 @@ defmodule Port do
* `{pid, {:connect, new_pid}}` - sets the `new_pid` as the new owner of
the port. Once a port is opened, the port is linked and connected to the
caller process and communication to the port only happens through the
connected process. This message makes `new_pid` the new connected process.
connected process. This message makes `new_pid` the new connected processes.
Unless the port is dead, the port will reply to the old owner with
`{port, :connected}`. See `connect/2`.
@@ -88,10 +88,10 @@ defmodule Port do
> On Unix systems, arguments are passed to a new operating system
> process as an array of strings but on Windows it is up to the child
> process to parse them and some Windows programs may apply their own
> rules, which are inconsistent with the standard C runtime `argv` parsing.
> rules, which are inconsistent with the standard C runtime `argv` parsing
>
> This is particularly troublesome when invoking `.bat`, `.cmd`, or `.com`
> files as these run implicitly through `cmd.exe`, whose argument parsing is
> This is particularly troublesome when invoking `.bat` or `.com` files
> as these run implicitly through `cmd.exe`, whose argument parsing is
> vulnerable to malicious input and can be used to run arbitrary shell
> commands.
>
@@ -99,7 +99,7 @@ defmodule Port do
> files or `.com` applications, you must not pass untrusted input as
> arguments to the program. You may avoid accidentally executing them
> by explicitly passing the extension of the program you want to run,
> such as `.exe`, and double-checking the program is indeed not a batch
> such as `.exe`, and double check the program is indeed not a batch
> file or `.com` application.
>
> This affects both `spawn` and `spawn_executable`.
@@ -156,7 +156,7 @@ defmodule Port do
We do not always have control over how third-party software terminates.
If necessary, one workaround is to wrap the child application in a script that
checks whether stdin has been closed. Here is such a script that has been
checks whether stdin has been closed. Here is such a script that has been
verified to work on bash shells:
#!/usr/bin/env bash
+6 -11
View File
@@ -79,11 +79,10 @@ defmodule Process do
@typedoc """
A process destination.
A remote or local PID, a local port, a process alias, a locally registered name,
or a tuple in the form of `{registered_name, node}` for a registered name at
another node.
A remote or local PID, a local port, a locally registered name, or a tuple in
the form of `{registered_name, node}` for a registered name at another node.
"""
@type dest :: pid | port | alias | (registered_name :: atom) | {registered_name :: atom, node}
@type dest :: pid | port | (registered_name :: atom) | {registered_name :: atom, node}
@doc """
Tells whether the given process is alive on the local node.
@@ -227,7 +226,7 @@ defmodule Process do
>
> The functions `Kernel.exit/1` and `Process.exit/2` are
> named similarly but provide very different functionalities. The
> `Kernel.exit/1` function should be used when the intent is to stop the current
> `Kernel:exit/1` function should be used when the intent is to stop the current
> process while `Process.exit/2` should be used when the intent is to send an
> exit signal to another process. Note also that `Kernel.exit/1` can be caught
> with `try/1` while `Process.exit/2` can only be handled by trapping exits and
@@ -340,7 +339,7 @@ defmodule Process do
@doc """
Sends a message to the given `dest`.
`dest` may be a remote or local PID, a local port, a process alias, a locally
`dest` may be a remote or local PID, a local port, a locally
registered name, or a tuple in the form of `{registered_name, node}` for a
registered name at another node.
@@ -831,16 +830,12 @@ defmodule Process do
Inlined by the compiler.
"""
@spec flag(:async_dist, boolean) :: boolean
@spec flag(:error_handler, module) :: module
@spec flag(:fullsweep_after, non_neg_integer) :: non_neg_integer
@spec flag(:max_heap_size, heap_size) :: heap_size
# :off_heap | :on_heap twice because :erlang.message_queue_data() is not exported
@spec flag(:message_queue_data, :off_heap | :on_heap) :: :off_heap | :on_heap
@spec flag(:min_bin_vheap_size, non_neg_integer) :: non_neg_integer
@spec flag(:min_heap_size, non_neg_integer) :: non_neg_integer
@spec flag(:monitor_nodes, term) :: term
@spec flag({:monitor_nodes, term}, term) :: term
@spec flag(:priority, priority_level) :: priority_level
@spec flag(:save_calls, 0..10_000) :: 0..10_000
@spec flag(:sensitive, boolean) :: boolean
@@ -1007,7 +1002,7 @@ defmodule Process do
end
@doc """
Adds a descriptive term to the current process.
Add a descriptive term to the current process.
The term does not need to be unique, and will be shown in Observer and in
crash logs.
+2 -2
View File
@@ -215,7 +215,7 @@ defmodule Protocol do
...
end
If you are using `Mix.install/2`, you can do so by passing the `consolidate_protocols`
If you are using `Mix.install/2`, you can do by passing the `consolidate_protocols`
option:
Mix.install(
@@ -691,7 +691,7 @@ defmodule Protocol do
structs_domain =
case structs do
[] -> Descr.none()
_ -> Descr.open_map(__struct__: {Descr.atom(structs), false})
_ -> Descr.open_map(__struct__: Descr.atom(structs))
end
domain =
+9 -20
View File
@@ -258,7 +258,7 @@ defmodule Range do
def size(range)
def size(first..last//step) when step > 0 and first > last, do: 0
def size(first..last//step) when step < 0 and first < last, do: 0
def size(first..last//step), do: div(last - first, step) + 1
def size(first..last//step), do: abs(div(last - first, step)) + 1
# TODO: Remove me on v2.0
def size(%{__struct__: Range, first: first, last: last} = range) do
@@ -476,8 +476,8 @@ defmodule Range do
"""
@doc since: "1.8.0"
@spec disjoint?(t, t) :: boolean
def disjoint?(first1..last1//step1, first2..last2//step2) do
if empty?(first1, last1, step1) or empty?(first2, last2, step2) do
def disjoint?(first1..last1//step1 = range1, first2..last2//step2 = range2) do
if size(range1) == 0 or size(range2) == 0 do
true
else
{first1, last1, step1} = normalize(first1, last1, step1)
@@ -487,7 +487,7 @@ defmodule Range do
last2 < first1 or last1 < first2 ->
true
step1 == 1 and step2 == 1 ->
abs(step1) == 1 and abs(step2) == 1 ->
false
true ->
@@ -498,9 +498,9 @@ defmodule Range do
if rem(first2 - first1, gcd) == 0 do
c = first1 - first2 + step2 - step1
t1 = Integer.floor_div(-c * u, step2)
t2 = Integer.floor_div(-c * v, step1)
t = max(t1 + 1, t2 + 1)
t1 = -c / step2 * u
t2 = -c / step1 * v
t = max(floor(t1) + 1, floor(t2) + 1)
x = div(c * u + t * step2, gcd) - 1
y = div(c * v + t * step1, gcd) - 1
@@ -513,20 +513,9 @@ defmodule Range do
end
end
@compile inline: [empty?: 3, normalize: 3]
defp empty?(first, last, step) do
case step > 0 do
true -> first > last
false -> first < last
end
end
@compile inline: [normalize: 3]
defp normalize(first, last, step) when first > last,
do: {first - div(first - last, step) * step, first, -step}
# A single-element range holds the same element regardless of the step, so
# make the step positive to keep the progression in disjoint?/2 increasing.
defp normalize(first, last, step) when first == last, do: {first, last, abs(step)}
do: {first - abs(div(first - last, step) * step), first, -step}
defp normalize(first, last, step), do: {first, last, step}
+1 -1
View File
@@ -340,7 +340,7 @@ defmodule Record do
fields
end
# Normalizes record fields to have default values.
# Normalizes of record fields to have default values.
defp fields(kind, fields) do
normalizer_fun = fn
{key, value} when is_atom(key) ->
+2 -2
View File
@@ -615,8 +615,8 @@ defmodule Regex do
defp safe_run(%Regex{re_pattern: re_pattern} = regex, string, options) do
# TODO: Remove me when Erlang/OTP 28+ is required
# This allows regexes precompiled on Erlang/OTP 27- to work on Erlang/OTP 28+
with {:re_pattern, _, _, _, <<_::bitstring>>} <- re_pattern,
true <- :erlang.system_info(:otp_release) >= [?2, ?8] do
with true <- :erlang.system_info(:otp_release) >= [?2, ?8],
{:re_pattern, _, _, _, <<_::bitstring>>} <- re_pattern do
%Regex{source: source, opts: compile_opts} = regex
:re.run(string, source, compile_opts ++ options)
else
+18 -22
View File
@@ -22,7 +22,7 @@ defmodule Registry do
implementation. We explore some of those use cases below.
The registry may also be transparently partitioned with the `:partitions` option,
which provides more scalable behavior for running registries in highly concurrent
which provides more scalable behavior for running registries on highly concurrent
environments with thousands or millions of entries.
## Using in `:via`
@@ -79,7 +79,7 @@ defmodule Registry do
`Registry` has a dispatch mechanism that allows developers to implement custom
dispatch logic triggered from the caller. For example, let's say we have a
duplicate registry started as follows:
duplicate registry started as so:
{:ok, _} = Registry.start_link(keys: :duplicate, name: Registry.DispatcherTest)
@@ -172,7 +172,7 @@ defmodule Registry do
the cost of delayed unsubscription. For example, if a process crashes,
its keys are automatically removed from the registry but the change may
not propagate immediately. This means certain operations may return processes
that are already dead. When this can happen, it will be explicitly stated
that are already dead. When such may happen, it will be explicitly stated
in the function documentation.
However, keep in mind those cases are typically not an issue. After all, a
@@ -218,7 +218,7 @@ defmodule Registry do
@typedoc "A list of guards to be evaluated when matching on objects in a registry"
@type guards :: [guard]
@typedoc "A pattern used to represent the output format part of a match spec"
@typedoc "A pattern used to representing the output format part of a match spec"
@type body :: [term]
@typedoc "A full match spec used when selecting objects in the registry"
@@ -230,7 +230,6 @@ defmodule Registry do
| {:name, registry}
| {:partitions, pos_integer}
| {:listeners, [atom]}
| {:compressed, boolean}
| {:meta, [{meta_key, meta_value}]}
@typedoc """
@@ -240,8 +239,8 @@ defmodule Registry do
"""
@typedoc since: "1.15.0"
@type listener_message ::
{:register, registry, key, pid, value}
| {:unregister, registry, key, pid}
{:register, registry, key, registry_partition :: pid, value}
| {:unregister, registry, key, registry_partition :: pid}
@typedoc """
Options used for `dispatch/4`.
@@ -347,7 +346,7 @@ defmodule Registry do
The registry requires the following keys:
* `:keys` - controls whether keys are `:unique`, `:duplicate`,
* `:keys` - chooses if keys are `:unique`, `:duplicate`,
`{:duplicate, :key}`, or `{:duplicate, :pid}`
* `:name` - the name of the registry and its tables
@@ -358,8 +357,6 @@ defmodule Registry do
and unregister events. The registered process must be monitored by the
listener if the listener wants to be notified if the registered process
crashes. Messages sent to listeners are of type `t:listener_message/0`.
* `:compressed` - turns on compression for registry's keys ETS tables, reducing
memory usage at the cost of additional CPU usage. Defaults to `false`.
* `:meta` - a keyword list of metadata to be attached to the registry.
For `:duplicate` registries, you can specify the partitioning strategy
@@ -639,7 +636,7 @@ defmodule Registry do
@doc """
Finds the `{pid, value}` pair for the given `key` in `registry` in no particular order.
Returns an empty list if there is no match.
An empty list if there is no match.
For unique registries, a single partition lookup is necessary. For
duplicate registries, all partitions must be looked up.
@@ -737,7 +734,7 @@ defmodule Registry do
This function is useful only when spawning processes is not an option,
for example, when copying the data to another process could be too
expensive, or when the work must be done within the current process
expensive. Or when the work must be done within the current process
for other reasons. In such cases, this function provides a scalable
mechanism for managing locks on top of the registry's infrastructure.
@@ -751,7 +748,6 @@ defmodule Registry do
"""
@doc since: "1.18.0"
@spec lock(registry, term, (-> result)) :: result when result: var
def lock(registry, lock_key, function)
when is_atom(registry) and is_function(function, 0) do
{_kind, partitions, _, pid_ets, _} = info!(registry)
@@ -772,7 +768,7 @@ defmodule Registry do
For example the `$1 > 1` guard condition would be expressed as the `{:>, :"$1", 1}` tuple.
Please note that guard conditions will work only for assigned
variables like `:"$1"`, `:"$2"`, and so forth.
Avoid using of special match variables `:"$_"` and `:"$$"`, because they might not work as expected.
Avoid usage of special match variables `:"$_"` and `:"$$"`, because it might not work as expected.
An empty list will be returned if there is no match.
@@ -842,7 +838,7 @@ defmodule Registry do
iex> Registry.keys(Registry.UniqueKeysTest, self())
["hello"]
This is possible for duplicate registries though:
Such is possible for duplicate registries though:
iex> Registry.start_link(keys: :duplicate, name: Registry.DuplicateKeysTest)
iex> Registry.keys(Registry.DuplicateKeysTest, self())
@@ -1464,12 +1460,12 @@ defmodule Registry do
end
@doc """
Selects key, pid, and values registered using full match specs.
Select key, pid, and values registered using full match specs.
The `spec` consists of a list of three-part tuples, in the shape of `[{match_pattern, guards, body}]`.
The `spec` consists of a list of three part tuples, in the shape of `[{match_pattern, guards, body}]`.
The first part, the match pattern, must be a tuple that will match the structure of the
data stored in the registry, which is `{key, pid, value}`. The atom `:_` can be used to
the data stored in the registry, which is `{key, pid, value}`. The atom `:_` can be used to
ignore a given value or tuple element, while the atom `:"$1"` can be used to temporarily
assign part of pattern to a variable for a subsequent comparison. This can be combined
like `{:"$1", :_, :_}`.
@@ -1481,7 +1477,7 @@ defmodule Registry do
variables like `:"$1"`, `:"$2"`, and so forth.
The third part, the body, is a list of shapes of the returned entries. Like guards, you have access to
assigned variables like `:"$1"`, which you can combine with hard-coded values to freely shape entries.
assigned variables like `:"$1"`, which you can combine with hard-coded values to freely shape entries
Note that tuples have to be wrapped in an additional tuple. To get a result format like
`%{key: key, pid: pid, value: value}`, assuming you bound those variables in order in the match part,
you would provide a body like `[%{key: :"$1", pid: :"$2", value: :"$3"}]`. Like guards, you can use
@@ -1823,7 +1819,7 @@ defmodule Registry.Partition do
@key_info -2
@doc """
Returns the name of the key partition table.
Returns the name of key partition table.
"""
@spec key_name(atom, non_neg_integer) :: atom
def key_name(registry, partition) do
@@ -1831,7 +1827,7 @@ defmodule Registry.Partition do
end
@doc """
Returns the name of the pid partition table.
Returns the name of pid partition table.
"""
@spec pid_name(atom, non_neg_integer) :: atom
def pid_name(name, partition) do
@@ -1848,7 +1844,7 @@ defmodule Registry.Partition do
end
@doc """
Runs the function with a lock.
Runs function with a lock.
"""
def lock(pid, key, lock) do
ref = GenServer.call(pid, {:lock, key})
+30 -79
View File
@@ -234,12 +234,12 @@ defmodule String do
to the definition of the encoding) is encountered, only one
code point needs to be rejected.
Most functions in this module perform self-synchronization too.
For example, `downcase/1` will return a downcased string, with
any invalid codepoints preserved at their location.
This module relies on this behavior to ignore such invalid
characters. For example, `length/1` will return
a correct result even if an invalid code point is fed into it.
In other words, this module expects invalid data to be detected
at the boundary, typically when retrieving data from the external source.
elsewhere, usually when retrieving data from the external source.
For example, a driver that reads strings from a database will be
responsible to check the validity of the encoding. `String.chunk/2`
can be used for breaking a string into valid and invalid parts.
@@ -935,28 +935,18 @@ defmodule String do
end
def upcase(string, :ascii) when is_binary(string) do
upcase_ascii(string, string, 0)
IO.iodata_to_binary(upcase_ascii(string))
end
def upcase(string, mode) when is_binary(string) and mode in @conditional_mappings do
String.Unicode.upcase(string, [], mode)
end
defp upcase_ascii(<<char, rest::bits>>, original, skip) when char >= ?a and char <= ?z do
prefix = binary_part(original, 0, skip)
upcase_ascii(rest, <<prefix::binary, char - 32>>)
end
defp upcase_ascii(<<char, rest::bits>>) when char >= ?a and char <= ?z,
do: [char - 32 | upcase_ascii(rest)]
defp upcase_ascii(<<_char, rest::bits>>, original, skip),
do: upcase_ascii(rest, original, skip + 1)
defp upcase_ascii(<<>>, original, _skip), do: original
defp upcase_ascii(<<char, rest::bits>>, acc) when char >= ?a and char <= ?z,
do: upcase_ascii(rest, <<acc::binary, char - 32>>)
defp upcase_ascii(<<char, rest::bits>>, acc), do: upcase_ascii(rest, <<acc::binary, char>>)
defp upcase_ascii(<<>>, acc), do: acc
defp upcase_ascii(<<char, rest::bits>>), do: [char | upcase_ascii(rest)]
defp upcase_ascii(<<>>), do: []
@doc """
Converts all characters in the given string to lowercase according to `mode`.
@@ -1015,32 +1005,18 @@ defmodule String do
end
def downcase(string, :ascii) when is_binary(string) do
downcase_ascii(string, string, 0)
end
def downcase(string, :greek) when is_binary(string) do
String.Unicode.downcase_greek(string, [])
IO.iodata_to_binary(downcase_ascii(string))
end
def downcase(string, mode) when is_binary(string) and mode in @conditional_mappings do
String.Unicode.downcase(string, [], mode)
end
defp downcase_ascii(<<char, rest::bits>>, original, skip) when char >= ?A and char <= ?Z do
prefix = binary_part(original, 0, skip)
downcase_ascii(rest, <<prefix::binary, char + 32>>)
end
defp downcase_ascii(<<char, rest::bits>>) when char >= ?A and char <= ?Z,
do: [char + 32 | downcase_ascii(rest)]
defp downcase_ascii(<<_char, rest::bits>>, original, skip),
do: downcase_ascii(rest, original, skip + 1)
defp downcase_ascii(<<>>, original, _skip), do: original
defp downcase_ascii(<<char, rest::bits>>, acc) when char >= ?A and char <= ?Z,
do: downcase_ascii(rest, <<acc::binary, char + 32>>)
defp downcase_ascii(<<char, rest::bits>>, acc), do: downcase_ascii(rest, <<acc::binary, char>>)
defp downcase_ascii(<<>>, acc), do: acc
defp downcase_ascii(<<char, rest::bits>>), do: [char | downcase_ascii(rest)]
defp downcase_ascii(<<>>), do: []
@doc """
Converts the first character in the given string to
@@ -1082,39 +1058,19 @@ defmodule String do
@letter_I_dot_above <<0x0130::utf8>>
def capitalize(<<@letter_i, right::binary>>, mode) do
capitalized = if mode == :turkic, do: @letter_I_dot_above, else: @letter_I
capitalized <> downcase_after_capitalize(right, mode, ?i)
if(mode == :turkic, do: @letter_I_dot_above, else: @letter_I) <> downcase(right, mode)
end
def capitalize(string, mode) when is_binary(string) do
case :unicode_util.gc(string) do
[gc] ->
grapheme_to_binary(:string.titlecase([gc]))
[gc, rest] ->
grapheme_to_binary(:string.titlecase([gc])) <>
downcase_after_capitalize(rest, mode, gc)
[gc | rest] ->
grapheme_to_binary(:string.titlecase([gc])) <>
downcase_after_capitalize(rest, mode, gc)
[] ->
""
{:error, <<byte, rest::bits>>} ->
<<byte>> <> downcase(rest, mode)
[gc] -> grapheme_to_binary(:string.titlecase([gc]))
[gc, rest] -> grapheme_to_binary(:string.titlecase([gc])) <> downcase(rest, mode)
[gc | rest] -> grapheme_to_binary(:string.titlecase([gc])) <> downcase(rest, mode)
[] -> ""
{:error, <<byte, rest::bits>>} -> <<byte>> <> downcase(rest, mode)
end
end
defp downcase_after_capitalize(rest, :greek, gc) do
String.Unicode.downcase_greek(rest, [], grapheme_to_binary(gc))
end
defp downcase_after_capitalize(rest, mode, _gc) do
downcase(rest, mode)
end
@doc false
@deprecated "Use String.trim_trailing/1 instead"
defdelegate rstrip(binary), to: String.Break, as: :trim_trailing
@@ -1126,7 +1082,7 @@ defmodule String do
end
@doc """
Replaces all leading occurrences of `match` by `replacement` in `string`.
Replaces all leading occurrences of `match` by `replacement` of `match` in `string`.
Returns the string untouched if there are no occurrences.
@@ -1770,7 +1726,7 @@ defmodule String do
end
defp do_replace(subject, [], _, n) do
binary_part(subject, n, byte_size(subject) - n)
[binary_part(subject, n, byte_size(subject) - n)]
end
defp do_replace(subject, [{start, length} | matches], replacement, n) do
@@ -1828,7 +1784,7 @@ defmodule String do
do: :unicode.characters_to_binary(acc)
defp do_reverse({:error, <<byte, rest::bits>>}, acc),
do: do_reverse(:unicode_util.gc(rest), []) <> <<byte>> <> :unicode.characters_to_binary(acc)
do: :unicode.characters_to_binary(acc) <> <<byte>> <> do_reverse(:unicode_util.gc(rest), [])
@doc """
Returns a string `subject` repeated `n` times.
@@ -2454,7 +2410,7 @@ defmodule String do
""
"""
@spec slice(t, integer, non_neg_integer) :: t
@spec slice(t, integer, non_neg_integer) :: grapheme
def slice(_, _, 0) do
""
@@ -3015,17 +2971,15 @@ defmodule String do
@doc """
Converts a string to an existing atom or creates a new one.
> #### Dynamic Atom Creation {: .warning}
>
> This function creates atoms dynamically and atoms are
> not garbage-collected. Therefore, `string` should not be an
> untrusted value, such as input received from a socket or during
> a web request. Consider using `to_existing_atom/1` instead.
Warning: this function creates atoms dynamically and atoms are
not garbage-collected. Therefore, `string` should not be an
untrusted value, such as input received from a socket or during
a web request. Consider using `to_existing_atom/1` instead.
By default, the maximum number of atoms is `1_048_576`. This limit
can be raised or lowered using the VM option `+t`.
The maximum atom size is 255 Unicode code points.
The maximum atom size is of 255 Unicode code points.
Inlined by the compiler.
@@ -3047,7 +3001,7 @@ defmodule String do
If the list of expected atoms is known upfront, prefer `to_existing_atom/2`.
The maximum atom size is 255 Unicode code points.
The maximum atom size is of 255 Unicode code points.
Raises an `ArgumentError` if the atom does not exist.
Inlined by the compiler.
@@ -3214,13 +3168,10 @@ defmodule String do
0.75
iex> String.bag_distance("abcd", "abcd")
1.0
iex> String.bag_distance("", "")
1.0
"""
@spec bag_distance(t, t) :: float
@doc since: "1.8.0"
def bag_distance("", ""), do: 1.0
def bag_distance(_string, ""), do: 0.0
def bag_distance("", _string), do: 0.0
+1 -1
View File
@@ -88,7 +88,7 @@ defmodule StringIO do
If options are provided, the result will be `{:ok, pid}`, returning the
IO device created. The option `:capture_prompt`, when set to `true`, causes
prompts (which are specified as arguments to `IO.get*` functions) to be
included in the device's output. See `open/3` for the list of supported
included in the device's output. See `options/3` for the list of supported
options.
If a function is provided, the device will be created and sent to the
+4 -4
View File
@@ -1026,10 +1026,10 @@ defmodule System do
> On Unix systems, arguments are passed to a new operating system
> process as an array of strings but on Windows it is up to the child
> process to parse them and some Windows programs may apply their own
> rules, which are inconsistent with the standard C runtime `argv` parsing.
> rules, which are inconsistent with the standard C runtime `argv` parsing
>
> This is particularly troublesome when invoking `.bat`, `.cmd`, or `.com`
> files as these run implicitly through `cmd.exe`, whose argument parsing is
> This is particularly troublesome when invoking `.bat` or `.com` files
> as these run implicitly through `cmd.exe`, whose argument parsing is
> vulnerable to malicious input and can be used to run arbitrary shell
> commands.
>
@@ -1037,7 +1037,7 @@ defmodule System do
> files or `.com` applications, you must not pass untrusted input as
> arguments to the program. You may avoid accidentally executing them
> by explicitly passing the extension of the program you want to run,
> such as `.exe`, and double-checking the program is indeed not a batch
> such as `.exe`, and double check the program is indeed not a batch
> file or `.com` application.
## Options
+1 -2
View File
@@ -1248,10 +1248,9 @@ defmodule Task do
{ref, nil}
end)
ref_count = map_size(refs)
on_timeout = Keyword.get(opts, :on_timeout, :nothing)
timeout = Keyword.get(opts, :timeout, 5_000)
limit = min(Keyword.get(opts, :limit, ref_count), ref_count)
limit = Keyword.get(opts, :limit, map_size(refs))
timeout_ref = make_ref()
timer_ref =
+8 -26
View File
@@ -123,8 +123,7 @@ defmodule Task.Supervised do
args: args,
reason: {log_value(kind, value), __STACKTRACE__},
# using :proc_lib over Process because we want the :undefined default, not nil
process_label: :proc_lib.get_label(self()),
orphan_compiler_module?: orphan_compiler_module?(kind, value)
process_label: :proc_lib.get_label(self())
}
},
%{
@@ -139,23 +138,6 @@ defmodule Task.Supervised do
end
end
defp orphan_compiler_module?(kind, reason) do
with :error <- kind,
:undef <- reason,
{:error_handler, :error_handler} <- :erlang.process_info(self(), :error_handler),
{:parent, parent} <- :erlang.process_info(self(), :parent),
{:error_handler, Kernel.ErrorHandler} <-
(try do
:erlang.process_info(parent, :error_handler)
rescue
_ -> nil
end) do
true
else
_ -> false
end
end
defp exit_reason(:error, reason, stacktrace), do: {reason, stacktrace}
defp exit_reason(:exit, reason, _stacktrace), do: reason
defp exit_reason(:throw, reason, stacktrace), do: {{:nocatch, reason}, stacktrace}
@@ -335,8 +317,8 @@ defmodule Task.Supervised do
# this response when the replying task dies (we'll see this in the :down
# message).
{{^monitor_ref, position}, reply} ->
%{^position => {:running, _element}} = waiting
waiting = Map.put(waiting, position, {:ok, reply})
%{^position => {pid, :running, _element}} = waiting
waiting = Map.put(waiting, position, {pid, {:ok, reply}})
stream_reduce({:cont, acc}, max, spawned, delivered, waiting, next, config)
# The task at position "position" died for some reason. We check if it
@@ -349,18 +331,18 @@ defmodule Task.Supervised do
case waiting do
# If the task replied, we don't care whether it went down for timeout
# or for normal reasons.
%{^position => {:ok, _} = ok} ->
%{^position => {_, {:ok, _} = ok}} ->
ok
# If the task exited by itself before replying, we emit {:exit, reason}.
%{^position => {:running, element}}
%{^position => {_, :running, element}}
when kind == :down ->
if zip_input_on_exit?, do: {:exit, {element, reason}}, else: {:exit, reason}
# If the task timed out before replying, we either exit (on_timeout: :exit)
# or emit {:exit, :timeout} (on_timeout: :kill_task) (note the task is already
# dead at this point).
%{^position => {:running, element}}
%{^position => {_, :running, element}}
when kind == :timed_out ->
if on_timeout == :exit do
stream_cleanup_inbox(monitor_pid, monitor_ref)
@@ -488,7 +470,7 @@ defmodule Task.Supervised do
end
end
# This function spawns a task for the given "value", and stores it as running
# This function spawns a task for the given "value", and puts the pid of this
# new task in the map of "waiting" tasks, which is returned.
defp stream_spawn(value, spawned, waiting, config) do
%{
@@ -507,7 +489,7 @@ defmodule Task.Supervised do
mfa_with_value = normalize_mfa_with_arg(mfa, value)
send(pid, {self(), {monitor_ref, spawned}, self(), callers, mfa_with_value})
stored_value = if zip_input_on_exit?, do: value, else: nil
Map.put(waiting, spawned, {:running, stored_value})
Map.put(waiting, spawned, {pid, :running, stored_value})
{:max_children, ^monitor_ref} ->
stream_close(config)
+6 -10
View File
@@ -132,12 +132,10 @@ defmodule Tuple do
"""
@doc since: "1.12.0"
@spec sum(tuple) :: number()
def sum(tuple), do: sum(tuple, 1, tuple_size(tuple) + 1, 0)
def sum(tuple), do: sum(tuple, tuple_size(tuple))
defp sum(_tuple, top, top, acc), do: acc
defp sum(tuple, index, top, acc),
do: sum(tuple, index + 1, top, acc + :erlang.element(index, tuple))
defp sum(_tuple, 0), do: 0
defp sum(tuple, index), do: :erlang.element(index, tuple) + sum(tuple, index - 1)
@doc """
Computes a product of tuple elements.
@@ -153,12 +151,10 @@ defmodule Tuple do
"""
@doc since: "1.12.0"
@spec product(tuple) :: number()
def product(tuple), do: product(tuple, 1, tuple_size(tuple) + 1, 1)
def product(tuple), do: product(tuple, tuple_size(tuple))
defp product(_tuple, top, top, acc), do: acc
defp product(tuple, index, top, acc),
do: product(tuple, index + 1, top, acc * :erlang.element(index, tuple))
defp product(_tuple, 0), do: 1
defp product(tuple, index), do: :erlang.element(index, tuple) * product(tuple, index - 1)
@doc """
Converts a tuple to a list.
+13 -33
View File
@@ -168,8 +168,8 @@ defmodule URI do
end
defp encode_kv_pair({key, value}, :rfc3986) do
encode_unreserved(Kernel.to_string(key), :percent) <>
"=" <> encode_unreserved(Kernel.to_string(value), :percent)
encode(Kernel.to_string(key), &char_unreserved?/1) <>
"=" <> encode(Kernel.to_string(value), &char_unreserved?/1)
end
defp encode_kv_pair({key, value}, :www_form) do
@@ -340,10 +340,6 @@ defmodule URI do
character in @reserved_characters
end
defguardp unreserved_char?(character)
when character in ?0..?9 or character in ?a..?z or character in ?A..?Z or
character in ~c"~_-."
@doc """
Checks if `character` is an unreserved one in a URI.
@@ -361,7 +357,7 @@ defmodule URI do
"""
@spec char_unreserved?(byte) :: boolean
def char_unreserved?(character) do
unreserved_char?(character)
character in ?0..?9 or character in ?a..?z or character in ?A..?Z or character in ~c"~_-."
end
@doc """
@@ -436,31 +432,15 @@ defmodule URI do
"""
@spec encode_www_form(binary) :: binary
def encode_www_form(<<>>), do: ""
def encode_www_form(string) when is_binary(string) do
encode_unreserved(string, "", :www_form)
for <<byte <- string>>, into: "" do
case percent(byte, &char_unreserved?/1) do
"%20" -> "+"
percent -> percent
end
end
end
# Matching upfront keeps the empty case from allocating the accumulator.
defp encode_unreserved(<<>>, _mode), do: ""
defp encode_unreserved(string, mode), do: encode_unreserved(string, "", mode)
defp encode_unreserved(<<?\s, rest::binary>>, acc, :www_form) do
encode_unreserved(rest, <<acc::binary, ?+>>, :www_form)
end
defp encode_unreserved(<<byte, rest::binary>>, acc, mode) when unreserved_char?(byte) do
encode_unreserved(rest, <<acc::binary, byte>>, mode)
end
defp encode_unreserved(<<byte, rest::binary>>, acc, mode) do
encode_unreserved(rest, <<acc::binary, ?%, hex(bsr(byte, 4)), hex(band(byte, 15))>>, mode)
end
defp encode_unreserved(<<>>, acc, _mode), do: acc
defp percent(char, predicate) do
if predicate.(char) do
<<char>>
@@ -1059,11 +1039,11 @@ defmodule URI do
%{uri | query: query}
end
def append_query(%URI{query: current} = uri, query) when is_binary(query) do
if String.ends_with?(current, "&") do
%{uri | query: current <> query}
def append_query(%URI{} = uri, query) when is_binary(query) do
if String.ends_with?(uri.query, "&") do
%{uri | query: uri.query <> query}
else
%{uri | query: current <> "&" <> query}
%{uri | query: uri.query <> "&" <> query}
end
end
+12 -26
View File
@@ -81,10 +81,8 @@ defmodule Version do
allowing us to express `~> 2.1` or `~> 2.1-dev`, something that wouldn't be allowed
when using the common comparison operators.
When the `:allow_pre` option is set `false` in `Version.match?/3`, a pre-release
version will not match the `~>`, `>`, and `>=` operators unless the operand is
itself a pre-release version. The `<` and `<=` operators are not affected by
`:allow_pre` and always match according to precedence.
When the `:allow_pre` option is set `false` in `Version.match?/3`, the requirement
will not match a pre-release version unless the operand is a pre-release version.
The default is to always allow pre-releases but note that in
Hex `:allow_pre` is set to `false`. See the table below for examples.
@@ -101,7 +99,6 @@ defmodule Version do
`>= 2.1.0` | `2.2.0-dev` | `true` | `true`
`>= 2.1.0` | `2.2.0-dev` | `false` | `false`
`>= 2.1.0-dev` | `2.2.6-dev` | `true` or `false` | `true`
`< 2.2.0` | `2.1.0-dev` | `true` or `false` | `true`
"""
@@ -285,9 +282,8 @@ defmodule Version do
## Options
* `:allow_pre` (boolean) - when `false`, pre-release versions will not match the
`~>`, `>`, and `>=` operators unless the operand is a pre-release version;
the `<` and `<=` operators are not affected. Defaults to `true`.
* `:allow_pre` (boolean) - when `false`, pre-release versions will not match
unless the operand is a pre-release version. Defaults to `true`.
For examples, please refer to the table above under the "Requirements" section.
## Examples
@@ -362,21 +358,11 @@ defmodule Version do
"""
@spec compare(version, version) :: :gt | :eq | :lt
def compare(
%Version{major: major1, minor: minor1, patch: patch1, pre: pre1},
%Version{major: major2, minor: minor2, patch: patch2, pre: pre2}
) do
do_compare(major1, minor1, patch1, pre1, major2, minor2, patch2, pre2)
end
def compare(version1, version2) do
{major1, minor1, patch1, pre1, _} = to_matchable(version1, true)
{major2, minor2, patch2, pre2, _} = to_matchable(version2, true)
do_compare(major1, minor1, patch1, pre1, major2, minor2, patch2, pre2)
do_compare(to_matchable(version1, true), to_matchable(version2, true))
end
defp do_compare(major1, minor1, patch1, pre1, major2, minor2, patch2, pre2) do
defp do_compare({major1, minor1, patch1, pre1, _}, {major2, minor2, patch2, pre2, _}) do
cond do
major1 > major2 -> :gt
major1 < major2 -> :lt
@@ -643,17 +629,17 @@ defmodule Version do
defp require_digits(string) do
if leading_zero?(string) or byte_size(string) > @max_numeric_component_digits,
do: :error,
else: parse_digits(string)
else: parse_digits(string, "")
end
defp leading_zero?(<<?0, _, _::binary>>), do: true
defp leading_zero?(_), do: false
defp parse_digits(<<>>), do: :error
defp parse_digits(<<char, rest::binary>>, acc) when char in ?0..?9,
do: parse_digits(rest, <<acc::binary, char>>)
defp parse_digits(string) do
if all_digits?(string), do: {:ok, :erlang.binary_to_integer(string)}, else: :error
end
defp parse_digits(<<>>, acc) when byte_size(acc) > 0, do: {:ok, String.to_integer(acc)}
defp parse_digits(_, _acc), do: :error
defp maybe_patch(patch, approximate?)
defp maybe_patch(nil, true), do: {:ok, nil}
@@ -677,7 +663,7 @@ defmodule Version do
end
defp convert_parts_to_integer([part | rest], acc) do
case parse_digits(part) do
case parse_digits(part, "") do
{:ok, integer} ->
if leading_zero?(part) do
:error
@@ -197,7 +197,7 @@ iex> MyRequestHandler.parse(%{"status" => "status_not_seen_anywhere", "message"
By explicitly listing all supported statuses, you guarantee that only a limited number of conversions may happen, and that all expected atoms already exist within the system.
An alternative is to use pattern matching and explicitly convert each string to its respective atom. This is useful when you need additional logic on different branches:
An alternative is to use pattern matching and explicitly convert each string to its respective atom. This is useful when you need additional logic on differnt branches:
```elixir
defmodule MyRequestHandler do
@@ -200,7 +200,7 @@ This anti-pattern is of special importance to library authors and whenever writi
* some frameworks, such as [Phoenix](https://phoenixframework.org), allow developers to raise exceptions in their code and uses a protocol to convert these exceptions into semantic HTTP responses
In cases where you have a complex algorithm and you must abort in the middle of a computation, you may use `throw` to abort and handle it with a `try/catch`, which was designed explicitly for this purpose. However, you must always catch what you throw, it must never be exposed as part of your library definition.
This anti-pattern was formerly known as [Using exceptions for control-flow](https://github.com/lucasvegi/Elixir-Code-Smells#using-exceptions-for-control-flow).
## Primitive obsession
@@ -380,11 +380,9 @@ If you really need to define modules dynamically, you can do so via meta-program
defmodule MyMacro do
defmacro call_examples(parts) do
for part <- parts do
module = Module.concat(OtherModule, part)
quote do
# This builds OtherModule.Foo at compile-time
unquote(module).example()
OtherModule.unquote(part).example()
end
end
end
@@ -144,7 +144,7 @@ iex> C.update(agent)
# state of shared information
iex> D.get(agent)
[:atom_value | %{a: 123}]
[:atom_value, %{a: 123}]
```
For a `GenServer` and other behaviours, this anti-pattern will manifest when scattering calls to `GenServer.call/3` and `GenServer.cast/2` throughout multiple modules, instead of encapsulating all the interaction with the `GenServer` in a single place.
@@ -279,7 +279,7 @@ defmodule Counter do
@doc "Bumps the value of the given counter."
def bump(name \\ __MODULE__, value) do
Agent.get_and_update(name, fn state -> {state, value + state} end)
Agent.get_and_update(fn state -> {state, value + state} end)
end
end
```
@@ -689,14 +689,14 @@ iex> Enum.map_join(cart, ", ", & &1.fruit)
"apple, banana, orange"
```
### [`intersperse(enum, separator)`](`Enum.intersperse/2`)
### [`intersperse(enum, separator \\ "")`](`Enum.intersperse/2`)
```elixir
iex> Enum.intersperse(["apple", "banana", "orange"], ", ")
["apple", ", ", "banana", ", ", "orange"]
```
### [`map_intersperse(enum, separator, mapper)`](`Enum.map_intersperse/3`)
### [`map_intersperse(enum, separator \\ "", mapper)`](`Enum.map_intersperse/3`)
```elixir
iex> Enum.map_intersperse(cart, ", ", & &1.fruit)
@@ -976,7 +976,7 @@ iex> Enum.chunk_every(cart, 2)
]
```
### [`chunk_every(enum, count, step, leftover \\ [])`](`Enum.chunk_every/4`)
### [`chunk_every(enum, count, step, leftover \\ [])`](`Enum.chunk_every/2`)
```elixir
iex> Enum.chunk_every(cart, 2, 2, [:elements, :to_complete])
@@ -1019,7 +1019,7 @@ iex> Enum.zip(fruits, counts)
See `Enum.zip/1` for zipping many collections at once.
### [`zip_with(enum1, enum2, fun)`](`Enum.zip_with/3`)
### [`zip_with(enum1, enum2, fun)`](`Enum.zip_with/2`)
```elixir
iex> fruits = ["apple", "banana", "orange"]
@@ -184,6 +184,8 @@ iex> :zlib.uncompress(compressed)
If you want to get deeper into Erlang, here's a list of online resources that cover Erlang's fundamentals and its more advanced features:
* This [Erlang Syntax: A Crash Course](https://elixir-lang.org/crash-course.html) provides a concise intro to Erlang's syntax. Each code snippet is accompanied by equivalent code in Elixir. This is an opportunity for you to not only get some exposure to Erlang's syntax but also review what you learned about Elixir.
* Erlang's official website has a short [tutorial](https://www.erlang.org/course). There is a chapter with pictures briefly describing Erlang's primitives for [concurrent programming](https://www.erlang.org/course/concurrent_programming.html).
* [Learn You Some Erlang for Great Good!](https://learnyousomeerlang.com/) is an excellent introduction to Erlang, its design principles, standard library, best practices, and much more. Once you have read through the crash course mentioned above, you'll be able to safely skip the first couple of chapters in the book that mostly deal with the syntax. When you reach [The Hitchhiker's Guide to Concurrency](https://learnyousomeerlang.com/the-hitchhikers-guide-to-concurrency) chapter, that's where the real fun starts.
@@ -29,7 +29,7 @@ For now, let's start by running `iex` (or `iex.bat` if you are on Windows PowerS
Open up `iex` and type the following expressions:
```elixir
Erlang/OTP 27 [64-bit] [smp:2:2] [...]
Erlang/OTP 26 [64-bit] [smp:2:2] [...]
Interactive Elixir - press Ctrl+C to exit
iex(1)> 40 + 2

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