Compare commits

...
66 Commits
Author SHA1 Message Date
José Valim 59bc1598a2 Fix string versioning 2023-05-30 11:19:39 +02:00
José Valim 46dd1f2a54 Fix mix do syntax 2023-05-30 11:13:37 +02:00
José Valim e884060f03 Fix zips paths 2023-05-30 11:09:07 +02:00
José Valim 80268ceee9 Add branch docs publishing 2023-05-30 11:03:30 +02:00
Bobby fb729784e5 Fix typo in the docs for "with" (#11958) 2022-07-03 11:36:25 +02:00
José Valim 7e4fbe657d Release v1.13.4 2022-04-07 08:08:02 +02:00
Wei Huang d52f533761 Make Registry.send work when value part is present (#11742)
Fix #11740.
2022-04-06 11:01:14 +02:00
José Valim e0c8b8af87 Manual stacktrace munging on eval is no longer necessary on Erlang/OTP 25 2022-03-25 12:02:35 +01:00
José Valim f950226e6c Add OTP-24.3 to CI (#11712) 2022-03-19 09:13:08 +01:00
Michał Łępicki 89e57882e1 Tweak type unification to fix infinite loop with recursive vars (#11664) 2022-02-28 14:47:31 +01:00
José Valim 67cf14c857 Add opening delimiter to sigil metadata/opts 2022-02-17 20:43:21 +01:00
José Valim fe43e23a25 Update local rebar3 2022-02-17 19:59:08 +01:00
Michał Łępicki 65681b83d7 Skip tests using Rebar2 on Erlang/OTP 25+ (#11643)
* Skip tests using Rebar2 on Erlang/OTP 25+

and clean up mix test_helper.exs exclude filters

* Remove unnecessary printing from mix test_helper.exs exclude filters
2022-02-17 18:36:33 +01:00
José Valim 63d682b3d7 Deprecate rebar2 2022-02-17 18:26:40 +01:00
Michał Łępicki c028c2d915 Recreate local.sample task fixture in test_helper.exs (#11640) 2022-02-17 16:44:23 +01:00
José Valim 4b480cb7e0 Allow iodata in sigil formatting functions 2022-02-17 14:57:20 +01:00
Michał Łępicki f809d03370 Update Mix.DepTest for Erlang/OTP 25 (#11636)
:digraph_utils.topsort/1 can return a different (but also valid)
topological ordering

The test failure on Erlang/OTP 25.0-rc1 was:

    1) test deps_paths (Mix.DepTest)
       test/mix/dep_test.exs:491
       Assertion with == failed
       code:  assert Enum.map(Mix.Dep.load_on_environment([]), & &1.app) == [:git_repo, :abc_repo, :deps_repo]
       left:  [:abc_repo, :git_repo, :deps_repo]
       right: [:git_repo, :abc_repo, :deps_repo]
       stacktrace:
         test/mix/dep_test.exs:499: anonymous fn/0 in Mix.DepTest."test deps_paths"/1
         (elixir 1.14.0-dev) lib/file.ex:1555: File.cd!/2
         test/test_helper.exs:127: MixTest.Case.in_fixture/3
         test/mix/dep_test.exs:31: Mix.DepTest.with_deps/2
         test/mix/dep_test.exs:497: (test)
2022-02-17 10:07:22 +01:00
Michał Łępicki ee6ed7558f Update Mix.TasksTestTest for Erlang/OTP 25 (#11637)
The order in which tests get executed can be different
depending on Erlang/OTP version

The test failure was:

    1) test logs and errors umbrella with file path (Mix.Tasks.TestTest)
       test/mix/tasks/test_test.exs:432
       Assertion with =~ failed
       code:  assert mix(["test", "apps/unknown_app/test"]) =~
                "==> bar\nPaths given to \"mix test\" did not match any directory/file: apps/unknown_app/test\n==> foo\nPaths given to \"mix test\" did not match any directory/file: apps/unknown_app/test\n"
       left:  "==> foo\nCompiling 1 file (.ex)\nGenerated foo app\n==> bar\nCompiling 1 file (.ex)\nGenerated bar app\n==> foo\nPaths given to \"mix test\" did not match any directory/file: apps/unknown_app/test\n==> bar\nPaths given to \"mix test\" did not match any directory/file: apps/unknown_app/test\n"
       right: "==> bar\nPaths given to \"mix test\" did not match any directory/file: apps/unknown_app/test\n==> foo\nPaths given to \"mix test\" did not match any directory/file: apps/unknown_app/test\n"
       stacktrace:
         test/mix/tasks/test_test.exs:436: anonymous fn/0 in Mix.Tasks.TestTest."test logs and errors umbrella with file path"/1
         (elixir 1.14.0-dev) lib/file.ex:1555: File.cd!/2
         test/test_helper.exs:127: MixTest.Case.in_fixture/3
         test/mix/tasks/test_test.exs:433: (test)
2022-02-17 10:06:54 +01:00
Kevin bb17204741 Only check for unwanted functions (#11632) 2022-02-17 09:05:59 +01:00
José Valim 1e4ed32eda @doc false URI.Error.message/1 2022-02-14 13:25:14 +01:00
José Valim bac5b1d800 Add compile-time dependencies on require
Projects like Plug use require to establish compile
time dependencies inside a Plug. The fact require
only added a compile-time dependency in v1.13.0 was
therefore a regression, addressed by this commit.
2022-02-14 12:41:00 +01:00
Dwi Prihandi 39d8675ac1 Fix typespec on Base.decode16! (#11526) 2022-02-13 20:27:30 +01:00
José Valim b63f8f541e Release v1.13.3 2022-02-09 16:26:17 +01:00
José Valim babbae7e47 Handle more unicode sequences in split_at, closes #11617 2022-02-09 15:53:39 +01:00
José Valim 0f0d4c0f6e Track all stale modules from config/lock as exports too 2022-02-04 16:50:37 +01:00
Steve Hall 5ee492af55 Fix coverage threshold ignored for Total line in Summary (#11571)
An incorrect variable is being passed to the `display/2` function on line 353 for the Total line in the coverage summary report. The result is that that Total line will always display in red no matter what the threshold is set to.
2022-02-03 13:22:32 +01:00
José Valim 25e480f3d0 Do not break signatures over multiple lines 2022-02-01 10:04:30 +01:00
José Valim 2dc6773d6c Fix recursion on guards with map fields, closes #11602 2022-01-31 14:58:39 +01:00
José Valim ff103b19b9 Fix indentation of options in mix format 2022-01-24 16:48:28 +01:00
Matt McCoy d99a16256b Supply file and line to formatter plugins (#11591)
Allow formatter plugins to print errors with accurate file and
line information. The line is only provided when a sigil is being
formatted.
2022-01-24 08:37:17 +01:00
José Valim 1601b309b8 Check for fun arity in formatter 2022-01-24 08:37:17 +01:00
Jonatan Männchen 421a77d508 Embedded Elixir Expressions in Formatter Plugins (#11587)
Support Sigils inside embedded expressions in files formatted
by a formatter plugin.
2022-01-22 09:36:02 +01:00
felipe stival cc9e9b29a7 Fix duplicate bindings causing weird behaviour (#11584)
Before this fix, evaluating `b = a` with assignments of: `a: 1, a: 2, c:
3` would eval AST equivalent to `^c = a`. This happened due to binding
normalization generating version numbers larger than the total number of
bindings. Later in the pipeline, the number of bindings is used to
compute the next version number, which would then conflict with an
existing binding.

This commit fixes that by not increasing the version number when
a repeated binding is normalized.
2022-01-19 19:38:23 +01:00
Eksperimental 67f7c6ba5b Fix bug with edge-case when converting to algebra quoted Elixir alias (#11572)
The following code would crash:

    iex> Code.quoted_to_algebra(Elixir)
    ** (MatchError) no match of right hand side value: "Elixir"
        (elixir 1.13.1) lib/code/normalizer.ex:254: Code.Normalizer.normalize_literal/3
        (elixir 1.13.1) lib/code.ex:1107: Code.quoted_to_algebra/2

Therefore, this as well:

    iex> Macro.to_string(Elixir)
    ** (MatchError) no match of right hand side value: "Elixir"
        (elixir 1.13.1) lib/code/normalizer.ex:254: Code.Normalizer.normalize_literal/3
        (elixir 1.13.1) lib/code.ex:1107: Code.quoted_to_algebra/2
        (elixir 1.13.1) lib/macro.ex:948: Macro.to_string/1
2022-01-16 09:28:25 +01:00
José Valim c99a17b8cd Optimize Map.new/2 2022-01-13 15:28:01 +01:00
José Valim 9ad182986c Release v1.13.2 2022-01-13 10:51:17 +01:00
José Valim 16292c65ba Update CHANGELOG 2022-01-12 14:43:11 +01:00
José Valim d25ce48d5d Bring back filter/reject on Map and Keyword
They are already being used in projects, which
means it is too late for a deprecation. The docs
instead discuss when to use them.
2022-01-12 14:29:13 +01:00
Aaron Gunderson 66b4ab19ae Mix.Tasks.Test.Coverage warns on failure (#11457)
Adds an explicit warning when exiting with an error code because of a
failed test coverage threshold.

Example:
```
-----------|--------------------------
    62.35% | Total

Coverage test failed, threshold not met:
        Coverage:   62.35%
        Threshold: 100.00%
```
2022-01-02 19:58:06 +01:00
Aleksei Matiushkin 01a4889e39 Fix the nested uniq: acc name clash by preserving a scope (#11532) 2022-01-01 09:56:42 +01:00
José Valim d5e7446399 Add missing Logger CHANGELOG entry 2021-12-31 11:12:22 +01:00
Thiago Santos ab1da111bf Warn on zero arity callbacks inside protocols (#11519) 2021-12-22 22:01:33 +01:00
José Valim 6549b00eba Ensure context modules are handled in optimized defmodule 2021-12-22 12:22:22 +01:00
José Valim 4431ac63e2 Do not use type exclusive to Erlang/OTP 24+ 2021-12-22 10:18:02 +01:00
Thales Macedo Garitezi 280215c24f Allow bypassing application mode validation in release spec (#11506)
Today, there is a mode validation check when doing a Mix Release that
prevents a parent application that has an application mode of
`:permanent`, for example, while a child application has mode `:load`,
as it might be unsafe.

However, some complex applications may need more control over the
application load/start order.  For such cases, the user would need a
way to tell Mix.Release to don't be strict while constructing the
`.rel` file.

To allow for better control over the mode validation check instead of
simply disabling the check completely, we introduce the
`:skip_mode_validation_for` to allow users to specify a list of
applications for which the strict application mode validations should
not be enforced.
2021-12-20 12:48:11 +01:00
Marc-André Lafortune 071285403d Check plugins first so they can format .ex and .exs files (#11507) 2021-12-20 08:51:59 +01:00
José Valim 3d3453c1e2 Do not emit warnings on Cursor.Fragment.container_cursor_to_quoted/2 2021-12-16 16:44:10 +01:00
José Valim 2dc3fd26b8 Update CHANGELOG 2021-12-16 15:39:52 +01:00
José Valim e61ed47066 Deprecate map/filter/reject in Map and Keyword 2021-12-16 15:33:41 +01:00
José Valim 33f9d04851 Release v1.13.1 2021-12-14 23:31:53 +01:00
José Valim 8804ff5488 Add a note about chaining multiple map+filter calls 2021-12-14 23:12:01 +01:00
José Valim 7e66a4c159 Add pattern to Code.Fragment.surround_context/3 2021-12-14 23:12:01 +01:00
José Valim b085ad259d Improve DateTime docs 2021-12-14 12:21:39 +01:00
José Valim 8afac93a05 Ensure async streams can be consumed from a separate process 2021-12-13 15:49:44 +01:00
Dorgan b725b8c055 Set a max line_length for Macro.to_string (#11471) 2021-12-11 10:45:32 +01:00
felipe stival 47171d934d Change approach: never show error if line is empty (#11466) 2021-12-10 09:28:47 +01:00
José Valim edafcc4e9a Handle improper lists on apply, closes #11465 2021-12-09 21:22:59 +01:00
Dorgan 233fc09e67 Fix formatting of lists in module attribues (#11462) 2021-12-08 23:51:40 +01:00
José Valim 31e24b3a12 Fix codepoint byte counting in slice, closes #11461 2021-12-08 23:50:48 +01:00
José Valim d3b38fb472 Fix halt for --version 2021-12-07 08:35:21 +01:00
José Valim 2efc1e5cac Make sure --version flag halts elixir and iex, closes #11453 2021-12-07 08:24:01 +01:00
José Valim a0b77bd884 Do not deprecate URI.parse/1
Closes #11450.
2021-12-06 14:59:48 +01:00
Parker Selbert cbba61a386 Specific base typespecs (#11449)
* Use specific options for base option typespecs

Each function had `keyword` as the option type, which didn't help guard
against typos or mismatched options.

* Fix padding use in encode/decode identity test
2021-12-05 19:50:33 +01:00
José Valim ebb347a34e Make protocol consolidation part of the Mix.install cache 2021-12-05 19:29:56 +01:00
Wojtek Mach 713633a939 Add missing @doc since (#11443) 2021-12-04 19:57:44 +01:00
Jason Axelson d239d9991a Update wording for 1.13 get_in update (#11442)
Make it more clear what the behavior change is.

Relates to this change: https://github.com/elixir-lang/elixir/commit/587c80eae6e29002370523f79273d56dabd48582#diff-43a76a6112c9082ee3ddb63acc2419db8764edd039cb52e29e436734b414574b
Related ElixirForum post: https://elixirforum.com/t/elixir-version-of-a-safe-navigation-operator-navigating-nil-in-maps-structs/6023/29
2021-12-04 19:31:34 +01:00
90 changed files with 1268 additions and 388 deletions
+87
View File
@@ -0,0 +1,87 @@
name: builds.hex.pm
on:
push:
branches:
- main
- v*.*
tags:
- v*
env:
ELIXIR_OPTS: "--warnings-as-errors"
ERLC_OPTS: "warnings_as_errors"
LANG: C.UTF-8
concurrency: builds_txt
jobs:
release_pre_built:
strategy:
fail-fast: true
max-parallel: 1
matrix:
include:
- otp: 23
otp_version: '23.3'
build_docs: build_docs
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 50
- name: Get tags
run: git fetch --tags origin
- uses: ./.github/workflows/release_pre_built
with:
otp_version: ${{ matrix.otp_version }}
otp: ${{ matrix.otp }}
build_docs: ${{ matrix.build_docs }}
- name: Utils.sh
run: |
cat << 'EOF' > utils.sh
function purge_key() {
curl \
-X POST \
-H "Fastly-Key: ${FASTLY_KEY}" \
-H "Accept: application/json" \
-H "Content-Length: 0" \
"https://api.fastly.com/service/$1/purge/$2"
}
function purge() {
purge_key ${FASTLY_REPO_SERVICE_ID} $1
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
sleep 2
purge_key ${FASTLY_REPO_SERVICE_ID} $1
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
sleep 2
purge_key ${FASTLY_REPO_SERVICE_ID} $1
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
}
EOF
chmod +x utils.sh
- name: Upload Docs to S3
if: ${{ matrix.build_docs }}
env:
AWS_ACCESS_KEY_ID: ${{ secrets.HEX_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.HEX_AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.HEX_AWS_REGION }}
AWS_S3_BUCKET: ${{ secrets.HEX_AWS_S3_BUCKET }}
FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }}
FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }}
FASTLY_KEY: ${{ secrets.HEX_FASTLY_KEY }}
run: |
source utils.sh
version=$(echo ${{ github.ref_name }} | sed -e 's/^v//g')
for f in doc/*; do
if [ -d "$f" ]; then
app=`echo $f | sed s/"doc\/"//`
tarball="${app}-${version}.tar.gz"
surrogate_key="docs/${app}-${version}"
tar -czf "${tarball}" -C "doc/${app}" .
aws s3 cp "${tarball}" "s3://${{ env.AWS_S3_BUCKET }}/docs/${tarball}" \
--cache-control "public,max-age=3600" \
--metadata "{\"surrogate-key\":\"${surrogate_key}\",\"surrogate-control\":\"public,max-age=604800\"}"
purge "${surrogate_key}"
fi
done
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
otp_release: ['OTP-24.0', 'OTP-23.3', 'OTP-23.0', 'OTP-22.3', 'OTP-22.0']
otp_release: ['OTP-24.3', 'OTP-24.0', 'OTP-23.3', 'OTP-23.0', 'OTP-22.3', 'OTP-22.0']
development: [false]
include:
- otp_release: master
@@ -0,0 +1,51 @@
name: "Release pre built"
description: "Builds elixir release, ExDoc and generates docs"
inputs:
otp:
description: "The major OTP version"
otp_version:
description: "The exact OTP version (major.minor[.patch])"
build_docs:
description: "If docs have to be built or not"
runs:
using: "composite"
steps:
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ inputs.otp_version }}
version-type: strict
- name: Build Elixir Release
shell: bash
run: |
make Precompiled.zip
mv Precompiled.zip elixir-otp-${{ inputs.otp }}.zip
shasum -a 1 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha1sum
shasum -a 256 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha256sum
echo "$PWD/bin" >> $GITHUB_PATH
- name: Get latest stable ExDoc version
if: ${{ inputs.build_docs }}
shell: bash
run: |
EX_DOC_LATEST_STABLE_VERSION=$(curl -s https://hex.pm/api/packages/ex_doc | jq --raw-output '.latest_stable_version')
echo "EX_DOC_LATEST_STABLE_VERSION=${EX_DOC_LATEST_STABLE_VERSION}" >> $GITHUB_ENV
- uses: actions/checkout@v3
if: ${{ inputs.build_docs }}
with:
repository: elixir-lang/ex_doc
ref: v${{ env.EX_DOC_LATEST_STABLE_VERSION }}
path: ex_doc
- name: Build ex_doc
if: ${{ inputs.build_docs }}
shell: bash
run: |
mv ex_doc ../ex_doc
cd ../ex_doc
../elixir/bin/mix do local.rebar --force, local.hex --force, deps.get, compile
cd ../elixir
- name: Build Docs
if: ${{ inputs.build_docs }}
shell: bash
run: |
make Docs.zip
shasum -a 1 Docs.zip > Docs.zip.sha1sum
shasum -a 256 Docs.zip > Docs.zip.sha256sum
+95 -1
View File
@@ -128,6 +128,99 @@ Now any application can use your formatter as follows:
Finally, the `Code` module has also been augmented with two functions: `Code.string_to_quoted_with_comments/2` and `Code.quoted_to_algebra/2`. Those functions allow someone to retrieve the Elixir AST with their original source code comments, and then convert this AST to formatted code. In other words, those functions provide a wrapper around the Elixir Code Formatter, supporting developers who wish to create tools that directly manipulate and custom format Elixir source code.
## v1.13.4 (2022-04-07)
This release has been verified to work with Erlang/OTP 25 RC2.
### 1. Enhancements
#### Elixir
* [Code] Allow iodata to be returned in sigil formatting functions
* [Code] Pass opening delimiter information to sigil formatting functions
### 2. Bug fixes
#### Elixir
* [Kernel] Tweak type unification to fix infinite loop with recursive vars
* [Kernel] Add compile-time dependencies on `require`
* [Registry] Make `Registry` send work with named triplets
### 3. Deprecations
#### Mix
* [mix rebar] Deprecate Rebar 2 as it no longer works on Erlang/OTP 25
## v1.13.3 (2022-02-09)
### 1. Enhancements
#### Mix
* [mix format] Supply file and line to formatter plugins
* [mix format] Support embedded Elixir expressions inside formatter plugins
### 2. Bug fixes
#### Elixir
* [Code] Fix duplicate bindings causing errors during evaluation
* [Kernel] Make sure signatures stored in the Documentation chunk does not contain newlines
* [Kernel] Fix infinite loop when compiling guards with recursive map access
* [Macro] Fix error on `Macro.to_string/1` when the plain alias `Elixir` is given
* [String] Fix error for certain codepoint combinations in `String.split_at/2`
#### Mix
* [mix compile] Recompile project files when exports from dependencies change
* [mix test] Fix total coverage always showing in red even when above the threshold
## v1.13.2 (2022-01-13)
### 1. Enhancements
#### Mix
* [mix format] Allow plugins to also format `.ex` and `.exs` files
* [mix release] Allow bypassing application mode validation in release spec
* [mix test] Print a message when the suite fails due to the coverage threshold
### 2. Bug fixes
#### Elixir
* [Code] Do not emit warnings on `Code.Fragment.container_cursor_to_quoted/2`
* [Kernel] Fix a crash when a for-comprehension with `:uniq` was used inside another comprehension with `:uniq`
* [Kernel] Ensure `env.context_modules` is properly set inside optimized `defmodule`
* [Keyword] Deprecate the recently added `Keyword.map/2` as it is equivalent to `Keyword.new/2`
* [Map] Deprecate the recently added `Map.map/2` as it is equivalent to `Map.new/2`
* [Protocol] Warn on zero arity callbacks inside protocols
## v1.13.1 (2021-12-14)
### 1. Bug fixes
#### Elixir
* [Code] Do not show code snippets in `SyntaxError` and `TokenMissingError` if line is empty
* [Exception] Do not fail blaming `ArgumentError` for improper lists on `apply/3`
* [Macro] Set a max `line_length` for `Macro.to_string/1`
* [Macro] Fix formatting of lists on module attributes for `Macro.to_string/1`
* [String] Fix incorrect codepoint byte counting in `slice` with negative positions in ranges
* [Task] Ensure async streams can be consumed from another process than the one that creates them
* [URI] Undeprecate `URI.parse/1` as `URI.new/1` is too strict in many common cases
* [URI] Make sure `URI.new/1` returns nil for empty paths
#### IEx
* [IEx] Make sure the `--version` flag halts IEx
#### Mix
* [Mix] Make protocol consolidation part of the `Mix.install/2` cache
## v1.13.0 (2021-12-03)
### 1. Enhancements
@@ -151,7 +244,7 @@ Finally, the `Code` module has also been augmented with two functions: `Code.str
* [Inspect] Allow default inspect fun to be set globally with `Inspect.Opts.default_inspect_fun/1`
* [IO] Allow `:eof` to be given as limit to `IO.getn/2`
* [Kernel] Support the `:sigils` option in `import Mod, only: :sigils` and allow the sigil modifiers to be also digits
* [Kernel] Make `get_in` consistently abort when `nil` values are found
* [Kernel] Make `get_in` consistently abort and return `nil` when `nil` values are found (previously Elixir would raise an error in this case). This allows a user to use `get_in` as a safe navigation operator.
* [Kernel] Improve compilation times by reducing the amount of copies of the AST across compiler processes
* [Kernel] Raise if trying to define a module with a slash in its name
* [Kernel] Warn when `?\` is used and there is no need for a escape character
@@ -187,6 +280,7 @@ Finally, the `Code` module has also been augmented with two functions: `Code.str
#### Logger
* [Logger] Add `Logger.put_application_level/2`
* [Logger] Print all log levels in accordance to Erlang/OTP. This also means `[warn]` is now shown as `[warning]`
#### Mix
+26 -17
View File
@@ -171,6 +171,7 @@ clean_residual_files:
rm -rf lib/mix/test/fixtures/git_rebar/
rm -rf lib/mix/test/fixtures/git_repo/
rm -rf lib/mix/test/fixtures/git_sparse_repo/
rm -rf lib/mix/test/fixtures/archive/ebin/
rm -f erl_crash.dump
$(Q) $(MAKE) clean_man
@@ -178,40 +179,48 @@ clean_residual_files:
LOGO_PATH = $(shell test -f ../docs/logo.png && echo "--logo ../docs/logo.png")
SOURCE_REF = $(shell tag="$(call GIT_TAG)" revision="$(call GIT_REVISION)"; echo "$${tag:-$$revision}")
DOCS_FORMAT = html
COMPILE_DOCS = CANONICAL=$(CANONICAL) bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" --main "$(3)" --source-url "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" $(call LOGO_PATH) --output doc/$(2) --canonical "https://hexdocs.pm/$(2)/$(CANONICAL)" --homepage-url "https://elixir-lang.org/docs.html" --formatter "$(DOCS_FORMAT)" $(4)
DOCS_COMPILE = CANONICAL=$(CANONICAL) bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" --main "$(3)" --source-url "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" $(call LOGO_PATH) --output doc/$(2) --canonical "https://hexdocs.pm/$(2)/$(CANONICAL)" --homepage-url "https://elixir-lang.org/docs.html" --formatter "$(DOCS_FORMAT)" $(4)
DOCS_CONFIG = bin/elixir lib/elixir/scripts/docs_config.exs "$(1)"
docs: compile ../ex_doc/bin/ex_doc docs_elixir docs_eex docs_mix docs_iex docs_ex_unit docs_logger
docs_elixir: compile ../ex_doc/bin/ex_doc
@ echo "==> ex_doc (elixir)"
$(Q) rm -rf doc/elixir
$(call COMPILE_DOCS,Elixir,elixir,Kernel,--config "lib/elixir/docs.exs")
$(call DOCS_COMPILE,Elixir,elixir,Kernel,--config "lib/elixir/scripts/elixir_docs.exs")
$(call DOCS_CONFIG,elixir)
docs_eex: compile ../ex_doc/bin/ex_doc
@ echo "==> ex_doc (eex)"
$(Q) rm -rf doc/eex
$(call COMPILE_DOCS,EEx,eex,EEx,--config "lib/mix/docs.exs")
$(call DOCS_COMPILE,EEx,eex,EEx,--config "lib/elixir/scripts/mix_docs.exs")
$(call DOCS_CONFIG,eex)
docs_mix: compile ../ex_doc/bin/ex_doc
@ echo "==> ex_doc (mix)"
$(Q) rm -rf doc/mix
$(call COMPILE_DOCS,Mix,mix,Mix,--config "lib/mix/docs.exs")
$(call DOCS_COMPILE,Mix,mix,Mix,--config "lib/elixir/scripts/mix_docs.exs")
$(call DOCS_CONFIG,mix)
docs_iex: compile ../ex_doc/bin/ex_doc
@ echo "==> ex_doc (iex)"
$(Q) rm -rf doc/iex
$(call COMPILE_DOCS,IEx,iex,IEx,--config "lib/mix/docs.exs")
$(call DOCS_COMPILE,IEx,iex,IEx,--config "lib/elixir/scripts/mix_docs.exs")
$(call DOCS_CONFIG,iex)
docs_ex_unit: compile ../ex_doc/bin/ex_doc
@ echo "==> ex_doc (ex_unit)"
$(Q) rm -rf doc/ex_unit
$(call COMPILE_DOCS,ExUnit,ex_unit,ExUnit,--config "lib/mix/docs.exs")
$(call DOCS_COMPILE,ExUnit,ex_unit,ExUnit,--config "lib/elixir/scripts/mix_docs.exs")
$(call DOCS_CONFIG,ex_unit)
docs_logger: compile ../ex_doc/bin/ex_doc
@ echo "==> ex_doc (logger)"
$(Q) rm -rf doc/logger
$(call COMPILE_DOCS,Logger,logger,Logger,--config "lib/mix/docs.exs")
$(call DOCS_COMPILE,Logger,logger,Logger,--config "lib/elixir/scripts/mix_docs.exs")
$(call DOCS_CONFIG,logger)
../ex_doc/bin/ex_doc:
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
@@ -220,23 +229,23 @@ docs_logger: compile ../ex_doc/bin/ex_doc
#==> Zip tasks
Docs.zip: docs
rm -f Docs-v$(VERSION).zip
zip -9 -r Docs-v$(VERSION).zip CHANGELOG.md doc NOTICE LICENSE README.md
@ echo "Docs file created $(CURDIR)/Docs-v$(VERSION).zip"
rm -f Docs.zip
zip -9 -r Docs.zip CHANGELOG.md doc NOTICE LICENSE README.md
@ echo "Docs file created $(CURDIR)/Docs.zip"
Precompiled.zip: build_man compile
rm -f Precompiled-v$(VERSION).zip
zip -9 -r Precompiled-v$(VERSION).zip bin CHANGELOG.md lib/*/ebin lib/*/lib LICENSE man NOTICE README.md VERSION
@ echo "Precompiled file created $(CURDIR)/Precompiled-v$(VERSION).zip"
rm -f Precompiled.zip
zip -9 -r Precompiled.zip bin CHANGELOG.md lib/*/ebin lib/*/lib LICENSE man NOTICE README.md VERSION
@ echo "Precompiled file created $(CURDIR)/Precompiled.zip"
zips: Precompiled.zip Docs.zip
@ echo ""
@ echo "### Checksums"
@ echo ""
@ shasum -a 1 < Precompiled-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA1:"
@ shasum -a 512 < Precompiled-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA512:"
@ shasum -a 1 < Docs-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Docs.zip SHA1:"
@ shasum -a 512 < Docs-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Docs.zip SHA512:"
@ shasum -a 1 < Precompiled.zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA1:"
@ shasum -a 512 < Precompiled.zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA512:"
@ shasum -a 1 < Docs.zip | sed -e "s/-//" | xargs echo " * Docs.zip SHA1:"
@ shasum -a 512 < Docs.zip | sed -e "s/-//" | xargs echo " * Docs.zip SHA512:"
@ echo ""
#==> Test tasks
+1 -1
View File
@@ -1 +1 @@
1.13.0
1.13.4
+2 -2
View File
@@ -1,7 +1,7 @@
#!/bin/sh
set -e
ELIXIR_VERSION=1.13.0
ELIXIR_VERSION=1.13.4
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
cat <<USAGE >&2
@@ -16,7 +16,7 @@ Usage: $(basename "$0") [options] [.exs file] [data]
-pr "FILE" Requires the given files/patterns in parallel (*)
-pa "PATH" Prepends the given path to Erlang code path (*)
-pz "PATH" Appends the given path to Erlang code path (*)
-v, --version Prints Erlang/OTP and Elixir versions
-v, --version Prints Erlang/OTP and Elixir versions (standalone)
--app APP Starts the given app and its dependencies (*)
--erl "SWITCHES" Switches to be passed down to Erlang (*)
+2 -2
View File
@@ -1,6 +1,6 @@
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
set ELIXIR_VERSION=1.13.0
set ELIXIR_VERSION=1.13.4
setlocal enabledelayedexpansion
if ""%1""=="""" if ""%2""=="""" goto documentation
@@ -23,7 +23,7 @@ echo -S SCRIPT Finds and executes the given script in $PATH
echo -pr "FILE" Requires the given files/patterns in parallel (*)
echo -pa "PATH" Prepends the given path to Erlang code path (*)
echo -pz "PATH" Appends the given path to Erlang code path (*)
echo -v, --version Prints Erlang/OTP and Elixir versions
echo -v, --version Prints Erlang/OTP and Elixir versions (standalone)
echo.
echo --app APP Starts the given app and its dependencies (*)
echo --erl "SWITCHES" Switches to be passed down to Erlang (*)
+1 -1
View File
@@ -7,7 +7,7 @@ Usage: $(basename "$0") [elixir switches] [compiler switches] [.ex files]
-h, --help Prints this message and exits
-o The directory to output compiled files
-v, --version Prints Elixir version and exits
-v, --version Prints Elixir version and exits (standalone)
--ignore-module-conflict Does not emit warnings if a module was previously defined
--no-debug-info Does not attach debug info to compiled modules
+1 -1
View File
@@ -16,7 +16,7 @@ echo Usage: %~nx0 [elixir switches] [compiler switches] [.ex files]
echo.
echo -h, --help Prints this message and exits
echo -o The directory to output compiled files
echo -v, --version Prints Elixir version and exits
echo -v, --version Prints Elixir version and exits (standalone)
echo.
echo --ignore-module-conflict Does not emit warnings if a module was previously defined
echo --no-debug-info Does not attach debug info to compiled modules
+18 -15
View File
@@ -92,6 +92,9 @@ defmodule Base do
"""
@type encode_case :: :upper | :lower
@type decode_case :: :upper | :lower | :mixed
b16_alphabet = '0123456789ABCDEF'
b64_alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
b64url_alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
@@ -267,7 +270,7 @@ defmodule Base do
"666f6f626172"
"""
@spec encode16(binary, keyword) :: binary
@spec encode16(binary, case: encode_case) :: binary
def encode16(data, opts \\ []) when is_binary(data) do
case = Keyword.get(opts, :case, :upper)
do_encode16(case, data)
@@ -300,7 +303,7 @@ defmodule Base do
{:ok, "foobar"}
"""
@spec decode16(binary, keyword) :: {:ok, binary} | :error
@spec decode16(binary, case: decode_case) :: {:ok, binary} | :error
def decode16(string, opts \\ []) do
{:ok, decode16!(string, opts)}
rescue
@@ -337,7 +340,7 @@ defmodule Base do
"foobar"
"""
@spec decode16!(binary, keyword) :: binary
@spec decode16!(binary, case: decode_case) :: binary
def decode16!(string, opts \\ [])
def decode16!(string, opts) when is_binary(string) and rem(byte_size(string), 2) == 0 do
@@ -367,7 +370,7 @@ defmodule Base do
"Zm9vYg"
"""
@spec encode64(binary, keyword) :: binary
@spec encode64(binary, padding: boolean) :: binary
def encode64(data, opts \\ []) when is_binary(data) do
pad? = Keyword.get(opts, :padding, true)
do_encode64(data, pad?)
@@ -397,7 +400,7 @@ defmodule Base do
{:ok, "foob"}
"""
@spec decode64(binary, keyword) :: {:ok, binary} | :error
@spec decode64(binary, ignore: :whitespace, padding: boolean) :: {:ok, binary} | :error
def decode64(string, opts \\ []) when is_binary(string) do
{:ok, decode64!(string, opts)}
rescue
@@ -431,7 +434,7 @@ defmodule Base do
"foob"
"""
@spec decode64!(binary, keyword) :: binary
@spec decode64!(binary, ignore: :whitespace, padding: boolean) :: binary
def decode64!(string, opts \\ []) when is_binary(string) do
pad? = Keyword.get(opts, :padding, true)
string |> remove_ignored(opts[:ignore]) |> do_decode64(pad?)
@@ -453,7 +456,7 @@ defmodule Base do
"_3_-_A"
"""
@spec url_encode64(binary, keyword) :: binary
@spec url_encode64(binary, padding: boolean) :: binary
def url_encode64(data, opts \\ []) when is_binary(data) do
pad? = Keyword.get(opts, :padding, true)
do_encode64url(data, pad?)
@@ -481,7 +484,7 @@ defmodule Base do
{:ok, <<255, 127, 254, 252>>}
"""
@spec url_decode64(binary, keyword) :: {:ok, binary} | :error
@spec url_decode64(binary, ignore: :whitespace, padding: boolean) :: {:ok, binary} | :error
def url_decode64(string, opts \\ []) when is_binary(string) do
{:ok, url_decode64!(string, opts)}
rescue
@@ -513,7 +516,7 @@ defmodule Base do
<<255, 127, 254, 252>>
"""
@spec url_decode64!(binary, keyword) :: binary
@spec url_decode64!(binary, ignore: :whitespace, padding: boolean) :: binary
def url_decode64!(string, opts \\ []) when is_binary(string) do
pad? = Keyword.get(opts, :padding, true)
string |> remove_ignored(opts[:ignore]) |> do_decode64url(pad?)
@@ -551,7 +554,7 @@ defmodule Base do
"MZXW6YTBOI"
"""
@spec encode32(binary, keyword) :: binary
@spec encode32(binary, case: encode_case, padding: boolean) :: binary
def encode32(data, opts \\ []) when is_binary(data) do
case = Keyword.get(opts, :case, :upper)
pad? = Keyword.get(opts, :padding, true)
@@ -594,7 +597,7 @@ defmodule Base do
{:ok, "foobar"}
"""
@spec decode32(binary, keyword) :: {:ok, binary} | :error
@spec decode32(binary, case: decode_case, padding: boolean) :: {:ok, binary} | :error
def decode32(string, opts \\ []) do
{:ok, decode32!(string, opts)}
rescue
@@ -640,7 +643,7 @@ defmodule Base do
"foobar"
"""
@spec decode32!(binary, keyword) :: binary
@spec decode32!(binary, case: decode_case, padding: boolean) :: binary
def decode32!(string, opts \\ []) when is_binary(string) do
case = Keyword.get(opts, :case, :upper)
pad? = Keyword.get(opts, :padding, true)
@@ -680,7 +683,7 @@ defmodule Base do
"CPNMUOJ1E8"
"""
@spec hex_encode32(binary, keyword) :: binary
@spec hex_encode32(binary, case: encode_case, padding: boolean) :: binary
def hex_encode32(data, opts \\ []) when is_binary(data) do
case = Keyword.get(opts, :case, :upper)
pad? = Keyword.get(opts, :padding, true)
@@ -724,7 +727,7 @@ defmodule Base do
{:ok, "foobar"}
"""
@spec hex_decode32(binary, keyword) :: {:ok, binary} | :error
@spec hex_decode32(binary, case: decode_case, padding: boolean) :: {:ok, binary} | :error
def hex_decode32(string, opts \\ []) do
{:ok, hex_decode32!(string, opts)}
rescue
@@ -771,7 +774,7 @@ defmodule Base do
"foobar"
"""
@spec hex_decode32!(binary, keyword) :: binary
@spec hex_decode32!(binary, case: decode_case, padding: boolean) :: binary
def hex_decode32!(string, opts \\ []) when is_binary(string) do
case = Keyword.get(opts, :case, :upper)
pad? = Keyword.get(opts, :padding, true)
+58 -5
View File
@@ -2,11 +2,14 @@ defmodule DateTime do
@moduledoc """
A datetime implementation with a time zone.
This datetime can be seen as an ephemeral snapshot
of a datetime at a given time zone. For such purposes,
it also includes both UTC and Standard offsets, as
well as the zone abbreviation field used exclusively
for formatting purposes.
This datetime can be seen as a snapshot of a date and time
at a given time zone. For such purposes, it also includes both
UTC and Standard offsets, as well as the zone abbreviation
field used exclusively for formatting purposes. Note future
datetimes are not necessarily guaranteed to exist, as time
zones may change any time in the future due to geopolitical
reasons. See the "Datetimes as snapshots" section for more
information.
Remember, comparisons in Elixir using `==/2`, `>/2`, `</2` and friends
are structural and based on the DateTime struct fields. For proper
@@ -41,6 +44,56 @@ defmodule DateTime do
Calendar.put_time_zone_database(Tzdata.TimeZoneDatabase)
See the proper names in the library installation instructions.
## Datetimes as snapshots
In the first section, we described datetimes as a "snapshot of
a date and time at a given time zone". To understand precisely
what we mean, let's see an example.
Imagine someone in Poland wants to schedule a meeting with someone
in Brazil in the next year. The meeting will happen at 2:30 AM
in the Polish time zone. At what time will the meeting happen in
Brazil?
You can consult the time zone database today, one year before,
using the API in this module and it will give you an answer that
is valid right now. However, this answer may not be valid in the
future. Why? Because both Brazil and Poland may change their timezone
rules, ultimately affecting the result. For example, a country may
choose to enter or abandon "Daylight Saving Time", which is a
process where we adjust the clock one hour forward or one hour
back once per year. Whenener the rules change, the exact instant
that 2:30 AM in Polish time will be in Brazil may change.
In other words, whenever working with future DateTimes, there is
no guarantee the results you get will always be correct, until
the event actually happens. Therefore, when you ask for a future
time, the answers you get are a snapshot that reflects the current
state of the time zone rules. For datetimes in the past, this is
not a problem, because time zone rules do not change for past
events.
To make matters worse, it may be that the 2:30 AM in Polish time
does not actually even exist or it is ambiguous. If a certain
time zone observes "Daylight Saving Time", they will move their
clock forward once a year. When this happens, there is a whole
hour that does not exist. Then, when they move the clock back,
there is a certain hour that will happen twice. So if you want
to schedule a meeting when this shift back happens, you would
need to explicitly say which of the 2:30 AM you precisely mean.
Applications that are date and time sensitive, need to take
these scenarios into account and correctly communicate them to
users.
The good news is: Elixir contains all of the building blocks
necessary to tackle those problems. The default timezone database
used by Elixir, `Calendar.UTCOnlyTimeZoneDatabase`, only works
with UTC, which does not observe those issues. Once you bring
a proper time zone database, the functions in this module will
query the database and return the relevant information. For
example, look at how `DateTime.new/4` returns different results
based on the scenarios described in this section.
"""
@enforce_keys [:year, :month, :day, :hour, :minute, :second] ++
+15 -5
View File
@@ -177,6 +177,7 @@ defmodule Code.Formatter do
defp state(comments, opts) do
force_do_end_blocks = Keyword.get(opts, :force_do_end_blocks, false)
locals_without_parens = Keyword.get(opts, :locals_without_parens, [])
file = Keyword.get(opts, :file, nil)
sigils = Keyword.get(opts, :sigils, [])
sigils =
@@ -199,7 +200,8 @@ defmodule Code.Formatter do
operand_nesting: 2,
skip_eol: false,
comments: comments,
sigils: sigils
sigils: sigils,
file: file
}
end
@@ -1320,13 +1322,21 @@ defmodule Code.Formatter do
entries =
case state.sigils do
%{^name => callback} ->
case callback.(hd(entries), sigil: List.to_atom([name]), modifiers: modifiers) do
binary when is_binary(binary) ->
[binary]
metadata = [
file: state.file,
line: meta[:line],
sigil: List.to_atom([name]),
modifiers: modifiers,
opening_delimiter: opening_delimiter
]
case callback.(hd(entries), metadata) do
iodata when is_binary(iodata) or is_list(iodata) ->
[IO.iodata_to_binary(iodata)]
other ->
raise ArgumentError,
"expected sigil callback to return a binary, got: #{inspect(other)}"
"expected sigil callback to return iodata, got: #{inspect(other)}"
end
%{} ->
+2 -2
View File
@@ -525,7 +525,7 @@ defmodule Code.Fragment do
|> position_surround_context(line, column, opts)
end
def surround_context(other, position, opts) do
def surround_context(other, {_, _} = position, opts) do
surround_context(to_charlist(other), position, opts)
end
@@ -827,7 +827,7 @@ defmodule Code.Fragment do
case :elixir_tokenizer.tokenize(fragment, line, column, tokenizer_opts) do
{:ok, _, _, _warnings, tokens} ->
:elixir.tokens_to_quoted(tokens, file, columns: columns, token_metadata: token_metadata)
:elixir.tokens_to_quoted(tokens, nil, columns: columns, token_metadata: token_metadata)
{:error, {line, column, {prefix, suffix}, token}, _rest, _warnings, _so_far} ->
location = [line: line, column: column]
+18 -9
View File
@@ -200,10 +200,15 @@ defmodule Code.Normalizer do
# Module attributes
defp do_normalize({:@, meta, [{name, name_meta, [value]}]}, state) do
value =
if is_list(value) do
normalize_kw_args(value, state, false)
else
do_normalize(value, state)
cond do
keyword?(value) ->
normalize_kw_args(value, state, true)
is_list(value) ->
normalize_literal(value, meta, state)
true ->
do_normalize(value, state)
end
{:@, meta, [{name, name_meta, [value]}]}
@@ -246,12 +251,16 @@ defmodule Code.Normalizer do
if is_atom(literal) and Code.Identifier.classify(literal) == :alias and
is_nil(meta[:delimiter]) do
"Elixir." <> segments = Atom.to_string(literal)
segments =
segments
|> String.split(".")
|> Enum.map(&String.to_atom/1)
case Atom.to_string(literal) do
"Elixir" ->
[:"Elixir"]
"Elixir." <> segments ->
segments
|> String.split(".")
|> Enum.map(&String.to_atom/1)
end
{:__aliases__, meta, segments}
else
+1
View File
@@ -2578,6 +2578,7 @@ defmodule Enum do
[:a, :b, :c, :e, :f, :g, :d]
"""
@doc since: "1.13.0"
def slide(enumerable, range_or_single_index, insertion_index)
def slide(enumerable, single_index, insertion_index) when is_integer(single_index) do
+7 -4
View File
@@ -732,6 +732,10 @@ defmodule ArgumentError do
) do
message =
cond do
not proper_list?(args) ->
"you attempted to apply a function named #{inspect(function)} on module #{inspect(module)} " <>
"with arguments #{inspect(args)}. Arguments (the third argument of apply) must always be a proper list"
# Note that args may be an empty list even if they were supplied
not is_atom(module) and is_atom(function) and args == [] ->
"you attempted to apply a function named #{inspect(function)} on #{inspect(module)}. " <>
@@ -747,10 +751,6 @@ defmodule ArgumentError do
"you attempted to apply a function named #{inspect(function)} on module #{inspect(module)}. " <>
"However #{inspect(function)} is not a valid function name. Function names (the second argument " <>
"of apply) must always be an atom"
not is_list(args) ->
"you attempted to apply a function named #{inspect(function)} on module #{inspect(module)} " <>
"with arguments #{inspect(args)}. Arguments (the third argument of apply) must always be a list"
end
{%{exception | message: message}, stacktrace}
@@ -759,6 +759,9 @@ defmodule ArgumentError do
def blame(exception, stacktrace) do
{exception, stacktrace}
end
defp proper_list?(list) when length(list) >= 0, do: true
defp proper_list?(_), do: false
end
defmodule ArithmeticError do
+8 -3
View File
@@ -218,12 +218,16 @@ defmodule Kernel.CLI do
# Parse shared options
defp parse_shared([opt | _], _config) when opt in @standalone_opts do
defp warn_standalone(opt) do
IO.puts(:stderr, "#{opt} : Standalone options can't be combined with other options")
end
defp parse_shared([opt | _], _config) when opt in @standalone_opts do
warn_standalone(opt)
System.halt(1)
end
defp parse_shared([opt | t], config) when opt in ["-v", "--version"] do
defp parse_shared([opt | t], _config) when opt in ["-v", "--version"] do
if function_exported?(IEx, :started?, 0) and IEx.started?() do
IO.puts("IEx " <> System.build_info()[:build])
else
@@ -231,7 +235,8 @@ defmodule Kernel.CLI do
IO.puts("Elixir " <> System.build_info()[:build])
end
parse_shared(t, config)
t != [] && warn_standalone(opt)
System.halt(0)
end
defp parse_shared(["-pa", h | t], config) do
+10 -9
View File
@@ -30,8 +30,8 @@ defmodule Kernel.LexicalTracker do
end
@doc false
def add_require(pid, module) when is_atom(module) do
:gen_server.cast(pid, {:add_require, module})
def add_export(pid, module) when is_atom(module) do
:gen_server.cast(pid, {:add_export, module})
end
@doc false
@@ -168,15 +168,16 @@ defmodule Kernel.LexicalTracker do
{:noreply, update_in(state.compile_env, &:ordsets.add_element({app, path, return}, &1))}
end
def handle_cast({:add_require, module}, state) do
def handle_cast({:add_export, module}, state) do
{:noreply, put_in(state.exports[module], true)}
end
def handle_cast({:add_import, module, fas, line, warn}, state) do
to_remove = for {{:import, {^module, _, _}} = key, _} <- state.directives, do: key
%{directives: directives, exports: exports} = state
to_remove = for {{:import, {^module, _, _}} = key, _} <- directives, do: key
directives =
state.directives
directives
|> Map.drop(to_remove)
|> add_directive(module, line, warn, :import)
@@ -185,7 +186,7 @@ defmodule Kernel.LexicalTracker do
add_directive(directives, {module, function, arity}, line, warn, :import)
end)
{:noreply, %{state | directives: directives}}
{:noreply, %{state | directives: directives, exports: Map.put(exports, module, true)}}
end
def handle_cast({:add_alias, module, line, warn}, state) do
@@ -221,9 +222,9 @@ defmodule Kernel.LexicalTracker do
do: Map.put(references, module, :compile)
defp add_reference(references, module, :runtime) when is_atom(module) do
case Map.fetch(references, module) do
{:ok, _} -> references
:error -> Map.put(references, module, :runtime)
case references do
%{^module => _} -> references
_ -> Map.put(references, module, :runtime)
end
end
+4 -3
View File
@@ -554,7 +554,9 @@ defmodule Kernel.SpecialForms do
defmacro alias(module, opts), do: error!([module, opts])
@doc """
Requires a module in order to use its macros.
Requires a module as a compile-time dependency.
Requiring a module is necessary in order to use its macros.
## Examples
@@ -576,7 +578,6 @@ defmodule Kernel.SpecialForms do
`require/2` also accepts `:as` as an option so it automatically sets
up an alias. Please check `alias/2` for more information.
"""
defmacro require(module, opts), do: error!([module, opts])
@@ -1611,7 +1612,7 @@ defmodule Kernel.SpecialForms do
end
Note how the code above is better organized and clearer once we
make sure each clause in `with` returns a normalize format.
make sure each clause in `with` returns a normalized format.
"""
defmacro with(args), do: error!([args])
+4 -22
View File
@@ -1374,27 +1374,9 @@ defmodule Keyword do
end
end
@doc """
Maps the function `fun` over all key-value pairs in `keywords`,
returning a keyword list with all the values replaced with
the result of the function.
## Examples
iex> Keyword.map([one: 1, two: 2, three: 3], fn {_key, val} -> to_string(val) end)
[one: "1", two: "2", three: "3"]
"""
@doc since: "1.13.0"
@spec map(t, ({key, value} -> value)) :: t
def map(keywords, fun) when is_list(keywords) and is_function(fun, 1) do
do_map(keywords, fun)
end
defp do_map([], _fun), do: []
defp do_map([{key, value} | rest], fun) do
new_value = fun.({key, value})
[{key, new_value} | do_map(rest, fun)]
@doc false
@deprecated "Use Keyword.new/2 instead"
def map(keywords, fun) when is_list(keywords) do
Enum.map(keywords, fn {k, v} -> {k, fun.({k, v})} end)
end
end
+1 -1
View File
@@ -945,7 +945,7 @@ defmodule Macro do
"""
@spec to_string(t()) :: String.t()
def to_string(tree) do
doc = Inspect.Algebra.format(Code.quoted_to_algebra(tree), :infinity)
doc = Inspect.Algebra.format(Code.quoted_to_algebra(tree), 98)
IO.iodata_to_binary(doc)
end
+29 -30
View File
@@ -214,7 +214,24 @@ defmodule Map do
"""
@spec new(Enumerable.t(), (term -> {key, value})) :: map
def new(enumerable, transform) when is_function(transform, 1) do
def new(enumerable, transform)
def new(%_{} = enumerable, transform), do: new_from_enum(enumerable, transform)
def new(%{} = map, transform), do: new_from_map(map, transform)
def new(enumerable, transform), do: new_from_enum(enumerable, transform)
defp new_from_map(map, transform) when is_function(transform, 1) do
iter = :maps.iterator(map)
next = :maps.next(iter)
:maps.from_list(do_map(next, transform))
end
defp do_map(:none, _fun), do: []
defp do_map({key, value, iter}, transform) do
[transform.({key, value}) | do_map(:maps.next(iter), transform)]
end
defp new_from_enum(enumerable, transform) when is_function(transform, 1) do
enumerable
|> Enum.map(transform)
|> :maps.from_list()
@@ -979,12 +996,14 @@ defmodule Map do
`fun` receives the key and value of each of the
elements in the map as a key-value pair.
`Map.filter/2` is faster than using `map |> Enum.filter(fun) |> Enum.into(%{})`,
as no intermediate list is being built.
See also `reject/2` which discards all elements where the
function returns a truthy value.
> Note: if you find yourself doing multiple calls to `Map.filter/2`
> and `Map.reject/2` in a pipeline, it is likely more efficient
> to use `Enum.map/2` and `Enum.filter/2` instead and convert to
> a map at the end using `Map.new/1`.
## Examples
iex> Map.filter(%{one: 1, two: 2, three: 3}, fn {_key, val} -> rem(val, 2) == 1 end)
@@ -1010,9 +1029,8 @@ defmodule Map do
end
@doc """
Returns map excluding the pairs from `map` for which `fun` returns a truthy value.
`Map.reject/2` is faster than using `map |> Enum.reject(fun) |> Enum.into(%{})`,
as no intermediate list is being built.
Returns map excluding the pairs from `map` for which `fun` returns
a truthy value.
See also `filter/2`.
@@ -1040,28 +1058,9 @@ defmodule Map do
end
end
@doc """
Maps the function `fun` over all key-value pairs in `map`, returning a map
with all the values replaced with the result of the function.
## Examples
iex> Map.map(%{1 => "joe", 2 => "mike", 3 => "robert"}, fn {_key, val} -> String.capitalize(val) end)
%{1 => "Joe", 2 => "Mike", 3 => "Robert"}
"""
@doc since: "1.13.0"
@spec map(map, ({key, value} -> value)) :: map
def map(map, fun) when is_map(map) and is_function(fun, 1) do
iter = :maps.iterator(map)
next = :maps.next(iter)
:maps.from_list(do_map(next, fun))
end
defp do_map(:none, _fun), do: []
defp do_map({key, value, iter}, fun) do
new_value = fun.({key, value})
[{key, new_value} | do_map(:maps.next(iter), fun)]
@doc false
@deprecated "Use Map.new/2 instead"
def map(map, fun) when is_map(map) do
new(map, fn {k, v} -> {k, fun.({k, v})} end)
end
end
+2 -1
View File
@@ -1811,7 +1811,8 @@ defmodule Module do
acc
true ->
:elixir_env.trace({:require, [], behaviour, []}, env)
event = {:remote_function, [], behaviour, :behaviour_info, 1}
:elixir_env.trace(event, %{env | function: {:__info__, 1}})
optional_callbacks = behaviour_info(behaviour, :optional_callbacks)
callbacks = behaviour_info(behaviour, :callbacks)
Enum.reduce(callbacks, acc, &add_callback(&1, behaviour, env, optional_callbacks, &2))
+11 -7
View File
@@ -403,10 +403,8 @@ defmodule Module.Types.Pattern do
# bar, {:ok, baz}
# bar, {:ok, bat}
expanded_args =
args
|> Enum.map(&flatten_union(&1, context))
|> cartesian_product()
flatten_args = Enum.map(args, &flatten_union(&1, context))
cartesian_args = cartesian_product(flatten_args)
# Remove clauses that do not match the expected type
# Ignore type variables in parameters by changing them to dynamic
@@ -425,11 +423,11 @@ defmodule Module.Types.Pattern do
# the type contexts from unifying argument and parameter to
# infer type variables in arguments
result =
flat_map_ok(expanded_args, fn expanded_args ->
flat_map_ok(cartesian_args, fn cartesian_args ->
result =
Enum.flat_map(clauses, fn {params, return} ->
result =
map_ok(Enum.zip(expanded_args, params), fn {arg, param} ->
map_ok(Enum.zip(cartesian_args, params), fn {arg, param} ->
case unify(arg, param, stack, context) do
{:ok, _type, context} -> {:ok, context}
{:error, reason} -> {:error, reason}
@@ -453,7 +451,13 @@ defmodule Module.Types.Pattern do
{:ok, returns_contexts} ->
{success_returns, contexts} = Enum.unzip(returns_contexts)
contexts = Enum.concat(contexts)
indexes = Enum.uniq(Enum.flat_map(args, &collect_var_indexes_from_type/1))
indexes =
for types <- flatten_args,
type <- types,
index <- collect_var_indexes_from_type(type),
do: index,
uniq: true
# Build unions from collected type contexts to unify with
# type variables from arguments
+4 -4
View File
@@ -43,14 +43,14 @@ defmodule Module.Types.Unify do
{:ok, same, context}
end
def unify(type, {:var, var}, stack, context) do
unify_var(var, type, stack, context, _var_source = false)
end
def unify({:var, var}, type, stack, context) do
unify_var(var, type, stack, context, _var_source = true)
end
def unify(type, {:var, var}, stack, context) do
unify_var(var, type, stack, context, _var_source = false)
end
def unify({:tuple, n, sources}, {:tuple, n, targets}, stack, context) do
result =
map_reduce_ok(Enum.zip(sources, targets), context, fn {source, target}, context ->
+5 -1
View File
@@ -383,7 +383,7 @@ defmodule Process do
@type spawn_opt ::
:link
| :monitor
| {:monitor, :erlang.monitor_option()}
| {:monitor, monitor_option()}
| {:priority, :low | :normal | :high}
| {:fullsweep_after, non_neg_integer}
| {:min_heap_size, non_neg_integer}
@@ -392,6 +392,10 @@ defmodule Process do
| {:message_queue_data, :off_heap | :on_heap}
@type spawn_opts :: [spawn_opt]
# TODO: Use :erlang.monitor_option() on Erlang/OTP 24+
@typep monitor_option ::
[alias: :explicit_unalias | :demonitor | :reply_demonitor, tag: term()]
@doc """
Spawns the given function according to the given options.
+2 -2
View File
@@ -723,14 +723,14 @@ defmodule Protocol do
defp callback_ast_to_fa({kind, {:"::", meta, [{name, _, args}, _return]}, _pos})
when kind in [:callback, :macrocallback] do
[{{name, length(args)}, meta}]
[{{name, length(List.wrap(args))}, meta}]
end
defp callback_ast_to_fa(
{kind, {:when, _, [{:"::", meta, [{name, _, args}, _return]}, _vars]}, _pos}
)
when kind in [:callback, :macrocallback] do
[{{name, length(args)}, meta}]
[{{name, length(List.wrap(args))}, meta}]
end
defp callback_ast_to_fa({kind, _, _pos}) when kind in [:callback, :macrocallback] do
+4
View File
@@ -260,6 +260,10 @@ defmodule Registry do
end
end
def send({registry, key, _value}, msg) do
Registry.send({registry, key}, msg)
end
@doc false
def unregister_name({registry, key}), do: unregister(registry, key)
def unregister_name({registry, key, _value}), do: unregister(registry, key)
+9 -5
View File
@@ -2723,12 +2723,16 @@ defmodule String do
graphemes_and_length: 1,
reverse_characters_to_binary: 1}
defp byte_size_remaining_at(binary, 0) do
byte_size(binary)
defp byte_size_unicode(binary) when is_binary(binary), do: byte_size(binary)
defp byte_size_unicode([head]), do: byte_size_unicode(head)
defp byte_size_unicode([head | tail]), do: byte_size_unicode(head) + byte_size_unicode(tail)
defp byte_size_remaining_at(unicode, 0) do
byte_size_unicode(unicode)
end
defp byte_size_remaining_at(binary, n) do
case :unicode_util.gc(binary) do
defp byte_size_remaining_at(unicode, n) do
case :unicode_util.gc(unicode) do
[_] -> 0
[_ | rest] -> byte_size_remaining_at(rest, n - 1)
[] -> 0
@@ -2736,7 +2740,7 @@ defmodule String do
end
end
defp codepoint_byte_size(cp) when cp <= 0x00FF, do: 1
defp codepoint_byte_size(cp) when cp <= 0x007F, do: 1
defp codepoint_byte_size(cp) when cp <= 0x07FF, do: 2
defp codepoint_byte_size(cp) when cp <= 0xFFFF, do: 3
defp codepoint_byte_size(_), do: 4
+7 -5
View File
@@ -657,12 +657,14 @@ defmodule Task do
end
defp build_stream(enumerable, fun, options) do
owner = self()
fn acc, acc_fun ->
owner = get_owner(self())
&Task.Supervised.stream(enumerable, &1, &2, get_callers(owner), fun, options, fn ->
{:ok, pid} = Task.Supervised.start_link(get_owner(owner), :nomonitor)
{:ok, :link, pid}
end)
Task.Supervised.stream(enumerable, acc, acc_fun, get_callers(self()), fun, options, fn ->
{:ok, pid} = Task.Supervised.start_link(owner, :nomonitor)
{:ok, :link, pid}
end)
end
end
# Returns a tuple with the node where this is executed and either the
+14 -12
View File
@@ -484,20 +484,22 @@ defmodule Task.Supervisor do
end
defp build_stream(supervisor, link_type, enumerable, fun, options) do
shutdown = options[:shutdown]
owner = self()
fn acc, acc_fun ->
shutdown = options[:shutdown]
owner = get_owner(self())
&Task.Supervised.stream(enumerable, &1, &2, get_callers(owner), fun, options, fn ->
args = [get_owner(owner), :monitor]
Task.Supervised.stream(enumerable, acc, acc_fun, get_callers(self()), fun, options, fn ->
args = [owner, :monitor]
case start_child_with_spec(supervisor, args, :temporary, shutdown) do
{:ok, pid} ->
if link_type == :link, do: Process.link(pid)
{:ok, link_type, pid}
case start_child_with_spec(supervisor, args, :temporary, shutdown) do
{:ok, pid} ->
if link_type == :link, do: Process.link(pid)
{:ok, link_type, pid}
{:error, :max_children} ->
{:error, :max_children}
end
end)
{:error, :max_children} ->
{:error, :max_children}
end
end)
end
end
end
+105 -25
View File
@@ -12,7 +12,7 @@ defmodule URI do
"""
defstruct scheme: nil,
path: "",
path: nil,
query: nil,
fragment: nil,
authority: nil,
@@ -20,7 +20,6 @@ defmodule URI do
host: nil,
port: nil
# TODO: Remove nil from path when we fully deprecate URI.parse on Elixir v1.17
@type t :: %__MODULE__{
authority: authority,
fragment: nil | binary,
@@ -38,6 +37,7 @@ defmodule URI do
defmodule Error do
defexception [:action, :reason, :part]
@doc false
def message(%Error{action: action, reason: reason, part: part}) do
"cannot #{action} due to reason #{reason}: #{inspect(part)}"
end
@@ -480,12 +480,13 @@ defmodule URI do
Creates a new URI struct from a URI or a string.
If a `%URI{}` struct is given, it returns `{:ok, uri}`. If a string is
given, it will parse it and returns `{:ok, uri}`. If the string is
invalid, it returns `{:error, part}` instead, with the invalid part of the URI.
given, it will parse and validate it. If the string is valid, it returns
`{:ok, uri}`, otherwise it returns `{:error, part}` with the invalid part
of the URI. For parsing URIs without further validation, see `parse/1`.
This function can parse both absolute and relative URLs. You can check
if a URI is absolute or relative by checking if the `scheme` field is
`nil` or not. All fields may be `nil`, except for the `path`.
`nil` or not.
When a URI is given without a port, the value returned by `URI.default_port/1`
for the URI's scheme is used for the `:port` field. The scheme is also
@@ -552,7 +553,7 @@ defmodule URI do
{:ok, %URI{
fragment: nil,
host: nil,
path: "",
path: nil,
port: 443,
query: "query",
scheme: "https",
@@ -634,6 +635,8 @@ defmodule URI do
end
end
defp uri_from_map(%{path: ""} = map), do: uri_from_map(%{map | path: nil})
defp uri_from_map(map) do
uri = Map.merge(%URI{}, map)
@@ -658,29 +661,106 @@ defmodule URI do
end
@doc """
Parses a well-formed URI into its components.
Parses a URI into its components, without further validation.
This function is deprecated as it fails to raise in case of invalid URIs.
Use `URI.new!/1` or `URI.new/1` instead. In case you want to mimic the
behaviour of this function, you can do:
This function can parse both absolute and relative URLs. You can check
if a URI is absolute or relative by checking if the `scheme` field is
nil or not. Furthermore, this function expects both absolute and
relative URIs to be well-formed and does not perform any validation.
See the "Examples" section below. Use `new/1` if you want more strict
validation.
case URI.new(path) do
{:ok, uri} -> uri
{:error, _} -> %URI{path: path}
end
When a URI is given without a port, the value returned by `URI.default_port/1`
for the URI's scheme is used for the `:port` field. The scheme is also
normalized to lowercase.
There are two differencws in the behaviour of this function compared to
`URI.new/1`:
If a `%URI{}` struct is given to this function, this function returns it
unmodified.
* This function sets the deprecated authority field
> Note: this function sets the field :authority for backwards
> compatibility reasons but it is deprecated.
* This function sets the path to `nil` when it is empty,
while `new/1` consider the path always exists and sets it
to an empty string
## Examples
iex> URI.parse("https://elixir-lang.org/")
%URI{
authority: "elixir-lang.org",
fragment: nil,
host: "elixir-lang.org",
path: "/",
port: 443,
query: nil,
scheme: "https",
userinfo: nil
}
iex> URI.parse("//elixir-lang.org/")
%URI{
authority: "elixir-lang.org",
fragment: nil,
host: "elixir-lang.org",
path: "/",
port: nil,
query: nil,
scheme: nil,
userinfo: nil
}
iex> URI.parse("/foo/bar")
%URI{
authority: nil,
fragment: nil,
host: nil,
path: "/foo/bar",
port: nil,
query: nil,
scheme: nil,
userinfo: nil
}
iex> URI.parse("foo/bar")
%URI{
authority: nil,
fragment: nil,
host: nil,
path: "foo/bar",
port: nil,
query: nil,
scheme: nil,
userinfo: nil
}
In contrast to `URI.new/1`, this function will parse poorly-formed
URIs, for example:
iex> URI.parse("/invalid_greater_than_in_path/>")
%URI{
authority: nil,
fragment: nil,
host: nil,
path: "/invalid_greater_than_in_path/>",
port: nil,
query: nil,
scheme: nil,
userinfo: nil
}
Another example is a URI with brackets in query strings. It is accepted
by `parse/1` but it will be refused by `new/1`:
iex> URI.parse("/?foo[bar]=baz")
%URI{
authority: nil,
fragment: nil,
host: nil,
path: "/",
port: nil,
query: "foo[bar]=baz",
scheme: nil,
userinfo: nil
}
"""
# TODO: Deprecate me at least on v1.17
@doc deprecated: "Use URI.new/1 or URI.new!/1 instead"
@spec parse(t | binary) :: t
def parse(%URI{} = uri), do: uri
@@ -813,6 +893,7 @@ defmodule URI do
%{rel | scheme: base.scheme, path: remove_dot_segments_from_path(rel.path)}
end
# TODO: Check only for nils in future versions
def merge(%URI{} = base, %URI{path: rel_path} = rel) when rel_path in ["", nil] do
%{base | query: rel.query || base.query, fragment: rel.fragment}
end
@@ -826,9 +907,7 @@ defmodule URI do
merge(parse(base), parse(rel))
end
# TODO: Deprecate me on Elixir v1.19
defp merge_paths(nil, rel_path), do: merge_paths("/", rel_path)
defp merge_paths("", rel_path), do: merge_paths("/", rel_path)
defp merge_paths(_, "/" <> _ = rel_path), do: remove_dot_segments_from_path(rel_path)
defp merge_paths(base_path, rel_path) do
@@ -868,7 +947,8 @@ end
defimpl String.Chars, for: URI do
def to_string(%{host: host, path: path} = uri)
when host != nil and path != "" and binary_part(path, 0, 1) != "/" do
when host != nil and is_binary(path) and
path != "" and binary_part(path, 0, 1) != "/" do
raise ArgumentError,
":path in URI must be empty or an absolute path if URL has a :host, got: #{inspect(uri)}"
end
+19
View File
@@ -0,0 +1,19 @@
# Generate docs_config.js for version chooser in ExDoc
[app] = System.argv()
{text_tags, 0} = System.cmd("git", ["tag"])
skipped = Version.parse!("1.0.3")
list_contents =
text_tags
|> String.split()
|> Enum.map(fn "v" <> rest -> Version.parse!(rest) end)
|> Enum.filter(&(Version.compare(&1, skipped) == :gt))
|> Enum.sort({:desc, Version})
|> Enum.map_intersperse(", ", fn version ->
version_string = to_string(version)
~s[{"version":"v#{version_string}", "url":"https://hexdocs.pm/#{app}/#{version_string}"}]
end)
File.mkdir_p!("doc/#{app}")
File.write!("doc/#{app}/docs_config.js", ["var versionNodes = [", list_contents, "];\n"])
+19 -12
View File
@@ -254,7 +254,7 @@ eval_quoted(Tree, Binding, #{line := Line} = E) ->
eval_forms(Tree, Binding, Opts) when is_list(Opts) ->
eval_forms(Tree, Binding, env_for_eval(Opts));
eval_forms(Tree, RawBinding, OrigE) ->
{Vars, Binding} = normalize_binding(RawBinding, [], [], 0),
{Vars, Binding} = normalize_binding(RawBinding, #{}, [], 0),
E = elixir_env:with_vars(OrigE, Vars),
{_, S} = elixir_env:env_to_erl(E),
{Erl, NewErlS, NewExS, NewE} = quoted_to_erl(Tree, E, S),
@@ -275,12 +275,19 @@ eval_forms(Tree, RawBinding, OrigE) ->
{Value, elixir_erl_var:dump_binding(NewBinding, NewExS, NewErlS), NewE}
end.
normalize_binding([{Key, Value} | Binding], Vars, Acc, I) when is_atom(Key) ->
normalize_binding(Binding, [{{Key, nil}, I} | Vars], [{{Key, nil}, Value} | Acc], I + 1);
normalize_binding([{Pair, Value} | Binding], Vars, Acc, I) ->
normalize_binding(Binding, [{Pair, I} | Vars], [{Pair, Value} | Acc], I + 1);
normalize_binding([], Vars, Acc, _I) ->
{maps:from_list(Vars), Acc}.
normalize_binding([Binding | NextBindings], VarsMap, Normalized, Counter) ->
{Pair, Value} = normalize_pair(Binding),
case VarsMap of
#{Pair := _} ->
normalize_binding(NextBindings, VarsMap, [{Pair, Value} | Normalized], Counter);
#{} ->
normalize_binding(NextBindings, VarsMap#{Pair => Counter}, [{Pair, Value} | Normalized], Counter + 1)
end;
normalize_binding([], VarsMap, Normalized, _Counter) ->
{VarsMap, Normalized}.
normalize_pair({Key, Value}) when is_atom(Key) -> {{Key, nil}, Value};
normalize_pair({Pair, Value}) -> {Pair, Value}.
recur_eval([Expr | Exprs], Binding, Env) ->
{value, Value, NewBinding} =
@@ -359,8 +366,8 @@ string_to_tokens(String, StartLine, StartColumn, File, Opts) when is_integer(Sta
{error, {Location, to_binary(Error), to_binary(Token)}}
end.
tokens_to_quoted(Tokens, File, Opts) ->
handle_parsing_opts(File, Opts),
tokens_to_quoted(Tokens, WarningFile, Opts) ->
handle_parsing_opts(WarningFile, Opts),
try elixir_parser:parse(Tokens) of
{ok, Forms} ->
@@ -370,7 +377,7 @@ tokens_to_quoted(Tokens, File, Opts) ->
{error, {Line, _, [Error, Token]}} ->
{error, {parser_location(Line), to_binary(Error), to_binary(Token)}}
after
erase(elixir_parser_file),
erase(elixir_parser_warning_file),
erase(elixir_parser_columns),
erase(elixir_token_metadata),
erase(elixir_literal_encoder)
@@ -406,7 +413,7 @@ parser_location(Meta) ->
to_binary(List) when is_list(List) -> elixir_utils:characters_to_binary(List);
to_binary(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8).
handle_parsing_opts(File, Opts) ->
handle_parsing_opts(WarningFile, Opts) ->
LiteralEncoder =
case lists:keyfind(literal_encoder, 1, Opts) of
{literal_encoder, Fun} -> Fun;
@@ -414,7 +421,7 @@ handle_parsing_opts(File, Opts) ->
end,
TokenMetadata = lists:keyfind(token_metadata, 1, Opts) == {token_metadata, true},
Columns = lists:keyfind(columns, 1, Opts) == {columns, true},
put(elixir_parser_file, File),
put(elixir_parser_warning_file, WarningFile),
put(elixir_parser_columns, Columns),
put(elixir_token_metadata, TokenMetadata),
put(elixir_literal_encoder, LiteralEncoder).
+2 -1
View File
@@ -133,7 +133,8 @@ fast_compile({defmodule, Meta, [Mod, [{do, TailBlock}]]}, NoLineE) ->
'Elixir.Macro':expand(Mod, E)
end,
elixir_module:compile(Expanded, Block, [], E).
ContextModules = [Expanded | ?key(E, context_modules)],
elixir_module:compile(Expanded, Block, [], E#{context_modules := ContextModules}).
%% Bootstrapper
+3 -1
View File
@@ -552,7 +552,9 @@ signature_to_binary(Module, '__struct__', []) ->
<<"%", ('Elixir.Kernel':inspect(Module))/binary, "{}">>;
signature_to_binary(_, Name, Signature) ->
'Elixir.Macro':to_string({Name, [{closing, []}], Signature}).
Quoted = {Name, [{closing, []}], Signature},
Doc = 'Elixir.Inspect.Algebra':format('Elixir.Code':quoted_to_algebra(Quoted), infinity),
'Elixir.IO':iodata_to_binary(Doc).
checker_chunk(#{definitions := Definitions, deprecated := Deprecated, is_behaviour := IsBehaviour}) ->
DeprecatedMap = maps:from_list(Deprecated),
+10 -10
View File
@@ -21,7 +21,7 @@ translate_reduce(Meta, Cases, Expr, Reduce, S) ->
({'case', CaseAnn, _, CaseBlock}, InnerAcc) -> {'case', CaseAnn, InnerAcc, CaseBlock}
end,
{build_reduce(Ann, TCases, InnerFun, TExpr, TReduce, false, SE), SE}.
build_reduce(Ann, TCases, InnerFun, TExpr, TReduce, false, SE).
translate_into(Meta, Cases, Expr, Opts, Return, S) ->
Ann = ?ann(Meta),
@@ -115,11 +115,11 @@ build_inline(Ann, Clauses, Expr, Into, Uniq, S) ->
build_inline_each(Ann, Clauses, Expr, false, Uniq, S) ->
InnerFun = fun(InnerExpr, _InnerAcc) -> InnerExpr end,
{build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, S), S};
build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, S);
build_inline_each(Ann, Clauses, Expr, {nil, _} = Into, Uniq, S) ->
InnerFun = fun(InnerExpr, InnerAcc) -> {cons, Ann, InnerExpr, InnerAcc} end,
ReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, Into, Uniq, S),
{?remote(Ann, lists, reverse, [ReduceExpr]), S};
{ReduceExpr, SR} = build_reduce(Ann, Clauses, InnerFun, Expr, Into, Uniq, S),
{?remote(Ann, lists, reverse, [ReduceExpr]), SR};
build_inline_each(Ann, Clauses, Expr, {bin, _, []}, Uniq, S) ->
{InnerValue, SV} = build_var(Ann, S),
Generated = erl_anno:set_generated(true, Ann),
@@ -138,8 +138,8 @@ build_inline_each(Ann, Clauses, Expr, {bin, _, []}, Uniq, S) ->
]}
end,
ReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, SV),
{?remote(Ann, erlang, list_to_bitstring, [ReduceExpr]), SV}.
{ReduceExpr, SR} = build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, SV),
{?remote(Ann, erlang, list_to_bitstring, [ReduceExpr]), SR}.
build_into(Ann, Clauses, Expr, {map, _, []}, Uniq, S) ->
{ReduceExpr, SR} = build_inline_each(Ann, Clauses, Expr, {nil, Ann}, Uniq, S),
@@ -161,7 +161,7 @@ build_into(Ann, Clauses, Expr, Into, Uniq, S) ->
?remote(Ann, 'Elixir.Collectable', into, [Into])
},
IntoReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, Acc, Uniq, SD),
{IntoReduceExpr, SN} = build_reduce(Ann, Clauses, InnerFun, Expr, Acc, Uniq, SD),
TryExpr =
{'try', Ann,
@@ -173,7 +173,7 @@ build_into(Ann, Clauses, Expr, Into, Uniq, S) ->
[stacktrace_clause(Ann, Fun, Acc, Kind, Reason, Stack)],
[]},
{{block, Ann, [MatchExpr, TryExpr]}, SD}.
{{block, Ann, [MatchExpr, TryExpr]}, SN}.
stacktrace_clause(Ann, Fun, Acc, Kind, Reason, Stack) ->
{clause, Ann,
@@ -186,7 +186,7 @@ stacktrace_clause(Ann, Fun, Acc, Kind, Reason, Stack) ->
build_reduce(Ann, Clauses, InnerFun, Expr, Into, false, S) ->
{Acc, SA} = build_var(Ann, S),
build_reduce_each(Clauses, InnerFun(Expr, Acc), Into, Acc, SA);
{build_reduce_each(Clauses, InnerFun(Expr, Acc), Into, Acc, SA), SA};
build_reduce(Ann, Clauses, InnerFun, Expr, Into, true, S) ->
%% Those variables are used only inside the anonymous function
%% so we don't need to worry about returning the scope.
@@ -209,7 +209,7 @@ build_reduce(Ann, Clauses, InnerFun, Expr, Into, true, S) ->
]},
EnumReduceCall = build_reduce_each(Clauses, InnerExpr, NewInto, Acc, SU),
?remote(Ann, erlang, element, [{integer, Ann, 1}, EnumReduceCall]).
{?remote(Ann, erlang, element, [{integer, Ann, 1}, EnumReduceCall]), SU}.
build_reduce_each([{enum, Meta, Left, Right, Filters} | T], Expr, Arg, Acc, S) ->
Ann = ?ann(Meta),
+5 -2
View File
@@ -87,9 +87,12 @@ snippet(InputString, Location, StartLine, StartColumn) ->
case lists:keyfind(column, 1, Location) of
{column, Column} ->
Lines = string:split(InputString, "\n", all),
Snippet = elixir_utils:characters_to_binary(lists:nth(Line - StartLine + 1, Lines)),
Snippet = (lists:nth(Line - StartLine + 1, Lines)),
Offset = if Line == StartLine -> Column - StartColumn; true -> Column - 1 end,
#{content => Snippet, offset => Offset};
case string:trim(Snippet, leading) of
[] -> nil;
_ -> #{content => elixir_utils:characters_to_binary(Snippet), offset => Offset}
end;
false ->
nil
+1 -1
View File
@@ -85,6 +85,7 @@ expand({require, Meta, [Ref, Opts]}, S, E) ->
if
is_atom(ERef) ->
elixir_aliases:ensure_loaded(Meta, ERef, ET),
elixir_env:trace({require, Meta, ERef, EOpts}, ET),
{ERef, ST, expand_require(Meta, ERef, EOpts, ET)};
true ->
form_error(Meta, E, ?MODULE, {expected_compile_time_module, require, Ref})
@@ -931,7 +932,6 @@ no_alias_expansion(Other) ->
Other.
expand_require(Meta, Ref, Opts, E) ->
elixir_env:trace({require, Meta, Ref, Opts}, E),
RE = E#{requires := ordsets:add_element(Ref, ?key(E, requires))},
expand_alias(Meta, false, Ref, Opts, RE).
+2 -2
View File
@@ -47,10 +47,10 @@ trace({alias_expansion, _Meta, Lookup, _Result}, #{lexical_tracker := Pid}) ->
?tracker:alias_dispatch(Pid, Lookup),
ok;
trace({require, _Meta, Module, _Opts}, #{lexical_tracker := Pid}) ->
?tracker:add_require(Pid, Module),
?tracker:remote_dispatch(Pid, Module, compile),
ok;
trace({struct_expansion, _Meta, Module, _Keys}, #{lexical_tracker := Pid}) ->
?tracker:add_require(Pid, Module),
?tracker:add_export(Pid, Module),
ok;
trace({alias_reference, _Meta, Module}, #{lexical_tracker := Pid} = E) ->
?tracker:remote_dispatch(Pid, Module, mode(E)),
+21 -11
View File
@@ -640,7 +640,6 @@ map -> struct_op struct_expr eol map_args : {'%', meta_from_token('$1'), ['$2',
Erlang code.
-define(file(), get(elixir_parser_file)).
-define(columns(), get(elixir_parser_columns)).
-define(token_metadata(), get(elixir_token_metadata)).
@@ -729,10 +728,12 @@ build_op(AST, {_Kind, Location, '//'}, Right) ->
build_op({UOp, _, [Left]}, {_Kind, {Line, Column, _} = Location, 'in'}, Right) when ?rearrange_uop(UOp) ->
%% TODO: Remove "not left in right" rearrangement on v2.0
elixir_errors:erl_warn({Line, Column}, ?file(),
warn(
{Line, Column},
"\"not expr1 in expr2\" is deprecated. "
"Instead use \"expr1 not in expr2\" if you require Elixir v1.5+, "
"or \"not(expr1 in expr2)\" if you have to support earlier Elixir versions"),
"or \"not(expr1 in expr2)\" if you have to support earlier Elixir versions"
),
Meta = meta_from_location(Location),
{UOp, Meta, [{'in', Meta, [Left, Right]}]};
@@ -1169,21 +1170,22 @@ error_invalid_kw_identifier({_, Location, KW}) ->
%% TODO: Make this an error on v2.0
warn_trailing_comma({',', {Line, Column, _}}) ->
elixir_errors:erl_warn({Line, Column}, ?file(),
"trailing commas are not allowed inside function/macro call arguments"
).
warn({Line, Column}, "trailing commas are not allowed inside function/macro call arguments").
%% TODO: Make this an error on v2.0
warn_empty_paren({_, {Line, Column, _}}) ->
elixir_errors:erl_warn({Line, Column}, ?file(),
warn(
{Line, Column},
"invalid expression (). "
"If you want to invoke or define a function, make sure there are "
"no spaces between the function name and its arguments. If you wanted "
"to pass an empty block or code, pass a value instead, such as a nil or an atom").
"to pass an empty block or code, pass a value instead, such as a nil or an atom"
).
%% TODO: Make this an error on v2.0
warn_pipe({arrow_op, {Line, Column, _}, Op}, {_, [_ | _], [_ | _]}) ->
elixir_errors:erl_warn({Line, Column}, ?file(),
warn(
{Line, Column},
io_lib:format(
"parentheses are required when piping into a function call. For example:\n\n"
" foo 1 ~ts bar 2 ~ts baz 3\n\n"
@@ -1197,6 +1199,14 @@ warn_pipe(_Token, _) ->
ok.
warn_empty_stab_clause({stab_op, {Line, Column, _}, '->'}) ->
elixir_errors:erl_warn({Line, Column}, ?file(),
warn(
{Line, Column},
"an expression is always required on the right side of ->. "
"Please provide a value after ->").
"Please provide a value after ->"
).
warn(LineColumn, Message) ->
case get(elixir_parser_warning_file) of
nil -> ok;
File -> elixir_errors:erl_warn(LineColumn, File, Message)
end.
+13 -2
View File
@@ -799,10 +799,21 @@ defmodule BaseTest do
|> Enum.shuffle()
|> IO.iodata_to_binary()
allowed_opts =
encode
|> Function.info()
|> Keyword.fetch!(:name)
|> case do
:encode16 -> [:case]
:encode64 -> [:padding]
:url_encode64 -> [:padding]
_ -> [:case, :padding]
end
expected =
data
|> encode.(case: encode_case, pad: pad?)
|> decode.(case: decode_case, pad: pad?)
|> encode.(Keyword.take([case: encode_case, padding: pad?], allowed_opts))
|> decode.(Keyword.take([case: decode_case, padding: pad?], allowed_opts))
assert data == expected,
"identity did not match for #{inspect(data)} when #{inspect(encode)} (#{encode_case})"
@@ -126,23 +126,23 @@ defmodule Code.Formatter.GeneralTest do
"""
formatter = fn content, opts ->
assert opts == [sigil: :W, modifiers: []]
assert opts == [file: nil, line: 1, sigil: :W, modifiers: [], opening_delimiter: "/"]
content |> String.split(~r/ +/) |> Enum.join(" ")
end
assert_format bad, good, sigils: [W: formatter]
bad = """
var = ~W/foo bar baz/abc
var = ~W<foo bar baz>abc
"""
good = """
var = ~W/foo bar baz/abc
var = ~W<foo bar baz>abc
"""
formatter = fn content, opts ->
assert opts == [sigil: :W, modifiers: 'abc']
content |> String.split(~r/ +/) |> Enum.join(" ")
assert opts == [file: nil, line: 1, sigil: :W, modifiers: 'abc', opening_delimiter: "<"]
content |> String.split(~r/ +/) |> Enum.intersperse(" ")
end
assert_format bad, good, sigils: [W: formatter]
@@ -162,34 +162,41 @@ defmodule Code.Formatter.GeneralTest do
"""
formatter = fn content, opts ->
assert opts == [sigil: :W, modifiers: []]
assert opts == [file: nil, line: 1, sigil: :W, modifiers: [], opening_delimiter: "'''"]
content |> String.split(~r/ +/) |> Enum.join(" ")
end
assert_format bad, good, sigils: [W: formatter]
bad = """
bad = ~S'''
if true do
~W'''
~W"""
foo
bar
baz
'''abc
"""abc
end
"""
'''
good = """
good = ~S'''
if true do
~W'''
~W"""
foo
bar
baz
'''abc
"""abc
end
"""
'''
formatter = fn content, opts ->
assert opts == [sigil: :W, modifiers: 'abc']
assert opts == [
file: nil,
line: 2,
sigil: :W,
modifiers: 'abc',
opening_delimiter: ~S/"""/
]
content |> String.split(~r/ +/) |> Enum.join("\n")
end
@@ -915,6 +915,11 @@ defmodule CodeFragmentTest do
assert cc2q("foo(123, ~r/") == s2q("foo(123, __cursor__())")
end
test "no warnings" do
assert cc2q(~s"?\\ ") == s2q("__cursor__()")
assert cc2q(~s"{fn -> end, ") == s2q("{fn -> nil end, __cursor__()}")
end
test "options" do
opts = [columns: true]
assert cc2q("foo(", opts) == s2q("foo(__cursor__())", opts)
@@ -115,6 +115,7 @@ defmodule Code.Normalizer.FormatterASTTest do
test "does not reformat aliases" do
assert_same ~S[:"Elixir.String"]
assert_same ~S[:"Elixir"]
end
test "quoted operators" do
@@ -164,6 +165,12 @@ defmodule Code.Normalizer.FormatterASTTest do
end
end
describe "lists" do
test "on module attribute" do
assert_same ~S"@foo [1]"
end
end
describe "charlists" do
test "without escapes" do
assert_same ~S['']
@@ -542,6 +542,26 @@ defmodule Code.Normalizer.QuotedASTTest do
assert quoted_to_string(quote(do: foo(catch: a))) == "foo(catch: a)"
assert quoted_to_string(quote(do: foo |> [bar: :baz])) == "foo |> [bar: :baz]"
end
test "list in module attribute" do
assert quoted_to_string(
quote do
@foo [1]
end
) == "@foo [1]"
assert quoted_to_string(
quote do
@foo [foo: :bar]
end
) == "@foo foo: :bar"
assert quoted_to_string(
quote do
@foo [1, foo: :bar]
end
) == "@foo [1, foo: :bar]"
end
end
describe "quoted_to_algebra/2 escapes" do
@@ -600,6 +620,7 @@ defmodule Code.Normalizer.QuotedASTTest do
assert quoted_to_string({:__block__, [], [:"a\nb\tc"]}, escape: false) == ~s/:"a\nb\tc"/
assert quoted_to_string({:__block__, [], [:"a\nb\tc"]}) == ~S/:"a\nb\tc"/
assert quoted_to_string(quote(do: :"Elixir")) == "Elixir"
assert quoted_to_string(quote(do: :"Elixir.Foo")) == "Foo"
assert quoted_to_string(quote(do: :"Elixir.Foo.Bar")) == "Foo.Bar"
assert quoted_to_string(quote(do: :"Elixir.foobar")) == ~S/:"Elixir.foobar"/
+15 -10
View File
@@ -52,6 +52,9 @@ defmodule CodeTest do
# The order of which values win is not guaranteed, but it should evaluate successfully.
assert Code.eval_string("b = String.Chars.to_string(a)", a: 0, a: 1) ==
{"1", [{:b, "1"}, {:a, 1}]}
assert Code.eval_string("b = String.Chars.to_string(a)", a: 0, a: 1, c: 2) ==
{"1", [{:c, 2}, {:b, "1"}, {:a, 1}]}
end
test "with many options" do
@@ -75,18 +78,20 @@ defmodule CodeTest do
end
end
test "raises streamlined argument errors" do
assert_raise ArgumentError,
~r"argument error while evaluating at line 1",
fn -> Code.eval_string("a <> b", a: :a, b: :b) end
if System.otp_release() < "25" do
test "raises streamlined argument errors" do
assert_raise ArgumentError,
~r"argument error while evaluating at line 1",
fn -> Code.eval_string("a <> b", a: :a, b: :b) end
assert_raise ArgumentError,
~r"argument error while evaluating example.ex at line 1",
fn -> Code.eval_string("a <> b", [a: :a, b: :b], file: "example.ex") end
assert_raise ArgumentError,
~r"argument error while evaluating example.ex at line 1",
fn -> Code.eval_string("a <> b", [a: :a, b: :b], file: "example.ex") end
assert_raise ArgumentError,
~r"argument error while evaluating example.ex between lines 1 and 2",
fn -> Code.eval_string("a <>\nb", [a: :a, b: :b], file: "example.ex") end
assert_raise ArgumentError,
~r"argument error while evaluating example.ex between lines 1 and 2",
fn -> Code.eval_string("a <>\nb", [a: :a, b: :b], file: "example.ex") end
end
end
end
+5 -1
View File
@@ -423,7 +423,11 @@ defmodule ExceptionTest do
assert blame_message(123, &apply(Kernel, :+, &1)) ==
"you attempted to apply a function named :+ on module Kernel with arguments 123. " <>
"Arguments (the third argument of apply) must always be a list"
"Arguments (the third argument of apply) must always be a proper list"
assert blame_message(123, &apply(Kernel, :+, [&1 | 456])) ==
"you attempted to apply a function named :+ on module Kernel with arguments [123 | 456]. " <>
"Arguments (the third argument of apply) must always be a proper list"
end
test "annotates function clause errors" do
+5 -1
View File
@@ -71,10 +71,14 @@ defmodule Kernel.CLITest do
assert output =~ "Erlang/OTP #{System.otp_release()}"
assert output =~ "Elixir #{System.version()}"
output = iex('--version')
assert output =~ "Erlang/OTP #{System.otp_release()}"
assert output =~ "IEx #{System.version()}"
output = elixir('--version -e "IO.puts(:test_output)"')
assert output =~ "Erlang/OTP #{System.otp_release()}"
assert output =~ "Elixir #{System.version()}"
assert output =~ "test_output"
assert output =~ "Standalone options can't be combined with other options"
end
test "--short-version smoke test" do
@@ -98,6 +98,19 @@ defmodule Kernel.ComprehensionTest do
assert Process.get(:into_halt)
end
test "nested for comprehensions with unique values" do
assert for(x <- [1, 1, 2], uniq: true, do: for(y <- [3, 3], uniq: true, do: x * y)) == [
[3],
[6]
]
assert for(<<x <- "abcabc">>,
uniq: true,
into: "",
do: for(<<y <- "zz">>, uniq: true, into: "", do: to_bin(x) <> to_bin(y))
) == "azbzcz"
end
test "for comprehensions with nilly filters" do
assert for(x <- 1..3, nilly(), do: x * 2) == []
end
@@ -119,6 +119,14 @@ defmodule Kernel.DocsTest do
def two_good_names(first, :ok), do: first
def two_good_names(second, :error), do: second
def really_long_signature(
really_long_var_named_one,
really_long_var_named_two,
really_long_var_named_three
) do
{really_long_var_named_one, really_long_var_named_two, really_long_var_named_three}
end
end
)
@@ -128,6 +136,7 @@ defmodule Kernel.DocsTest do
assert [
arg_names,
only_underscore,
really_long_signature,
two_good_names,
with_defaults,
with_map_and_default,
@@ -141,6 +150,12 @@ defmodule Kernel.DocsTest do
# only_underscore/1
assert {{:only_underscore, 1}, ["only_underscore(_)"]} = only_underscore
# really_long_signature/3
assert {{:really_long_signature, 3},
[
"really_long_signature(really_long_var_named_one, really_long_var_named_two, really_long_var_named_three)"
]} = really_long_signature
# two_good_names/2
assert {{:two_good_names, 2}, ["two_good_names(first, atom)"]} = two_good_names
@@ -491,6 +491,21 @@ defmodule Kernel.ErrorsTest do
assert_eval_raise TokenMissingError, ~r/nofile:1:3: invalid escape \\ at end of file/, '1 \\'
end
test "show snippet on missing tokens" do
assert_eval_raise TokenMissingError,
"nofile:1:25: missing terminator: end (for \"do\" starting at line 1)\n" <>
" |\n" <>
" 1 | defmodule ShowSnippet do\n" <>
" | ^",
'defmodule ShowSnippet do'
end
test "don't show snippet when error line is empty" do
assert_eval_raise TokenMissingError,
"nofile:3:1: missing terminator: end (for \"do\" starting at line 1)",
'defmodule ShowSnippet do\n\n'
end
test "function local conflict" do
assert_eval_raise CompileError,
"nofile:3: imported Kernel.&&/2 conflicts with local function",
@@ -21,8 +21,8 @@ defmodule Kernel.LexicalTrackerTest do
assert D.references(config[:pid]) == {[String], [], [], []}
end
test "can add requires", config do
D.add_require(config[:pid], URI)
test "can add module requires and exports", config do
D.add_export(config[:pid], URI)
assert D.references(config[:pid]) == {[], [URI], [], []}
D.remote_dispatch(config[:pid], URI, :runtime)
@@ -33,7 +33,6 @@ defmodule Kernel.LexicalTrackerTest do
end
test "can add module imports", config do
D.add_require(config[:pid], String)
D.add_import(config[:pid], String, [], 1, true)
D.import_dispatch(config[:pid], String, {:upcase, 1}, :runtime)
@@ -44,7 +43,7 @@ defmodule Kernel.LexicalTrackerTest do
end
test "can add module with {function, arity} imports", config do
D.add_require(config[:pid], String)
D.add_export(config[:pid], String)
D.add_import(config[:pid], String, [upcase: 1], 1, true)
D.import_dispatch(config[:pid], String, {:upcase, 1}, :compile)
@@ -181,18 +180,18 @@ defmodule Kernel.LexicalTrackerTest do
{{compile, _exports, runtime, _}, _binding} =
Code.eval_string("""
defmodule Kernel.LexicalTrackerTest.Defdelegate do
defdelegate a, to: A
defdelegate parse(arg), to: URI
opts = [to: B]
defdelegate b, opts
opts = [to: Range]
defdelegate disjoint?(left, right), opts
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
end |> elem(3)
""")
refute A in compile
assert B in compile
assert A in runtime
refute URI in compile
assert Range in compile
assert URI in runtime
end
test "imports adds an export dependency" do
@@ -209,6 +208,20 @@ defmodule Kernel.LexicalTrackerTest do
refute String in runtime
end
test "requires adds a compile dependency" do
{{compile, exports, runtime, _}, _binding} =
Code.eval_string("""
defmodule Kernel.LexicalTrackerTest.Requires do
require String
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
end |> elem(3)
""")
assert String in compile
refute String in exports
refute String in runtime
end
test "structs are exports or compile time" do
{{compile, exports, runtime, _}, _binding} =
Code.eval_string("""
@@ -521,6 +521,9 @@ defmodule Kernel.QuoteTest.AliasHygieneTest do
assert {:__aliases__, [alias: false], [:Foo, :Bar]} = quote(do: Foo.Bar)
assert {:__aliases__, [alias: false], [:Dict, :Bar]} = quote(do: Dict.Bar)
assert {:__aliases__, [alias: Dict.Bar], [:SuperDict, :Bar]} = quote(do: SuperDict.Bar)
# Edge-case
assert {:__aliases__, _, [Elixir]} = quote(do: Elixir)
end
test "expand aliases" do
@@ -1163,9 +1163,11 @@ defmodule Kernel.WarningTest do
def without_specs(term, options \\ [])
@callback foo :: {:ok, term}
@callback foo(term) :: {:ok, term}
@callback foo(term, keyword) :: {:ok, term, keyword}
@callback foo_when :: {:ok, x} when x: term
@callback foo_when(x) :: {:ok, x} when x: term
@callback foo_when(x, opts) :: {:ok, x, opts} when x: term, opts: keyword
@@ -1178,12 +1180,18 @@ defmodule Kernel.WarningTest do
""")
end)
assert message =~
"cannot define @callback foo/0 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
assert message =~
"cannot define @callback foo/1 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
assert message =~
"cannot define @callback foo/2 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
assert message =~
"cannot define @callback foo_when/0 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
assert message =~
"cannot define @callback foo_when/1 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
+24 -8
View File
@@ -723,8 +723,12 @@ defmodule KernelTest do
result = expand_to_string(quote(do: rand() in 1..2))
assert result =~ "var = rand()"
assert result =~
":erlang.andalso(:erlang.is_integer(var), :erlang.andalso(:erlang.>=(var, 1), :erlang.\"=<\"(var, 2)))"
assert result =~ """
:erlang.andalso(
:erlang.is_integer(var),
:erlang.andalso(:erlang.>=(var, 1), :erlang.\"=<\"(var, 2))
)\
"""
# Empty list
assert expand_to_string(quote(do: :x in [])) =~ "_ = :x\nfalse"
@@ -754,14 +758,26 @@ defmodule KernelTest do
assert expand_to_string(quote(do: foo in [])) ==
"_ = foo\nfalse"
assert expand_to_string(quote(do: foo in [1, 2, 3])) ==
":erlang.orelse(:erlang.orelse(:erlang.\"=:=\"(foo, 1), :erlang.\"=:=\"(foo, 2)), :erlang.\"=:=\"(foo, 3))"
assert expand_to_string(quote(do: foo in [1, 2, 3])) == """
:erlang.orelse(
:erlang.orelse(:erlang.\"=:=\"(foo, 1), :erlang.\"=:=\"(foo, 2)),
:erlang.\"=:=\"(foo, 3)
)\
"""
assert expand_to_string(quote(do: foo in 0..1)) ==
":erlang.andalso(:erlang.is_integer(foo), :erlang.andalso(:erlang.>=(foo, 0), :erlang.\"=<\"(foo, 1)))"
assert expand_to_string(quote(do: foo in 0..1)) == """
:erlang.andalso(
:erlang.is_integer(foo),
:erlang.andalso(:erlang.>=(foo, 0), :erlang.\"=<\"(foo, 1))
)\
"""
assert expand_to_string(quote(do: foo in -1..0)) ==
":erlang.andalso(:erlang.is_integer(foo), :erlang.andalso(:erlang.>=(foo, -1), :erlang.\"=<\"(foo, 0)))"
assert expand_to_string(quote(do: foo in -1..0)) == """
:erlang.andalso(
:erlang.is_integer(foo),
:erlang.andalso(:erlang.>=(foo, -1), :erlang.\"=<\"(foo, 0))
)\
"""
assert expand_to_string(quote(do: foo in 1..1)) ==
":erlang.\"=:=\"(foo, 1)"
+10
View File
@@ -326,6 +326,8 @@ defmodule MacroTest do
end
test "aliases call" do
assert macro_to_string(quote(do: Elixir)) == "Elixir"
assert macro_to_string(quote(do: Foo)) == "Foo"
assert macro_to_string(quote(do: Foo.Bar.baz(1, 2, 3))) == "Foo.Bar.baz(1, 2, 3)"
assert macro_to_string(quote(do: Foo.Bar.baz([1, 2, 3]))) == "Foo.Bar.baz([1, 2, 3])"
assert macro_to_string(quote(do: Foo.bar(<<>>, []))) == "Foo.bar(<<>>, [])"
@@ -810,6 +812,14 @@ defmodule MacroTest do
defmodule Foo.Bar do
assert __MODULE__ in __ENV__.context_modules
end
assert Foo.Bar in __ENV__.context_modules
Code.compile_string("""
defmodule Foo.Bar.Compiled do
true = __MODULE__ in __ENV__.context_modules
end
""")
end
test "to_match/1" do
@@ -119,7 +119,6 @@ defmodule Module.LocalsTrackerTest do
end
test "does not include unreachable locals" do
assert NoPrivate.module_info(:functions) ==
[__info__: 1, baz: 0, module_info: 0, module_info: 1]
assert NoPrivate.module_info(:functions) |> Keyword.take([:foo, :bar, :"MACRO-foo"]) == []
end
end
@@ -200,20 +200,20 @@ defmodule Module.Types.TypesTest do
# types_test.ex:1
is_binary(y)
where "y" was given the same type as "x" in:
where "x" was given the same type as "y" in:
# types_test.ex:1
x = y
where "y" was given the type integer() in:
# types_test.ex:1
is_integer(x)
where "y" was given the type binary() in:
# types_test.ex:1
is_binary(y)
where "x" was given the type integer() in:
# types_test.ex:1
is_integer(x)
"""
end
@@ -256,20 +256,20 @@ defmodule Module.Types.TypesTest do
# types_test.ex:1
is_binary(y)
where "y" was given the same type as "x" in:
where "x" was given the same type as "y" in:
# types_test.ex:1
x = y
where "y" was given the type integer() in:
# types_test.ex:1
is_integer(x)
where "y" was given the type binary() in:
# types_test.ex:1
is_binary(y)
where "x" was given the type integer() in:
# types_test.ex:1
is_integer(x)
"""
end
@@ -443,7 +443,17 @@ defmodule Module.Types.TypesTest do
# types_test.ex:5
%{"id" => user_id} = user
where "user" was given the same type as "amount" in:
where "amount" was given the type binary() in:
# types_test.ex:3
%{"amount" => amount} = event
where "amount" was given the same type as "user" in:
# types_test.ex:4
%{"user" => user} = event
where "user" was given the type binary() in:
# types_test.ex:4
%{"user" => user} = event
@@ -452,11 +462,6 @@ defmodule Module.Types.TypesTest do
# types_test.ex:5
%{"id" => user_id} = user
where "amount" was given the type binary() in:
# types_test.ex:3
%{"amount" => amount} = event
"""
end
@@ -687,5 +692,45 @@ defmodule Module.Types.TypesTest do
end
) == :none
end
test "no-recursion on guards with map fields" do
assert warning(
[assigns],
(
variable_enum = assigns.variable_enum
case true do
_ when variable_enum != nil -> assigns.variable_enum
end
)
) == :none
end
test "other recursive" do
assert warning(
[x, y],
(
key_var = y
%{^key_var => _value} = x
key_var2 = y
%{^key_var2 => _value2} = x
y.z
)
) == :none
end
test "other recursive2" do
assert warning(
[x, y],
(
key_var = y
%{^key_var => _value} = x
key_var2 = y
%{^key_var2 => _value2} = x
key_var3 = y
%{^key_var3 => _value3} = x
)
) == :none
end
end
end
@@ -502,25 +502,25 @@ defmodule Module.Types.UnifyTest do
assert {:ok, {:var, _}, context} = unify({:var, 0}, :tuple, var_context)
assert {:ok, {:var, _}, context} = unify({:var, 1}, {:var, 0}, context)
assert {:ok, {:var, _}, context} = unify({:var, 0}, {:var, 1}, context)
assert context.types[0] == {:var, 1}
assert context.types[1] == :tuple
assert context.types[0] == :tuple
assert context.types[1] == {:var, 0}
assert {:ok, {:var, _}, context} = unify({:var, 0}, {:var, 1}, var_context)
assert {:ok, {:var, _}, context} = unify({:var, 1}, {:var, 2}, context)
assert {:ok, {:var, _}, _context} = unify({:var, 2}, {:var, 0}, context)
assert context.types[0] == :unbound
assert context.types[1] == {:var, 0}
assert context.types[2] == {:var, 1}
assert context.types[0] == {:var, 1}
assert context.types[1] == {:var, 2}
assert context.types[2] == :unbound
assert {:ok, {:var, _}, context} = unify({:var, 0}, {:var, 1}, var_context)
assert {:error, {:unable_unify, {{:var, 0}, {:tuple, 1, [{:var, 0}]}, _}}} =
assert {:error, {:unable_unify, {{:var, 1}, {:tuple, 1, [{:var, 0}]}, _}}} =
unify_lift({:var, 1}, {:tuple, 1, [{:var, 0}]}, context)
assert {:ok, {:var, _}, context} = unify({:var, 0}, {:var, 1}, var_context)
assert {:ok, {:var, _}, context} = unify({:var, 1}, {:var, 2}, context)
assert {:error, {:unable_unify, {{:var, 0}, {:tuple, 1, [{:var, 0}]}, _}}} =
assert {:error, {:unable_unify, {{:var, 2}, {:tuple, 1, [{:var, 0}]}, _}}} =
unify_lift({:var, 2}, {:tuple, 1, [{:var, 0}]}, context)
end
@@ -707,9 +707,9 @@ defmodule Module.Types.UnifyTest do
{{:var, 0}, var_context} = new_var({:foo, [version: 0], nil}, new_context())
{{:var, 1}, var_context} = new_var({:bar, [version: 1], nil}, var_context)
{:ok, {:var, 1}, var_context} = unify({:var, 0}, {:var, 1}, var_context)
assert flatten_union({:var, 0}, var_context) == [{:var, 0}]
assert flatten_union({:var, 1}, var_context) == [{:var, 0}]
{:ok, {:var, 0}, var_context} = unify({:var, 0}, {:var, 1}, var_context)
assert flatten_union({:var, 0}, var_context) == [{:var, 1}]
assert flatten_union({:var, 1}, var_context) == [{:var, 1}]
end
test "format_type/1" do
+14
View File
@@ -896,6 +896,20 @@ defmodule RegistryTest do
assert {%RuntimeError{message: "some error"}, _stacktrace} = error
end
test "send works", %{registry: registry} do
name = {registry, "self"}
Registry.register_name(name, self())
GenServer.cast({:via, Registry, name}, :message)
assert_received {:"$gen_cast", :message}
end
test "send works with value", %{registry: registry} do
name = {registry, "self", "value"}
Registry.register_name(name, self())
GenServer.cast({:via, Registry, name}, :message)
assert_received {:"$gen_cast", :message}
end
defp register_task(registry, key, value) do
parent = self()
+6
View File
@@ -131,6 +131,11 @@ defmodule StringTest do
end
end
test "split_at/2 with invalid guard" do
assert String.split_at(<<?a, 195, 10, ?a>>, 2) == {<<?a, 195>>, <<10, ?a>>}
assert String.split_at(<<107, 205, 135, 184>>, 1) == {<<107, 205, 135>>, <<184>>}
end
test "upcase/1" do
assert String.upcase("123 abcd 456 efg hij ( %$#) kl mnop @ qrst = -_ uvwxyz") ==
"123 ABCD 456 EFG HIJ ( %$#) KL MNOP @ QRST = -_ UVWXYZ"
@@ -652,6 +657,7 @@ defmodule StringTest do
assert String.slice("あいうえお", -10..-15) == ""
assert String.slice("hello あいうえお Unicode", 8..-1) == "うえお Unicode"
assert String.slice("abc", -1..14) == "c"
assert String.slice("a·̀ͯ‿.⁀:", 0..-2) == "a·̀ͯ‿.⁀"
assert_raise FunctionClauseError, fn ->
String.slice(nil, 0..1)
@@ -450,6 +450,15 @@ defmodule Task.SupervisorTest do
assert_receive :done
end
test "consuming from another process", config do
parent = self()
stream = Task.Supervisor.async_stream(config[:supervisor], [1, 2, 3], &send(parent, &1))
Task.start(Stream, :run, [stream])
assert_receive 1
assert_receive 2
assert_receive 3
end
end
describe "async_stream_nolink" do
+9
View File
@@ -829,6 +829,15 @@ defmodule TaskTest do
assert_receive :done
end
test "consuming from another process" do
parent = self()
stream = Task.async_stream([1, 2, 3], &send(parent, &1))
Task.start(Stream, :run, [stream])
assert_receive 1
assert_receive 2
assert_receive 3
end
end
for {desc, concurrency} <- [==: 4, <: 2, >: 8] do
+8
View File
@@ -39,6 +39,14 @@ defmodule PathHelpers do
executable_path("elixirc")
end
def iex(args) do
run_cmd(iex_executable(), args)
end
def iex_executable do
executable_path("iex")
end
def write_beam({:module, name, bin, _} = res) do
File.mkdir_p!(unquote(path))
beam_path = Path.join(unquote(path), Atom.to_string(name) <> ".beam")
+4 -4
View File
@@ -94,7 +94,7 @@ defmodule URITest do
describe "new/1" do
test "empty" do
assert URI.new("") == {:ok, %URI{path: ""}}
assert URI.new("") == {:ok, %URI{}}
end
test "errors on bad URIs" do
@@ -131,7 +131,7 @@ defmodule URITest do
query: nil,
fragment: nil,
port: 443,
path: "",
path: nil,
userinfo: nil
}
@@ -349,9 +349,9 @@ defmodule URITest do
assert URI.merge(base, "") |> to_string == "http://example.com/foo/bar"
assert URI.merge(base, "#fragment") |> to_string == "http://example.com/foo/bar#fragment"
assert URI.merge(base, "?query") |> to_string == "http://example.com/foo/bar?query"
assert URI.merge(base, %URI{path: ""}) |> to_string == "http://example.com/foo/bar"
assert URI.merge(base, %URI{}) |> to_string == "http://example.com/foo/bar"
assert URI.merge(base, %URI{path: "", fragment: "fragment"})
assert URI.merge(base, %URI{fragment: "fragment"})
|> to_string == "http://example.com/foo/bar#fragment"
base = URI.new!("http://example.com")
+3 -2
View File
@@ -663,9 +663,10 @@ defmodule Mix do
config = Keyword.get(opts, :config, [])
system_env = Keyword.get(opts, :system_env, [])
consolidate_protocols? = Keyword.get(opts, :consolidate_protocols, true)
id =
{deps, config, system_env}
{deps, config, system_env, consolidate_protocols?}
|> :erlang.term_to_binary()
|> :erlang.md5()
|> Base.encode16(case: :lower)
@@ -703,7 +704,7 @@ defmodule Mix do
erlc_paths: ["src"],
elixirc_paths: ["lib"],
compilers: [],
consolidate_protocols: Keyword.get(opts, :consolidate_protocols, true)
consolidate_protocols: consolidate_protocols?
]
started_apps = Application.started_applications()
+32 -25
View File
@@ -2,6 +2,7 @@ defmodule Mix.Compilers.Elixir do
@moduledoc false
@manifest_vsn 13
@checkpoint_vsn 2
import Record
@@ -102,7 +103,7 @@ defmodule Mix.Compilers.Elixir do
{false, stale, old_lock, old_config}
end
{stale_local_mods, stale_local_exports, all_local_exports} =
{stale_modules, stale_exports, all_local_exports} =
stale_local_deps(manifest, stale, modified, all_local_exports)
prev_paths = for source(source: source) <- all_sources, do: source
@@ -120,8 +121,8 @@ defmodule Mix.Compilers.Elixir do
all_modules,
all_sources,
removed,
stale_local_mods,
Map.merge(stale_local_exports, removed_modules),
Map.merge(stale_modules, removed_modules),
Map.merge(stale_exports, removed_modules),
dest
)
end
@@ -180,7 +181,7 @@ defmodule Mix.Compilers.Elixir do
delete_compiler_info()
end
else
# We need to return ok if deps_changed? or stale_local_mods changed,
# We need to return ok if deps_changed? or stale_modules changed,
# even if no code was compiled, because we need to propagate the changed
# status to compile.protocols. This will be the case whenever:
#
@@ -193,7 +194,7 @@ defmodule Mix.Compilers.Elixir do
# will only compute the diff with current protocols. In fact, there is no
# need to reconsolidate if an Erlang file changes and it doesn't trigger
# any other change, but the diff check should be reasonably fast anyway.
status = if removed != [] or deps_changed? or stale_local_mods != %{}, do: :ok, else: :noop
status = if removed != [] or deps_changed? or stale_modules != %{}, do: :ok, else: :noop
# If nothing changed but there is one more recent mtime, bump the manifest
if status != :noop or Enum.any?(Map.values(sources_stats), &(elem(&1, 0) > modified)) do
@@ -283,8 +284,8 @@ defmodule Mix.Compilers.Elixir do
all_modules,
all_sources,
removed,
stale_local_mods,
stale_local_exports,
stale_modules,
stale_exports,
dest
) do
# TODO: Use :maps.from_keys/2 on Erlang/OTP 24+
@@ -294,13 +295,17 @@ defmodule Mix.Compilers.Elixir do
into: %{},
do: {module, []}
{checkpoint_stale, checkpoint_modules} = parse_checkpoint(manifest)
modules_to_recompile = Map.merge(checkpoint_modules, modules_to_recompile)
stale_local_mods = Map.merge(checkpoint_stale, stale_local_mods)
{checkpoint_stale_modules, checkpoint_stale_exports, checkpoint_modules} =
parse_checkpoint(manifest)
if map_size(stale_local_mods) != map_size(checkpoint_stale) or
modules_to_recompile = Map.merge(checkpoint_modules, modules_to_recompile)
stale_modules = Map.merge(checkpoint_stale_modules, stale_modules)
stale_exports = Map.merge(checkpoint_stale_exports, stale_exports)
if map_size(stale_modules) != map_size(checkpoint_stale_modules) or
map_size(stale_exports) != map_size(checkpoint_stale_exports) or
map_size(modules_to_recompile) != map_size(checkpoint_modules) do
write_checkpoint(manifest, stale_local_mods, modules_to_recompile)
write_checkpoint(manifest, stale_modules, stale_exports, modules_to_recompile)
end
sources_stats =
@@ -332,8 +337,8 @@ defmodule Mix.Compilers.Elixir do
all_modules,
all_sources,
removed ++ changed,
stale_local_mods,
stale_local_exports,
stale_modules,
stale_exports,
dest
)
@@ -654,16 +659,16 @@ defmodule Mix.Compilers.Elixir do
# files that have changed. Then it recursively figures out
# all the files that changed (via the module dependencies) and
# return the non-changed entries and the removed sources.
defp update_stale_entries(modules, _sources, [], stale_mods, stale_exports, _compile_path)
when stale_mods == %{} and stale_exports == %{} do
defp update_stale_entries(modules, _sources, [], stale_modules, stale_exports, _compile_path)
when stale_modules == %{} and stale_exports == %{} do
{modules, %{}, []}
end
defp update_stale_entries(modules, sources, changed, stale_mods, stale_exports, compile_path) do
defp update_stale_entries(modules, sources, changed, stale_modules, stale_exports, compile_path) do
# TODO: Use :maps.from_keys/2 on Erlang/OTP 24+
changed = Enum.into(changed, %{}, &{&1, []})
reducer = &remove_stale_entry(&1, &2, sources, stale_exports, compile_path)
remove_stale_entries(modules, %{}, changed, stale_mods, reducer)
remove_stale_entries(modules, %{}, changed, stale_modules, reducer)
end
defp remove_stale_entries(modules, exports, old_changed, old_stale, reducer) do
@@ -720,6 +725,8 @@ defmodule Mix.Compilers.Elixir do
defp stale_local_deps(manifest, stale_modules, modified, old_exports) do
base = Path.basename(manifest)
# The stale modules so far will become both stale_modules and stale_exports,
# as any export from a dependency needs to be recompiled.
# TODO: Use :maps.from_keys/2 on Erlang/OTP 24+
stale_modules = for module <- stale_modules, do: {module, []}, into: %{}
@@ -727,7 +734,7 @@ defmodule Mix.Compilers.Elixir do
not scm.fetchable?,
manifest = Path.join([opts[:build], ".mix", base]),
Mix.Utils.last_modified(manifest) > modified,
reduce: {stale_modules, %{}, old_exports} do
reduce: {stale_modules, stale_modules, old_exports} do
{modules, exports, new_exports} ->
{_manifest_modules, dep_sources} = read_manifest(manifest)
@@ -1017,19 +1024,19 @@ defmodule Mix.Compilers.Elixir do
(manifest <> ".checkpoint") |> File.read!() |> :erlang.binary_to_term()
rescue
_ ->
{%{}, %{}}
{%{}, %{}, %{}}
else
{@manifest_vsn, stale, recompile_modules} ->
{stale, recompile_modules}
{@checkpoint_vsn, stale_modules, stale_exports, recompile_modules} ->
{stale_modules, stale_exports, recompile_modules}
_ ->
{%{}, %{}}
{%{}, %{}, %{}}
end
end
defp write_checkpoint(manifest, stale, recompile_modules) do
defp write_checkpoint(manifest, stale_modules, stale_exports, recompile_modules) do
File.mkdir_p!(Path.dirname(manifest))
term = {@manifest_vsn, stale, recompile_modules}
term = {@checkpoint_vsn, stale_modules, stale_exports, recompile_modules}
checkpoint_data = :erlang.term_to_binary(term, [:compressed])
File.write!(manifest <> ".checkpoint", checkpoint_data)
end
+9
View File
@@ -90,6 +90,15 @@ defmodule Mix.Dep.Loader do
# 4. Inferred from files in dependency (mix.exs, rebar.config, Makefile)
manager = opts[:manager] || scm_manager(scm, opts) || manager || infer_manager(opts[:dest])
# TODO: Remove rebar2 support on Elixir v1.15+.
if manager == :rebar do
Mix.shell().error(
"dependency #{inspect(dep.app)} is using Rebar 2, " <>
"which is no longer maintained and no longer works in recent Erlang/OTP versions. " <>
"Remove the :manager option or set it to :rebar3 instead"
)
end
dep = %{dep | manager: manager, status: scm_status(scm, opts)}
{dep, children} =
+13 -2
View File
@@ -609,13 +609,24 @@ defmodule Mix.Release do
end
defp build_release_spec(release, modes) do
%{name: name, version: version, erts_version: erts_version, applications: apps} = release
%{
name: name,
version: version,
erts_version: erts_version,
applications: apps,
options: options
} = release
skip_mode_validation_for =
options
|> Keyword.get(:skip_mode_validation_for, [])
|> MapSet.new()
rel_apps =
for {app, mode} <- modes do
properties = Map.get(apps, app) || throw({:error, "Unknown application #{inspect(app)}"})
children = Keyword.get(properties, :applications, [])
validate_mode!(app, mode, modes, children)
app in skip_mode_validation_for || validate_mode!(app, mode, modes, children)
build_app_for_release(app, mode, properties)
end
+17 -13
View File
@@ -104,11 +104,11 @@ defmodule Mix.Tasks.Format do
The `opts` passed to `format/2` contains all the formatting options and either:
* `:sigil` (atom) - the sigil being formatted, e.g. `:M`.
* `:sigil` (atom) - the sigil being formatted, e.g. `:M`.
* `:modifiers` (charlist) - list of sigil modifiers.
* `:modifiers` (charlist) - list of sigil modifiers.
* `:extension` (string) - the extension of the file being formatted, e.g. `".md"`.
* `:extension` (string) - the extension of the file being formatted, e.g. `".md"`.
Now any application can use your formatter as follows:
@@ -213,6 +213,7 @@ defmodule Mix.Tasks.Format do
to be formatted. The options are returned for reflection
purposes.
"""
@doc since: "1.13.0"
def formatter_for_file(file, opts \\ []) do
{dot_formatter, formatter_opts} = eval_dot_formatter(opts)
@@ -287,7 +288,15 @@ defmodule Mix.Tasks.Format do
end
end
formatter_opts = Keyword.put(formatter_opts, :plugins, plugins)
sigils =
for plugin <- plugins,
sigil <- find_sigils_from_plugins(plugin, formatter_opts),
do: {sigil, &plugin.format(&1, &2 ++ formatter_opts)}
formatter_opts =
formatter_opts
|> Keyword.put(:plugins, plugins)
|> Keyword.put(:sigils, sigils)
if deps == [] and subs == [] do
{{formatter_opts, []}, sources}
@@ -494,12 +503,12 @@ defmodule Mix.Tasks.Format do
ext = Path.extname(file)
cond do
plugin = find_plugin_for_extension(formatter_opts, ext) ->
&plugin.format(&1, [extension: ext, file: file] ++ formatter_opts)
ext in ~w(.ex .exs) ->
&elixir_format(&1, [file: file] ++ formatter_opts)
plugin = find_plugin_for_extension(formatter_opts, ext) ->
&plugin.format(&1, [extension: ext] ++ formatter_opts)
true ->
& &1
end
@@ -536,12 +545,7 @@ defmodule Mix.Tasks.Format do
defp stdin_or_wildcard(path), do: path |> Path.expand() |> Path.wildcard(match_dot: true)
defp elixir_format(content, formatter_opts) do
sigils =
for plugin <- Keyword.fetch!(formatter_opts, :plugins),
sigil <- find_sigils_from_plugins(plugin, formatter_opts),
do: {sigil, &plugin.format(&1, &2 ++ formatter_opts)}
IO.iodata_to_binary([Code.format_string!(content, [sigils: sigils] ++ formatter_opts), ?\n])
IO.iodata_to_binary([Code.format_string!(content, formatter_opts), ?\n])
end
defp find_sigils_from_plugins(plugin, formatter_opts) do
+8
View File
@@ -455,6 +455,14 @@ defmodule Mix.Tasks.Release do
* `:steps` - a list of steps to execute when assembling the release. See
the "Steps" section for more information.
* `:skip_mode_validation_for` - a list of application names
(atoms) specifying applications to skip strict validation of
"unsafe" modes. An "unsafe" case is when a parent application
mode is `:permanent` but one of the applications it depends on
is set to `:load`. Use this with care, as a release with
invalid modes may no longer boot without additional tweaks.
Defaults to `[]`.
Note each release definition can be given as an anonymous function. This
is useful if some release attributes are expensive to compute:
+12 -1
View File
@@ -299,6 +299,7 @@ defmodule Mix.Tasks.Test.Coverage do
print_summary(module_results, totals, summary_opts)
if totals < get_threshold(summary_opts) do
print_failed_threshold(totals, get_threshold(summary_opts))
System.at_exit(fn _ -> exit({:shutdown, 3}) end)
end
@@ -349,7 +350,14 @@ defmodule Mix.Tasks.Test.Coverage do
Mix.shell().info("-----------|--------------------------")
results |> Enum.sort() |> Enum.each(&display(&1, threshold))
Mix.shell().info("-----------|--------------------------")
display({totals, "Total"}, opts)
display({totals, "Total"}, threshold)
Mix.shell().info("")
end
defp print_failed_threshold(totals, threshold) do
Mix.shell().info("Coverage test failed, threshold not met:\n")
Mix.shell().info(" Coverage: #{format_number(totals, 6)}%")
Mix.shell().info(" Threshold: #{format_number(threshold, 6)}%")
Mix.shell().info("")
end
@@ -369,6 +377,9 @@ defmodule Mix.Tasks.Test.Coverage do
defp color(percentage, threshold) when percentage >= threshold, do: :green
defp color(_, _), do: :red
defp format_number(number, length) when is_integer(number),
do: format_number(number / 1, length)
defp format_number(number, length), do: :io_lib.format("~#{length}.2f", [number])
defp format_name(name) when is_binary(name), do: name
+7 -4
View File
@@ -510,13 +510,16 @@ defmodule Mix.Tasks.Xref do
@doc false
def trace({:require, meta, module, _opts}, env),
do: add_trace(:export, :require, module, module, meta, env)
do: add_trace(:compile, :require, module, module, meta, env)
def trace({:import, meta, module, _opts}, env),
do: add_trace(:export, :import, module, module, meta, env)
def trace({:struct_expansion, meta, module, _keys}, env),
do: add_trace(:export, :struct, module, module, meta, env)
def trace({:alias_reference, meta, module}, env) when env.module != module,
do: add_trace(mode(env), :alias, module, module, meta, env)
do: add_trace(mode(env), :module, module, module, meta, env)
def trace({:remote_function, meta, module, function, arity}, env),
do: add_trace(mode(env), :call, module, {module, function, arity}, meta, env)
@@ -551,14 +554,14 @@ defmodule Mix.Tasks.Xref do
# We don't want to show aliases if there is an entry of the same type
non_aliases =
for {_file, _line, module_or_mfa, mode, type} <- entries,
type != :alias,
type != :module,
into: %{},
do: {{trace_module(module_or_mfa), mode}, []}
shell = Mix.shell()
for {file, line, module_or_mfa, mode, type} <- entries,
type != :alias or not Map.has_key?(non_aliases, {module_or_mfa, mode}) do
type != :module or not Map.has_key?(non_aliases, {module_or_mfa, mode}) do
shell.info([
Exception.format_file_line(Path.relative_to_cwd(file), line),
?\s,
+1
View File
@@ -4,3 +4,4 @@
/git_rebar/
/git_repo/
/git_sparse_repo/
/archive/ebin/
-3
View File
@@ -1,3 +0,0 @@
{application,local_sample,
[{modules,['Elixir.Mix.Tasks.Local.Sample']},
{applications,[kernel,stdlib,elixir]}]}.
BIN
View File
Binary file not shown.
+5 -2
View File
@@ -496,8 +496,11 @@ defmodule Mix.DepTest do
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
assert Enum.map(Mix.Dep.load_on_environment([]), & &1.app) ==
[:git_repo, :abc_repo, :deps_repo]
# Both orders below are valid after topological sort
assert Enum.map(Mix.Dep.load_on_environment([]), & &1.app) in [
[:git_repo, :abc_repo, :deps_repo],
[:abc_repo, :git_repo, :deps_repo]
]
assert Map.keys(Mix.Project.deps_paths()) == [:abc_repo, :deps_repo, :git_repo]
+5 -3
View File
@@ -202,7 +202,7 @@ defmodule Mix.RebarTest do
# We run only on Unix because Windows has a hard time
# removing the Rebar executable after executed.
@tag [unix: true]
@tag :unix
test "gets and compiles dependencies" do
in_tmp("get and compile dependencies", fn ->
Mix.Project.push(RebarAsDep)
@@ -256,7 +256,7 @@ defmodule Mix.RebarTest do
# We run only on Unix because Windows has a hard time
# removing the Rebar executable after executed.
@tag [unix: true]
@tag :unix
test "applies variables from :system_env option when compiling dependencies" do
in_tmp("applies variables from system_env", fn ->
Mix.Project.push(RebarAsDepWithEnv)
@@ -295,6 +295,7 @@ defmodule Mix.RebarTest do
File.rm(MixTest.Case.tmp_path("rebar_dep/mix.exs"))
end
@tag :rebar
test "gets and compiles dependencies with Rebar2" do
in_tmp("get and compile dependencies for Rebar2", fn ->
Mix.Project.push(Rebar2AsDep)
@@ -328,7 +329,8 @@ defmodule Mix.RebarTest do
# We run only on Unix because Windows has a hard time
# removing the Rebar executable after executed.
@tag [unix: true]
@tag :unix
@tag :rebar
test "applies variables from :system_env option when compiling dependencies for Rebar2" do
in_tmp("applies variables from system_env for Rebar2", fn ->
Mix.Project.push(Rebar2AsDep)
+7
View File
@@ -479,6 +479,13 @@ defmodule Mix.ReleaseTest do
assert message =~
"Application :mix has mode :permanent but it depends on :elixir which is set to :none"
end
test "does not raise on child unsafe mode if parent is in `skip_mode_validation_for`" do
# `:mix` is a parent that depends on `:elixir`
release = release(applications: [elixir: :load], skip_mode_validation_for: [:mix])
assert make_boot_script(release, @boot_script_path, release.boot_scripts.start) == :ok
assert make_boot_script(release, @boot_script_path, release.boot_scripts.start_clean) == :ok
end
end
describe "make_cookie/1" do
@@ -182,6 +182,11 @@ defmodule Mix.Tasks.Compile.ElixirTest do
Process.put({MixTest.Case.Sample, :application}, extra_applications: [:logger])
File.mkdir_p!("config")
File.write!("config/config.exs", """
import Config
config :logger, :level, :debug
""")
File.write!("lib/a.ex", """
defmodule A do
_ = Logger.metadata()
@@ -234,6 +239,12 @@ defmodule Mix.Tasks.Compile.ElixirTest do
refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
assert File.stat!("_build/dev/lib/sample/.mix/compile.elixir").mtime > @old_time
# No-op does not recompile
File.touch!("_build/dev/lib/sample/.mix/compile.elixir", @old_time)
assert recompile.() == {:ok, []}
refute_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
# Changing self fully recompiles
File.write!("config/config.exs", """
import Config
@@ -263,6 +274,76 @@ defmodule Mix.Tasks.Compile.ElixirTest do
Application.delete_env(:sample, :foo, persistent: true)
end
test "recompiles files when config changes export dependencies" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
Process.put({MixTest.Case.Sample, :application}, extra_applications: [:ex_unit])
File.mkdir_p!("config")
File.write!("lib/a.ex", """
defmodule A do
def test_struct do
%ExUnit.Test{}
end
end
""")
assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
assert_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
recompile = fn ->
Mix.ProjectStack.pop()
Mix.Project.push(MixTest.Case.Sample)
Mix.Tasks.Loadconfig.load_compile("config/config.exs")
Mix.Tasks.Compile.Elixir.run(["--verbose"])
end
# Adding config recompiles
File.write!("config/config.exs", """
import Config
config :ex_unit, :some, :config
""")
File.touch!("_build/dev/lib/sample/.mix/compile.elixir", @old_time)
assert recompile.() == {:ok, []}
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
assert File.stat!("_build/dev/lib/sample/.mix/compile.elixir").mtime > @old_time
# Changing config recompiles
File.write!("config/config.exs", """
import Config
config :ex_unit, :some, :another
""")
File.touch!("_build/dev/lib/sample/.mix/compile.elixir", @old_time)
assert recompile.() == {:ok, []}
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
assert File.stat!("_build/dev/lib/sample/.mix/compile.elixir").mtime > @old_time
# Removing config recompiles
File.write!("config/config.exs", """
import Config
""")
File.touch!("_build/dev/lib/sample/.mix/compile.elixir", @old_time)
assert recompile.() == {:ok, []}
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
assert File.stat!("_build/dev/lib/sample/.mix/compile.elixir").mtime > @old_time
# No-op does not recompile
File.touch!("_build/dev/lib/sample/.mix/compile.elixir", @old_time)
assert recompile.() == {:ok, []}
refute_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
end)
after
Application.delete_env(:ex_unit, :some, persistent: true)
end
test "recompiles files when config changes with crashes" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
+6 -1
View File
@@ -206,6 +206,8 @@ defmodule Mix.Tasks.FormatTest do
assert opts[:from_formatter_exs] == :yes
assert opts[:sigil] == :W
assert opts[:modifiers] == 'abc'
assert opts[:line] == 2
assert opts[:file] =~ ~r/\/a\.ex$/
contents |> String.split(~r/\s/) |> Enum.join("\n")
end
end
@@ -247,12 +249,15 @@ defmodule Mix.Tasks.FormatTest do
def features(opts) do
assert opts[:from_formatter_exs] == :yes
[extensions: ~w(.w)]
[extensions: ~w(.w), sigils: [:W]]
end
def format(contents, opts) do
assert opts[:from_formatter_exs] == :yes
assert opts[:extension] == ".w"
assert opts[:file] =~ ~r/\/a\.w$/
assert [W: sigil_fun] = opts[:sigils]
assert is_function(sigil_fun, 2)
contents |> String.split(~r/\s/) |> Enum.join("\n")
end
end
+14 -2
View File
@@ -168,7 +168,14 @@ defmodule Mix.Tasks.TestTest do
# We skip a test in bar to force coverage below the default threshold
# which should result in an exit status of 1.
assert {_, code} = mix_code(["test", "--cover", "--exclude", "maybe_skip"])
assert {output, code} = mix_code(["test", "--cover", "--exclude", "maybe_skip"])
assert output =~ """
Coverage test failed, threshold not met:
Coverage: 0.00%
Threshold: 90.00%
"""
unless windows?() do
assert code == 3
@@ -426,9 +433,14 @@ defmodule Mix.Tasks.TestTest do
in_fixture("umbrella_test", fn ->
# Run false positive test first so at least the code is compiled
# and we can perform more aggressive assertions later
assert mix(["test", "apps/unknown_app/test"]) =~ """
output = mix(["test", "apps/unknown_app/test"])
assert output =~ """
==> bar
Paths given to "mix test" did not match any directory/file: apps/unknown_app/test
"""
assert output =~ """
==> foo
Paths given to "mix test" did not match any directory/file: apps/unknown_app/test
"""
+6 -7
View File
@@ -253,7 +253,7 @@ defmodule Mix.Tasks.XrefTest do
output = """
Compiling 2 files (.ex)
Generated sample app
lib/b.ex:2: require A (export)
lib/b.ex:2: import A (export)
lib/b.ex:3: call A.macro/0 (compile)
lib/b.ex:4: import A.macro/0 (compile)
lib/b.ex:5: call A.fun/0 (compile)
@@ -287,6 +287,7 @@ defmodule Mix.Tasks.XrefTest do
output = """
Compiling 2 files (.ex)
Generated sample app
lib/b.ex:2: require A (compile)
lib/b.ex:3: call A.macro/0 (compile)
"""
@@ -313,10 +314,11 @@ defmodule Mix.Tasks.XrefTest do
output = """
Compiling 2 files (.ex)
Generated sample app
lib/b.ex:3: require A (compile)
lib/b.ex:3: call A.macro/0 (compile)
"""
message = "Too many traces (found: 1, permitted: 0)"
message = "Too many traces (found: 2, permitted: 0)"
assert_raise Mix.Error, message, fn ->
assert_trace(~w[--label compile --fail-above 0], "lib/b.ex", files, output)
@@ -759,10 +761,7 @@ defmodule Mix.Tasks.XrefTest do
defmodule B do
# Let's also test that we track literal atom behaviours
@behaviour :"Elixir.A.Behaviour"
def foo do
A.foo()
end
def foo, do: :foo
end
""")
@@ -772,7 +771,7 @@ defmodule Mix.Tasks.XrefTest do
digraph "xref graph" {
"lib/a.ex"
"lib/a.ex" -> "lib/b.ex" [label="(compile)"]
"lib/b.ex" -> "lib/a.ex" [label="(export)"]
"lib/b.ex" -> "lib/a.ex"
"lib/b.ex"
}
"""
+1 -1
View File
@@ -391,7 +391,7 @@ defmodule Mix.UmbrellaTest do
assert_received {:mix_shell, :info, ["Compiled lib/bar.ex"]}
# But exports dependencies are not recompiled
File.write!("lib/bar.ex", "defmodule Bar, do: (require Foo)")
File.write!("lib/bar.ex", "defmodule Bar, do: (import Foo, warn: false)")
assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
assert_received {:mix_shell, :info, ["Compiled lib/bar.ex"]}
+26 -5
View File
@@ -7,12 +7,13 @@ Application.put_env(:logger, :backends, [])
os_exclude = if match?({:win32, _}, :os.type()), do: [unix: true], else: [windows: true]
epmd_exclude = if match?({:win32, _}, :os.type()), do: [epmd: true], else: []
ExUnit.start(trace: "--trace" in System.argv(), exclude: epmd_exclude ++ os_exclude)
git_exclude = if Mix.SCM.Git.git_version() <= {1, 7, 4}, do: [git_sparse: true], else: []
rebar_exclude = if System.otp_release() >= "25", do: [rebar: true], else: []
unless {1, 7, 4} <= Mix.SCM.Git.git_version() do
IO.puts(:stderr, "Skipping tests with git sparse checkouts...")
ExUnit.configure(exclude: :git_sparse)
end
ExUnit.start(
trace: "--trace" in System.argv(),
exclude: epmd_exclude ++ os_exclude ++ git_exclude ++ rebar_exclude
)
# Clear environment variables that may affect tests
System.delete_env("http_proxy")
@@ -412,6 +413,26 @@ Enum.each([:invalidapp, :invalidvsn, :noappfile, :nosemver, :ok], fn dep ->
File.mkdir_p!(Path.expand("fixtures/deps_status/deps/#{dep}/.git", __DIR__))
end)
# Archive ebin
target = Path.expand("fixtures/archive", __DIR__)
unless File.dir?(Path.join(target, "ebin")) do
File.mkdir_p!(Path.join(target, "ebin"))
File.write!(Path.join([target, "ebin", "local_sample.app"]), """
{application,local_sample,
[
{modules,['Elixir.Mix.Tasks.Local.Sample']},
{applications,[kernel,stdlib,elixir]}
]
}.
""")
[{name, bin}] = Code.compile_file("lib/local.sample.ex", target)
File.write!(Path.join([target, "ebin", Atom.to_string(name) <> ".beam"]), bin)
end
## Generate helper modules
path = MixTest.Case.tmp_path("beams")