Compare commits

...
49 Commits
Author SHA1 Message Date
José Valim 4e6d67c169 Add branch docs publishing 2023-05-30 11:41:49 +02:00
jendis 614ae049e6 Fix elixir test assertion for erlang >=OTP-24.1 (#11311) 2021-10-14 16:43:34 +02:00
MatteoandJosé Valim b1492cb0cf Fix logger translator with otp-24 (#11222)
* Handle new significant key in sasl reports

* Add OTP-24.0 to CI matrix

Co-authored-by: José Valim <jose.valim@dashbit.co>
2021-08-31 23:46:27 +02:00
Wojtek Mach 237a71311c Update warnings for OTP master (#10807) 2021-08-31 19:27:51 +02:00
José Valim e40b280fd8 Print task times on MIX_DEBUG=1 2021-06-01 11:43:49 +02:00
José Valim 308255bda8 Release v1.11.4 2021-03-16 12:25:59 +01:00
José Valim 1e81a5b9a5 Run formatter 2021-02-28 01:33:19 +01:00
José Valim 0627d7b0d4 Pass env and target to Mix.Config readers 2021-02-27 21:52:56 +01:00
Wojtek Mach 99ec7522d3 Backport OTP 24 compatibility patches to v1.11 (#10743)
* Update Erlang warnings translation (#10694)

Since https://github.com/erlang/otp/commit/8ecc648df22dbfe9dd68a3e2b968ba2b6a2ca7fe
instead of `{eval_failure, Reason}` we'd get `{eval_failure, Call, Reason}`

* Fix translating erl compiler errors to diagnostics on OTP 24 (#10719)

The compiler previously emitted just `line` and now emits `{line,
column}`.

The diagnostic struct accepts as position either:

    nil
    line
    {start_line, start_col, end_line, end_col}

so we could have used the `column` to create the 4-tuple but we don't
have enough information to do that correctly, we don't know where the
line ends.

* Fix remaining warning translations for OTP 24 (#10720)

Yecc error message changed so we need to loosen up our assertion:

OTP 23

    iex> iex
    Erlang/OTP 23 [erts-11.1.7] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1]

    Interactive Elixir (1.11.3) - press Ctrl+C to exit (type h() ENTER for help)
    iex(1)> :yecc.file('f.yrl')
    f.yrl:1: syntax error before: '.'
    :error

OTP 24

    iex> iex
    Erlang/OTP 24 [DEVELOPMENT] [erts-11.1.7] [source-802d2c5083] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1]

    Interactive Elixir (1.12.0-dev) - press Ctrl+C to exit (type h() ENTER for help)
    iex(1)> :yecc.file('f.yrl')
    f.yrl:1:5: syntax error before: .

    :error

* Support 16bit floats in bitstrings (#10740)

On OTP 24:

    iex> <<x::float-16>> = <<60, 0>>
    iex> x
    1.0
    iex> <<x::float-16>>
    <<60, 0>>

Before OTP 24 we'd get errors or wouldn't match:

    iex> <<1.0::float-16>>
    ** (ArgumentError) argument error while evaluating iex at line 1

    <<x::float-16>> = <<60, 0>>
    ** (MatchError) no match of right hand side value: <<60, 0>>

    iex> (fn <<x::float-16>> -> x; _ -> :nomatch end).(<<60, 0>>)
    :nomatch

* Handle :compile.file/2 returning :error on OTP 24 (#10742)

Before OTP 24 when given invalid args, :compile.file/2 would return:

    iex(1)> :compile.file('a.erl', [{:d, 'foo', 'bar'}, :report])
    {:error, :badarg}

On OTP 24:

    iex(1)> :compile.file('a.erl', [{:d, 'foo', 'bar'}, :report])

    *** Internal compiler error ***
    exception error: bad argument
      in function  io_lib:format/2
         called as io_lib:format("badly formed '~s'",[{"foo","bar"}])
      in call from sys_messages:list_errors/3 (sys_messages.erl, line 53)
      in call from lists:foreach/2 (lists.erl, line 1342)
      in call from compile:comp_ret_err/1 (compile.erl, line 546)
      in call from compile:'-internal_fun/2-anonymous-0-'/2 (compile.erl, line 229)
      in call from compile:'-do_compile/2-anonymous-0-'/1 (compile.erl, line 219)
    :error
2021-02-20 14:33:11 +01:00
José Valim d30c5c0185 Release v1.11.3 2021-01-04 16:47:18 +01:00
José Valim 3fc334ebc3 Support all literals in guards, closes #10613 2021-01-02 17:19:30 +01:00
Julian Doherty df8955c0dd Improve Enum.chunk_while/4 docs to clarify after_fun semantics (#10595)
- add `{:halt, acc}` tuple return doc for `chunk_fun`
- explain `after_fun` semantics in more detail
- format return tuples for `chunk_fun` and `after_fun` as markdown lists
- added note that `acc` in `after_fun` return tuple is ignored
- note that `Enum.chunk_while/4` returns a list of *chunks*, not a list
  of lists (emitted chunks can be anything, not necessarily just lists)
2021-01-02 17:09:25 +01:00
José Valim 6b87a54825 Clarify writing documentation guide, closes #10610 2021-01-02 17:09:20 +01:00
José Valim 72ec902554 Revert "Add support for new rebar3 output env option (#10018)"
This option will be enabled on Elixir v1.12 instead.

This reverts commit fc9f7690c1.
2020-12-21 13:01:34 +01:00
José Valim 2c9fff0f93 Update CHANGELOG 2020-12-17 13:23:40 +01:00
José Valim c42f7af02b Do not match on error_info on OTP 24 2020-12-17 13:21:32 +01:00
José Valim 9c77960a26 Consider macros and module attributes on the left side of ++ on ExUnit.Diff
Closes #10520
2020-12-17 13:17:19 +01:00
José Valim a20f413841 Do not warn for attributes used in after_compile
Closes #10579.
2020-12-17 13:08:07 +01:00
José Valim ffbae20014 Improve xref error message, closes #10582 2020-12-17 08:18:16 +01:00
José Valim 9c81578ab5 Update CHANGELOG 2020-12-13 09:24:38 +01:00
José Valim a9f7b8e18c Run formatter 2020-12-13 09:15:02 +01:00
José Valim 61c3c34f44 Do not discard context from with, closes #10538 2020-12-13 09:14:39 +01:00
José Valim d8faf5bb7b Fix bootstrapping 2020-12-13 09:13:47 +01:00
José Valim 9d03032cfd Add line and column to EEx tokenizer 2020-12-13 09:06:15 +01:00
José Valim 3c4c68b3d5 Fix variable overriding in records, closes #10540 2020-12-13 09:05:09 +01:00
José Valim bed0a68176 Do not use unicode chars if ANSI is disabled
See https://github.com/elixir-lang/elixir/issues/10481#issuecomment-742448132
2020-12-10 12:19:19 +01:00
José Valim 60642c340d Improve error message on do-block in module attr, closes #10544 2020-12-10 12:19:14 +01:00
Eric Meadows-Jönsson 35e4263934 Fix docs tests on OTP 23 (#10510) 2020-11-29 10:19:53 +01:00
José Valim 43327512da Do not use private and removed ram_file:compress/1 (#10439) 2020-11-29 10:19:22 +01:00
José Valim 32692f14ff Fix test on Erlang master 2020-11-29 09:51:00 +01:00
Eric Meadows-Jönsson f80bd3c478 Fix intermittent test failure caused by archive warning (#10493) 2020-11-29 09:43:36 +01:00
José Valim 23934b48ba Fix failure on Erlang/OTP master 2020-11-29 09:43:28 +01:00
Dominic Letz c370a8a724 Add :compressed to stream_modes spec (#10490)
:compressed is already supported here so that gzipped files
can be streamed `File.stream!("log.gz", [:compressed])`.
2020-11-29 09:43:17 +01:00
José Valim 8c2237f80d Handle in memory modules in Xref.calls, closes #10502 2020-11-21 08:34:30 +01:00
José Valim a5e9c39023 Do not raise when checking for ambiguity with static_atoms_encoder, closes #10501 2020-11-20 12:43:14 +01:00
José Valim 6d918f5fb4 Wrap around :file instead of calling internals 2020-11-20 07:14:43 +01:00
Eksperimental 4347468d8a Update GitHub actions/checkout to v2 (#10496) 2020-11-18 13:21:49 +01:00
José Valim f044def5a1 Handle recursive vars in map values (#10498) 2020-11-18 12:42:23 +01:00
José Valim 05c9ccad12 Check for stacktrace at compile-time and runtime 2020-11-03 20:18:50 +01:00
José Valim 7533e56c1b Release v1.11.2 2020-11-03 15:23:56 +01:00
José Valim 3f083795b6 Do not trace protocol impl checks
A more complete solution is found in master but this
approach is less disruptive in the v1.11 branch.
2020-11-02 11:35:51 +01:00
José Valim 1bbc959d1a Prune tracers when fetching __ENV__ inside functions, closes #10466 2020-10-30 13:30:17 +01:00
José Valim 1603f21b0a Fix race condition when loading test helper, closes #10464 2020-10-29 19:29:06 +01:00
José Valim 02c3957667 Clarify wording about siblings in umbrella 2020-10-22 10:53:14 +02:00
José Valim eeab9f5aa1 Ensure manually set tracers are kept on compilation
Closes #10451.
2020-10-22 08:50:10 +02:00
José Valim 3f3e987a23 Handle aliases with code recursion
It addresses a regression introduced in 97e8b0242d.

Closes https://github.com/nerves-project/nerves/issues/557.
2020-10-20 20:33:58 +02:00
José Valim 53c8b27ee2 Fix bootstrap issue in Code module 2020-10-17 20:10:26 +02:00
José Valim 3550dd10af Do not crash when getting docs for missing erts appdir
Closes #10437.
2020-10-17 19:46:51 +02:00
José Valim 7e44b88122 Raise for is_record guards, closes #10436 2020-10-17 19:33:10 +02:00
69 changed files with 1088 additions and 455 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
+7 -6
View File
@@ -13,8 +13,9 @@ jobs:
name: Linux, ${{ matrix.otp_release }}, Ubuntu 16.04
continue-on-error: ${{ matrix.development }}
strategy:
fail-fast: false
matrix:
otp_release: ['OTP-23.0', 'OTP-22.3', 'OTP-22.0', 'OTP-21.3.8', 'OTP-21.0']
otp_release: ['OTP-24.0', 'OTP-23.0', 'OTP-22.3', 'OTP-22.0', 'OTP-21.3.8', 'OTP-21.0']
development: [false]
include:
- otp_release: master
@@ -23,17 +24,17 @@ jobs:
development: true
runs-on: ubuntu-16.04
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
with:
fetch-depth: 50
- name: Install Erlang/OTP
run: |
cd $RUNNER_TEMP
wget -O otp.tar.gz https://repo.hex.pm/builds/otp/ubuntu-14.04/${{ matrix.otp_release }}.tar.gz
wget -O otp.tar.gz https://repo.hex.pm/builds/otp/ubuntu-16.04/${{ matrix.otp_release }}.tar.gz
mkdir -p otp
tar zxf otp.tar.gz -C otp --strip-components=1
otp/Install -minimal $(pwd)/otp
echo "::add-path::$(pwd)/otp/bin"
echo "$(pwd)/otp/bin" >> $GITHUB_PATH
- name: Compile Elixir
run: |
rm -rf .git
@@ -61,7 +62,7 @@ jobs:
steps:
- name: Configure Git
run: git config --global core.autocrlf input
- uses: actions/checkout@v1
- uses: actions/checkout@v2
with:
fetch-depth: 50
- name: Cache Erlang/OTP package
@@ -88,7 +89,7 @@ jobs:
name: Check POSIX-compliant
runs-on: ubuntu-16.04
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
with:
fetch-depth: 50
- name: Install Shellcheck
@@ -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
+64 -2
View File
@@ -14,7 +14,7 @@ Elixir v1.11 builds on top of the recently added compilation tracers to track ap
These two conditions may seem contradictory. After all, if a module is available, it must have come from a dependency. This is not true in two scenarios:
* Modules from Elixir and Erlang/OTP are always available - even if their applications are not explicitly listed as a dependency
* Modules from Elixir and Erlang/OTP are always available - even if their applications are not listed as a dependency
* In an umbrella project, because all child applications are compiled within the same VM, you may have a module from a sibling project available, even if you don't depend on said sibling
@@ -36,7 +36,7 @@ on :ssl. To fix this, you must do one of:
to your "def project" in mix.exs
```
This comes with extra benefits in umbrella projects, as it requires child applications to explicitly list their dependencies, completely rejecting cyclic dependencies between siblings.
This comes with extra benefits in umbrella projects, as it requires applications to depend on the siblings they depend on, which will fail if there are any cyclic dependencies.
## Compiler checks: data constructors
@@ -243,6 +243,68 @@ The Calendar module ships with a new `Calendar.strftime/3` function, which provi
Mix also includes two new tasks: `mix app.config`, for application runtime configuration, and `mix test.coverage`, which generates aggregated coverage reports for umbrella projects and for test suites partitioned across processes.
## v1.11.4 (2021-03-16)
This release introduces fixes for better support of Erlang/OTP 24+.
### 1. Enhancements
#### Elixir
* [Kernel] Update formatting when printing warnings and errors from Erlang/OTP 24+
* [Kernel] Support float-16 on bitstrings
#### Mix
* [mix local.rebar] This task will now install rebar3 version 3.14.4, compiled with Erlang/OTP 21
## v1.11.3 (2021-01-04)
### 1. Enhancements
#### Elixir
* [Macro] Add `Macro.unique_var/2` and `Macro.generate_unique_arguments/2`
### 2. Bug fixes
#### Elixir
* [Code] Do not raise when checking for operator ambiguity when `:static_atoms_encoder` is set in `Code.string_to_quoted`
* [Kernel] Emit undefined function warnings from `with`
* [Kernel] Do not fail type checking when literals are used in guards
* [Module] Do not warn for attributes used in `@after_compile`
* [Record] Make sure nested record names do not clobber each other
#### ExUnit
* [ExUnit.Assertions] Do not crash if there are macros and module attributes on the left side of `++`
#### IEx
* [IEx.Helpers] Do not use Unicode chars if ANSI is disabled
#### Mix
* [mix deps.compile] Fix compatibility with rebar v3.14
* [mix release] Do not use private `ram_file:compress/1`
* [mix xref] Do not crash when retrieving calls for modules in memory
## v1.11.2 (2020-11-03)
### 1. Bug fixes
#### Elixir
* [Code] Do not crash when getting docs for missing `erts` appdir
* [Kernel] Raise meaningful error if `:erlang.is_record` is used in guards
* [Kernel] Prune tracers when fetching `__ENV__` inside functions
#### Mix
* [mix] Fix regression where aliases could not call themselves recursively
* [mix compile] Do not discard tracers that are set programatically
## v1.11.1 (2020-10-16)
### 1. Bug fixes
+25 -17
View File
@@ -178,40 +178,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 = 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)
$(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)
$(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)
$(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)
$(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)
$(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 +228,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.11.1
1.11.4
+2 -2
View File
@@ -41,7 +41,7 @@ defmodule EEx.Compiler do
# Generates the buffers by handling each expression from the tokenizer.
# It returns Macro.t/0 or it raises.
defp generate_buffer([{:text, chars} | rest], buffer, scope, state) do
defp generate_buffer([{:text, _line, _column, chars} | rest], buffer, scope, state) do
buffer = state.engine.handle_text(buffer, IO.chardata_to_string(chars))
generate_buffer(rest, buffer, scope, state)
end
@@ -179,7 +179,7 @@ defmodule EEx.Compiler do
# Look middle expressions that immediately follow a start_expr
defp look_ahead_middle(
[{:text, text}, {:middle_expr, line, _column, _, chars} | rest] = tokens,
[{:text, _, _, text}, {:middle_expr, line, _, _, chars} | rest] = tokens,
start,
contents
) do
+48 -26
View File
@@ -6,7 +6,7 @@ defmodule EEx.Tokenizer do
@type column :: non_neg_integer
@type marker :: '=' | '/' | '|' | ''
@type token ::
{:text, content}
{:text, line, column, content}
| {:expr | :start_expr | :middle_expr | :end_expr, line, column, marker, content}
| {:eof, line, column}
@@ -17,7 +17,7 @@ defmodule EEx.Tokenizer do
It returns {:ok, list} with the following tokens:
* `{:text, content}`
* `{:text, line, column, content}`
* `{:expr, line, column, marker, content}`
* `{:start_expr, line, column, marker, content}`
* `{:middle_expr, line, column, marker, content}`
@@ -36,8 +36,11 @@ defmodule EEx.Tokenizer do
def tokenize(list, line, column, opts)
when is_list(list) and is_integer(line) and line >= 0 and is_integer(column) and column >= 0 do
column = opts.indentation + column
{list, line, column} = (opts.trim && trim_init(list, line, column)) || {list, line, column}
tokenize(list, line, column, opts, [], [])
{list, line, column} =
(opts.trim && trim_init(list, line, column, opts)) || {list, line, column}
tokenize(list, line, column, opts, [{line, column}], [])
end
defp tokenize('<%%' ++ t, line, column, opts, buffer, acc) do
@@ -53,7 +56,8 @@ defmodule EEx.Tokenizer do
{rest, new_line, new_column, buffer} =
trim_if_needed(rest, new_line, new_column, opts, buffer)
tokenize(rest, new_line, new_column, opts, buffer, acc)
acc = tokenize_text(buffer, acc)
tokenize(rest, new_line, new_column, opts, [{new_line, new_column}], acc)
end
end
@@ -76,7 +80,7 @@ defmodule EEx.Tokenizer do
acc = tokenize_text(buffer, acc)
final = {key, line, column, marker, expr}
tokenize(rest, new_line, new_column, opts, [], [final | acc])
tokenize(rest, new_line, new_column, opts, [{new_line, new_column}], [final | acc])
end
end
@@ -164,49 +168,67 @@ defmodule EEx.Tokenizer do
# Tokenize the buffered text by appending
# it to the given accumulator.
defp tokenize_text([], acc) do
defp tokenize_text([{_line, _column}], acc) do
acc
end
defp tokenize_text(buffer, acc) do
[{:text, Enum.reverse(buffer)} | acc]
[{line, column} | buffer] = Enum.reverse(buffer)
[{:text, line, column, buffer} | acc]
end
defp trim_if_needed(rest, line, column, opts, buffer) do
if opts.trim do
buffer = trim_left(buffer, 0)
{rest, line, column} = trim_right(rest, line, column, 0)
{rest, line, column} = trim_right(rest, line, column, 0, opts)
{rest, line, column, buffer}
else
{rest, line, column, buffer}
end
end
defp trim_init([h | t], line, column) when h in @spaces, do: trim_init(t, line, column + 1)
defp trim_init([?\r, ?\n | t], line, _column), do: trim_init(t, line + 1, 1)
defp trim_init([?\n | t], line, _column), do: trim_init(t, line + 1, 1)
defp trim_init([?<, ?% | _] = rest, line, column), do: {rest, line, column}
defp trim_init(_, _, _), do: false
defp trim_init([h | t], line, column, opts) when h in @spaces,
do: trim_init(t, line, column + 1, opts)
defp trim_init([?\r, ?\n | t], line, _column, opts),
do: trim_init(t, line + 1, opts.indentation + 1, opts)
defp trim_init([?\n | t], line, _column, opts),
do: trim_init(t, line + 1, opts.indentation + 1, opts)
defp trim_init([?<, ?% | _] = rest, line, column, _opts),
do: {rest, line, column}
defp trim_init(_, _, _, _), do: false
defp trim_left(buffer, count) do
case trim_whitespace(buffer) do
[?\n, ?\r | rest] -> trim_left(rest, count + 1)
[?\n | rest] -> trim_left(rest, count + 1)
case trim_whitespace(buffer, 0) do
{[?\n, ?\r | rest], _} -> trim_left(rest, count + 1)
{[?\n | rest], _} -> trim_left(rest, count + 1)
_ when count > 0 -> [?\n | buffer]
_ -> buffer
end
end
defp trim_right(rest, line, column, count) do
case trim_whitespace(rest) do
[?\r, ?\n | rest] -> trim_right(rest, line + 1, 1, count + 1)
[?\n | rest] -> trim_right(rest, line + 1, 1, count + 1)
[] -> {[], line, column + length(rest)}
_ when count > 0 -> {[?\n | rest], line - 1, column}
_ -> {rest, line, column}
defp trim_right(rest, line, column, last_column, opts) do
case trim_whitespace(rest, column) do
{[?\r, ?\n | rest], column} ->
trim_right(rest, line + 1, opts.indentation + 1, column + 1, opts)
{[?\n | rest], column} ->
trim_right(rest, line + 1, opts.indentation + 1, column, opts)
{[], column} ->
{[], line, column}
_ when last_column > 0 ->
{[?\n | rest], line - 1, last_column}
_ ->
{rest, line, column}
end
end
defp trim_whitespace([h | t]) when h in @spaces, do: trim_whitespace(t)
defp trim_whitespace(list), do: list
defp trim_whitespace([h | t], column) when h in @spaces, do: trim_whitespace(t, column + 1)
defp trim_whitespace(list, column), do: {list, column}
end
+40 -39
View File
@@ -7,36 +7,36 @@ defmodule EEx.TokenizerTest do
@opts %{indentation: 0, trim: false}
test "simple chars lists" do
assert T.tokenize('foo', 1, 1, @opts) == {:ok, [{:text, 'foo'}, {:eof, 1, 4}]}
assert T.tokenize('foo', 1, 1, @opts) == {:ok, [{:text, 1, 1, 'foo'}, {:eof, 1, 4}]}
end
test "simple strings" do
assert T.tokenize("foo", 1, 1, @opts) == {:ok, [{:text, 'foo'}, {:eof, 1, 4}]}
assert T.tokenize("foo", 1, 1, @opts) == {:ok, [{:text, 1, 1, 'foo'}, {:eof, 1, 4}]}
end
test "strings with embedded code" do
assert T.tokenize('foo <% bar %>', 1, 1, @opts) ==
{:ok, [{:text, 'foo '}, {:expr, 1, 5, '', ' bar '}, {:eof, 1, 14}]}
{:ok, [{:text, 1, 1, 'foo '}, {:expr, 1, 5, '', ' bar '}, {:eof, 1, 14}]}
end
test "strings with embedded equals code" do
assert T.tokenize('foo <%= bar %>', 1, 1, @opts) ==
{:ok, [{:text, 'foo '}, {:expr, 1, 5, '=', ' bar '}, {:eof, 1, 15}]}
{:ok, [{:text, 1, 1, 'foo '}, {:expr, 1, 5, '=', ' bar '}, {:eof, 1, 15}]}
end
test "strings with embedded slash code" do
assert T.tokenize('foo <%/ bar %>', 1, 1, @opts) ==
{:ok, [{:text, 'foo '}, {:expr, 1, 5, '/', ' bar '}, {:eof, 1, 15}]}
{:ok, [{:text, 1, 1, 'foo '}, {:expr, 1, 5, '/', ' bar '}, {:eof, 1, 15}]}
end
test "strings with embedded pipe code" do
assert T.tokenize('foo <%| bar %>', 1, 1, @opts) ==
{:ok, [{:text, 'foo '}, {:expr, 1, 5, '|', ' bar '}, {:eof, 1, 15}]}
{:ok, [{:text, 1, 1, 'foo '}, {:expr, 1, 5, '|', ' bar '}, {:eof, 1, 15}]}
end
test "strings with more than one line" do
assert T.tokenize('foo\n<%= bar %>', 1, 1, @opts) ==
{:ok, [{:text, 'foo\n'}, {:expr, 2, 1, '=', ' bar '}, {:eof, 2, 11}]}
{:ok, [{:text, 1, 1, 'foo\n'}, {:expr, 2, 1, '=', ' bar '}, {:eof, 2, 11}]}
end
test "strings with more than one line and expression with more than one line" do
@@ -48,11 +48,11 @@ defmodule EEx.TokenizerTest do
'''
exprs = [
{:text, 'foo '},
{:text, 1, 1, 'foo '},
{:expr, 1, 5, '=', ' bar\n\nbaz '},
{:text, '\n'},
{:text, 3, 7, '\n'},
{:expr, 4, 1, '', ' foo '},
{:text, '\n'},
{:text, 4, 10, '\n'},
{:eof, 5, 1}
]
@@ -61,21 +61,21 @@ defmodule EEx.TokenizerTest do
test "quotation" do
assert T.tokenize('foo <%% true %>', 1, 1, @opts) ==
{:ok, [{:text, 'foo <% true %>'}, {:eof, 1, 16}]}
{:ok, [{:text, 1, 1, 'foo <% true %>'}, {:eof, 1, 16}]}
end
test "quotation with do/end" do
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1, 1, @opts) ==
{:ok, [{:text, 'foo <% true do %>bar<% end %>'}, {:eof, 1, 32}]}
{:ok, [{:text, 1, 1, 'foo <% true do %>bar<% end %>'}, {:eof, 1, 32}]}
end
test "quotation with interpolation" do
exprs = [
{:text, 'a <% b '},
{:text, 1, 1, 'a <% b '},
{:expr, 1, 9, '=', ' c '},
{:text, ' '},
{:text, 1, 17, ' '},
{:expr, 1, 18, '=', ' d '},
{:text, ' e %> f'},
{:text, 1, 26, ' e %> f'},
{:eof, 1, 33}
]
@@ -84,7 +84,7 @@ defmodule EEx.TokenizerTest do
test "improperly formatted quotation with interpolation" do
exprs = [
{:text, '<%% a <%= b %> c %>'},
{:text, 1, 1, '<%% a <%= b %> c %>'},
{:eof, 1, 22}
]
@@ -93,7 +93,7 @@ defmodule EEx.TokenizerTest do
test "eex comments" do
exprs = [
{:text, 'foo '},
{:text, 1, 1, 'foo '},
{:eof, 1, 16}
]
@@ -102,7 +102,8 @@ defmodule EEx.TokenizerTest do
test "eex comments with do/end" do
exprs = [
{:text, 'foo bar'},
{:text, 1, 1, 'foo '},
{:text, 1, 19, 'bar'},
{:eof, 1, 32}
]
@@ -111,7 +112,7 @@ defmodule EEx.TokenizerTest do
test "elixir comments" do
exprs = [
{:text, 'foo '},
{:text, 1, 1, 'foo '},
{:expr, 1, 5, [], ' true # this is a boolean '},
{:eof, 1, 35}
]
@@ -122,7 +123,7 @@ defmodule EEx.TokenizerTest do
test "elixir comments with do/end" do
exprs = [
{:start_expr, 1, 1, [], ' if true do # startif '},
{:text, 'text'},
{:text, 1, 27, 'text'},
{:end_expr, 1, 31, [], ' end # closeif '},
{:eof, 1, 50}
]
@@ -133,9 +134,9 @@ defmodule EEx.TokenizerTest do
test "strings with embedded do end" do
exprs = [
{:text, 'foo '},
{:text, 1, 1, 'foo '},
{:start_expr, 1, 5, '', ' if true do '},
{:text, 'bar'},
{:text, 1, 21, 'bar'},
{:end_expr, 1, 24, '', ' end '},
{:eof, 1, 33}
]
@@ -145,12 +146,12 @@ defmodule EEx.TokenizerTest do
test "strings with embedded -> end" do
exprs = [
{:text, 'foo '},
{:text, 1, 1, 'foo '},
{:start_expr, 1, 5, '', ' cond do '},
{:middle_expr, 1, 18, '', ' false -> '},
{:text, 'bar'},
{:text, 1, 32, 'bar'},
{:middle_expr, 1, 35, '', ' true -> '},
{:text, 'baz'},
{:text, 1, 48, 'baz'},
{:end_expr, 1, 51, '', ' end '},
{:eof, 1, 60}
]
@@ -162,9 +163,9 @@ defmodule EEx.TokenizerTest do
test "strings with multiple callbacks" do
exprs = [
{:start_expr, 1, 1, '=', ' a fn -> '},
{:text, 'foo'},
{:text, 1, 15, 'foo'},
{:middle_expr, 1, 18, '', ' end, fn -> '},
{:text, 'bar'},
{:text, 1, 34, 'bar'},
{:end_expr, 1, 37, '', ' end '},
{:eof, 1, 46}
]
@@ -176,9 +177,9 @@ defmodule EEx.TokenizerTest do
test "strings with callback followed by do block" do
exprs = [
{:start_expr, 1, 1, '=', ' a fn -> '},
{:text, 'foo'},
{:text, 1, 15, 'foo'},
{:middle_expr, 1, 18, '', ' end do '},
{:text, 'bar'},
{:text, 1, 30, 'bar'},
{:end_expr, 1, 33, '', ' end '},
{:eof, 1, 42}
]
@@ -188,11 +189,11 @@ defmodule EEx.TokenizerTest do
test "strings with embedded keywords blocks" do
exprs = [
{:text, 'foo '},
{:text, 1, 1, 'foo '},
{:start_expr, 1, 5, '', ' if true do '},
{:text, 'bar'},
{:text, 1, 21, 'bar'},
{:middle_expr, 1, 24, '', ' else '},
{:text, 'baz'},
{:text, 1, 34, 'baz'},
{:end_expr, 1, 37, '', ' end '},
{:eof, 1, 46}
]
@@ -206,9 +207,9 @@ defmodule EEx.TokenizerTest do
exprs = [
{:start_expr, 1, 2, '=', ' if true do '},
{:text, '\n TRUE \n'},
{:text, 1, 20, '\n TRUE \n'},
{:middle_expr, 3, 3, '', ' else '},
{:text, '\n FALSE \n'},
{:text, 3, 13, '\n FALSE \n'},
{:end_expr, 5, 3, '', ' end '},
{:eof, 7, 3}
]
@@ -218,7 +219,7 @@ defmodule EEx.TokenizerTest do
test "trim mode with comment" do
exprs = [
{:text, '\n123'},
{:text, 1, 19, '\n123'},
{:eof, 2, 4}
]
@@ -227,9 +228,9 @@ defmodule EEx.TokenizerTest do
test "trim mode with CRLF" do
exprs = [
{:text, '0\n'},
{:text, 1, 1, '0\n'},
{:expr, 2, 3, '=', ' 12 '},
{:text, '\n34'},
{:text, 2, 15, '\n34'},
{:eof, 3, 3}
]
@@ -238,9 +239,9 @@ defmodule EEx.TokenizerTest do
test "trim mode set to false" do
exprs = [
{:text, ' '},
{:text, 1, 1, ' '},
{:expr, 1, 2, '=', ' 12 '},
{:text, ' \n'},
{:text, 1, 11, ' \n'},
{:eof, 2, 1}
]
+10 -2
View File
@@ -1373,8 +1373,16 @@ defmodule Code do
:error ->
case :code.which(module) do
:preloaded ->
path = Path.join([:code.lib_dir(:erts), "doc", "chunks", "#{module}.chunk"])
fetch_docs_from_chunk(path)
# The erts directory is not necessarily included in releases
# unless it is listed as an extra application.
case :code.lib_dir(:erts) do
path when is_list(path) ->
path = Path.join([path, "doc", "chunks", "#{module}.chunk"])
fetch_docs_from_chunk(path)
{:error, _} ->
{:error, :chunk_not_found}
end
_ ->
{:error, :module_not_found}
+21 -9
View File
@@ -473,15 +473,25 @@ defmodule Enum do
@doc """
Chunks the `enumerable` with fine grained control when every chunk is emitted.
`chunk_fun` receives the current element and the accumulator and
must return `{:cont, chunk, acc}` to emit the given chunk and
continue with accumulator or `{:cont, acc}` to not emit any chunk
and continue with the return accumulator.
`chunk_fun` receives the current element and the accumulator and must return:
`after_fun` is invoked when iteration is done and must also return
`{:cont, chunk, acc}` or `{:cont, acc}`.
* `{:cont, chunk, acc}` to emit a chunk and continue with the accumulator
* `{:cont, acc}` to not emit any chunk and continue with the accumulator
* `{:halt, acc}` to halt chunking over the `enumerable`.
Returns a list of lists.
`after_fun` is invoked with the final accumulator when iteration is
finished (or `halt`ed) to handle any trailing elements that were returned
as part of an accumulator, but were not emited as a chunk by `chunk_fun`.
It must return:
* `{:cont, chunk, acc}` to emit a chunk. The chunk will be appended to the
list of already emitted chunks.
* `{:cont, acc}` to not emit a chunk
The `acc` in `after_fun` is required in order to mirror the tuple format
from `chunk_fun` but it will be discarded since the traversal is complete.
Returns a list of emitted chunks.
## Examples
@@ -498,6 +508,8 @@ defmodule Enum do
...> end
iex> Enum.chunk_while(1..10, [], chunk_fun, after_fun)
[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
iex> Enum.chunk_while([1, 2, 3, 5, 7], [], chunk_fun, after_fun)
[[1, 2], [3, 5, 7]]
"""
@doc since: "1.5.0"
@@ -512,7 +524,7 @@ defmodule Enum do
{_, {res, acc}} =
Enumerable.reduce(enumerable, {:cont, {[], acc}}, fn entry, {buffer, acc} ->
case chunk_fun.(entry, acc) do
{:cont, emit, acc} -> {:cont, {[emit | buffer], acc}}
{:cont, chunk, acc} -> {:cont, {[chunk | buffer], acc}}
{:cont, acc} -> {:cont, {buffer, acc}}
{:halt, acc} -> {:halt, {buffer, acc}}
end
@@ -520,7 +532,7 @@ defmodule Enum do
case after_fun.(acc) do
{:cont, _acc} -> :lists.reverse(res)
{:cont, elem, _acc} -> :lists.reverse([elem | res])
{:cont, chunk, _acc} -> :lists.reverse([chunk | res])
end
end
+1
View File
@@ -111,6 +111,7 @@ defmodule File do
@type stream_mode ::
encoding_mode()
| :append
| :compressed
| :trim_bom
| {:read_ahead, pos_integer | false}
| {:delayed_write, non_neg_integer, non_neg_integer}
+15 -4
View File
@@ -1,7 +1,8 @@
defmodule IO.ANSI.Docs do
@moduledoc false
@bullet_text "• "
@bullet_text_unicode "• "
@bullet_text_ascii "* "
@bullets [?*, ?-, ?+]
@spaces [" ", "\n", "\t"]
@@ -219,7 +220,10 @@ defmodule IO.ANSI.Docs do
end
defp traverse_erlang_html({:dt, _, entries}, indent, options) do
["#{indent} ", @bullet_text | handle_erlang_html_text(entries, indent <> " ", options)]
[
"#{indent} ",
bullet_text(options) | handle_erlang_html_text(entries, indent <> " ", options)
]
end
defp traverse_erlang_html({:dd, _, entries}, indent, options) do
@@ -240,7 +244,10 @@ defmodule IO.ANSI.Docs do
end
else
for {:li, _, lines} <- entries do
["#{indent} ", @bullet_text | handle_erlang_html_text(lines, indent <> " ", options)]
[
"#{indent} ",
bullet_text(options) | handle_erlang_html_text(lines, indent <> " ", options)
]
end
end
end
@@ -424,7 +431,7 @@ defmodule IO.ANSI.Docs do
case stripped do
<<bullet, ?\s, item::binary>> when bullet in @bullets ->
write_text(text, indent, options)
process_list(@bullet_text, item, rest, count, indent, options)
process_list(bullet_text(options), item, rest, count, indent, options)
<<d1, ?., ?\s, item::binary>> when d1 in ?0..?9 ->
write_text(text, indent, options)
@@ -930,6 +937,10 @@ defmodule IO.ANSI.Docs do
end
end
defp bullet_text(options) do
if options[:enabled], do: @bullet_text_unicode, else: @bullet_text_ascii
end
defp color(style, colors) do
IO.ANSI.format_fragment(colors[style], colors[:enabled])
end
+29 -2
View File
@@ -3123,7 +3123,15 @@ defmodule Kernel do
not function? and __CALLER__.context == :match ->
raise ArgumentError,
"invalid write attribute syntax, you probably meant to use: @#{name} expression"
"""
invalid write attribute syntax. If you want to define an attribute, don't do this:
@foo = :value
Instead, do this:
@foo :value
"""
# Typespecs attributes are currently special cased by the compiler
is_list(args) and typespec?(name) ->
@@ -3219,7 +3227,26 @@ defmodule Kernel do
end
end
# All other cases
# Error cases
defp do_at([{call, meta, ctx_or_args}, [{:do, _} | _] = kw], _meta, name, _function?, _env) do
args =
cond do
is_atom(ctx_or_args) -> []
true -> ctx_or_args
end
code = "\n@#{name} (#{Macro.to_string({call, meta, args ++ [kw]})})"
raise ArgumentError, """
expected 0 or 1 argument for @#{name}, got 2.
It seems you are trying to use the do-syntax with @module attributes \
but the do-block is binding to the attribute name. You probably want \
to wrap the argument value in parentheses, like this:
#{String.replace(code, "\n", "\n ")}
"""
end
defp do_at(args, _meta, name, _function?, _env) do
raise ArgumentError, "expected 0 or 1 argument for @#{name}, got: #{length(args)}"
end
+2 -2
View File
@@ -233,9 +233,9 @@ defmodule Kernel.SpecialForms do
Sizes for types are a bit more nuanced. The default size for integers is 8.
For floats, it is 64. For floats, `size * unit` must result in 32 or 64,
For floats, it is 64. For floats, `size * unit` must result in 16, 32, or 64,
corresponding to [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point)
binary32 and binary64, respectively.
binary16, binary32, and binary64, respectively.
For binaries, the default is the size of the binary. Only the last binary in a
match can use the default size. All others must have their size specified
+58 -7
View File
@@ -337,8 +337,13 @@ defmodule Macro do
end
@doc """
Generates AST nodes for a given number of required argument variables using
`Macro.var/2`.
Generates AST nodes for a given number of required argument
variables using `Macro.var/2`.
Note the arguments are not unique. If you later on want
to access this same varibles, you can invoke this function
with the same inputs. Use `generate_unique_arguments/2` to
generate a unique arguments that can't be overridden.
## Examples
@@ -349,19 +354,47 @@ defmodule Macro do
@doc since: "1.5.0"
@spec generate_arguments(0, context :: atom) :: []
@spec generate_arguments(pos_integer, context) :: [{atom, [], context}, ...] when context: atom
def generate_arguments(amount, context)
def generate_arguments(amount, context), do: generate_arguments(amount, context, &var/2)
def generate_arguments(0, context) when is_atom(context), do: []
@doc """
Generates AST nodes for a given number of required argument
variables using `Macro.unique_var/2`.
def generate_arguments(amount, context)
when is_integer(amount) and amount > 0 and is_atom(context) do
for id <- 1..amount, do: var(String.to_atom("arg" <> Integer.to_string(id)), context)
## Examples
iex> [var1, var2] = Macro.generate_unique_arguments(2, __MODULE__)
iex> {:arg1, [counter: c1], __MODULE__} = var1
iex> {:arg2, [counter: c2], __MODULE__} = var2
iex> is_integer(c1) and is_integer(c2)
true
"""
@doc since: "1.11.3"
@spec generate_unique_arguments(0, context :: atom) :: []
@spec generate_unique_arguments(pos_integer, context) :: [
{atom, [counter: integer], context},
...
]
when context: atom
def generate_unique_arguments(amount, context),
do: generate_arguments(amount, context, &unique_var/2)
defp generate_arguments(0, context, _fun) when is_atom(context), do: []
defp generate_arguments(amount, context, fun)
when is_integer(amount) and amount > 0 and is_atom(context) do
for id <- 1..amount, do: fun.(String.to_atom("arg" <> Integer.to_string(id)), context)
end
@doc """
Generates an AST node representing the variable given
by the atoms `var` and `context`.
Note this variable is not unique. If you later on want
to access this same varible, you can invoke `var/2`
again with the same argument. Use `unique_var/2` to
generate a unique variable that can't be overridden.
## Examples
In order to build a variable, a context is expected.
@@ -383,6 +416,24 @@ defmodule Macro do
{var, [], context}
end
@doc """
Generates an AST node representing a unique variable
given by the atoms `var` and `context`.
## Examples
iex> {:foo, [counter: c], __MODULE__} = Macro.unique_var(:foo, __MODULE__)
iex> is_integer(c)
true
"""
@doc since: "1.11.3"
@spec unique_var(var, context) :: {var, [counter: integer], context}
when var: atom, context: atom
def unique_var(var, context) when is_atom(var) and is_atom(context) do
{var, [counter: :elixir_module.next_counter(context)], context}
end
@doc """
Performs a depth-first traversal of quoted expressions
using an accumulator.
+1 -1
View File
@@ -306,7 +306,7 @@ defmodule Module.Types.Expr do
stack = push_expr_stack(expr, stack)
case reduce_ok(clauses, context, &with_clause(&1, stack, &2)) do
{:ok, _context} -> {:ok, :dynamic, context}
{:ok, context} -> {:ok, :dynamic, context}
{:error, reason} -> {:error, reason}
end
end
+139 -135
View File
@@ -24,105 +24,6 @@ defmodule Module.Types.Pattern do
of_pattern(pattern, %{stack | context: :pattern}, context)
end
# :atom
def of_pattern(atom, _stack, context) when is_atom(atom) do
{:ok, {:atom, atom}, context}
end
# 12
def of_pattern(literal, _stack, context) when is_integer(literal) do
{:ok, :integer, context}
end
# 1.2
def of_pattern(literal, _stack, context) when is_float(literal) do
{:ok, :float, context}
end
# "..."
def of_pattern(literal, _stack, context) when is_binary(literal) do
{:ok, :binary, context}
end
# <<...>>>
def of_pattern({:<<>>, _meta, args}, stack, context) do
result = Of.binary(args, stack, context, &of_pattern/3)
case result do
{:ok, context} -> {:ok, :binary, context}
{:error, reason} -> {:error, reason}
end
end
# left | []
def of_pattern({:|, _meta, [left_expr, []]} = expr, stack, context) do
stack = push_expr_stack(expr, stack)
of_pattern(left_expr, stack, context)
end
# left | right
def of_pattern({:|, _meta, [left_expr, right_expr]} = expr, stack, context) do
stack = push_expr_stack(expr, stack)
case of_pattern(left_expr, stack, context) do
{:ok, left, context} ->
case of_pattern(right_expr, stack, context) do
{:ok, {:list, right}, context} ->
{:ok, to_union([left, right], context), context}
{:ok, right, context} ->
{:ok, to_union([left, right], context), context}
{:error, reason} ->
{:error, reason}
end
{:error, reason} ->
{:error, reason}
end
end
# []
def of_pattern([], _stack, context) do
{:ok, {:list, :dynamic}, context}
end
# [expr, ...]
def of_pattern(exprs, stack, context) when is_list(exprs) do
stack = push_expr_stack(exprs, stack)
case map_reduce_ok(exprs, context, &of_pattern(&1, stack, &2)) do
{:ok, types, context} -> {:ok, {:list, to_union(types, context)}, context}
{:error, reason} -> {:error, reason}
end
end
# left ++ right
def of_pattern(
{{:., _meta1, [:erlang, :++]}, _meta2, [left_expr, right_expr]} = expr,
stack,
context
) do
stack = push_expr_stack(expr, stack)
case of_pattern(left_expr, stack, context) do
{:ok, {:list, left}, context} ->
case of_pattern(right_expr, stack, context) do
{:ok, {:list, right}, context} ->
{:ok, {:list, to_union([left, right], context)}, context}
{:ok, right, context} ->
{:ok, {:list, to_union([left, right], context)}, context}
{:error, reason} ->
{:error, reason}
end
{:error, reason} ->
{:error, reason}
end
end
# _
def of_pattern({:_, _meta, atom}, _stack, context) when is_atom(atom) do
{:ok, :dynamic, context}
@@ -139,21 +40,6 @@ defmodule Module.Types.Pattern do
{:ok, type, context}
end
# {left, right}
def of_pattern({left, right}, stack, context) do
of_pattern({:{}, [], [left, right]}, stack, context)
end
# {...}
def of_pattern({:{}, _meta, exprs} = expr, stack, context) do
stack = push_expr_stack(expr, stack)
case map_reduce_ok(exprs, context, &of_pattern(&1, stack, &2)) do
{:ok, types, context} -> {:ok, {:tuple, length(types), types}, context}
{:error, reason} -> {:error, reason}
end
end
# left = right
def of_pattern({:=, _meta, [left_expr, right_expr]} = expr, stack, context) do
stack = push_expr_stack(expr, stack)
@@ -163,23 +49,6 @@ defmodule Module.Types.Pattern do
do: unify(left_type, right_type, stack, context)
end
# %{...}
def of_pattern({:%{}, _meta, args} = expr, stack, context) do
stack = push_expr_stack(expr, stack)
Of.open_map(args, stack, context, &of_pattern/3)
end
# %Struct{...}
def of_pattern({:%, meta1, [module, {:%{}, _meta2, args}]} = expr, stack, context)
when is_atom(module) do
stack = push_expr_stack(expr, stack)
with {:ok, struct, context} <- Of.struct(module, meta1, context),
{:ok, map, context} <- Of.open_map(args, stack, context, &of_pattern/3) do
unify(map, struct, stack, context)
end
end
# %_{...}
def of_pattern(
{:%, _meta1, [{:_, _meta2, var_context}, {:%{}, _meta3, args}]} = expr,
@@ -195,7 +64,8 @@ defmodule Module.Types.Pattern do
end
# %var{...} and %^var{...}
def of_pattern({:%, _meta1, [var, {:%{}, _meta2, args}]} = expr, stack, context) do
def of_pattern({:%, _meta1, [var, {:%{}, _meta2, args}]} = expr, stack, context)
when not is_atom(var) do
stack = push_expr_stack(expr, stack)
with {:ok, var_type, context} = of_pattern(var, stack, context),
@@ -205,6 +75,10 @@ defmodule Module.Types.Pattern do
end
end
def of_pattern(expr, stack, context) do
of_shared(expr, stack, context, &of_pattern/3)
end
def unify_kinds(:required, _), do: :required
def unify_kinds(_, :required), do: :required
def unify_kinds(:optional, :optional), do: :optional
@@ -401,10 +275,8 @@ defmodule Module.Types.Pattern do
{:ok, type, context}
end
# other literals
def of_guard(expr, stack, context) do
# Fall back to of_pattern/3 for literals
of_pattern(expr, stack, context)
of_shared(expr, stack, context, &of_guard/3)
end
defp fresh_context(context) do
@@ -550,4 +422,136 @@ defmodule Module.Types.Pattern do
defp type_guard?(name) do
name in @type_guards
end
## Shared
# :atom
defp of_shared(atom, _stack, context, _fun) when is_atom(atom) do
{:ok, {:atom, atom}, context}
end
# 12
defp of_shared(literal, _stack, context, _fun) when is_integer(literal) do
{:ok, :integer, context}
end
# 1.2
defp of_shared(literal, _stack, context, _fun) when is_float(literal) do
{:ok, :float, context}
end
# "..."
defp of_shared(literal, _stack, context, _fun) when is_binary(literal) do
{:ok, :binary, context}
end
# <<...>>>
defp of_shared({:<<>>, _meta, args}, stack, context, fun) do
case Of.binary(args, stack, context, fun) do
{:ok, context} -> {:ok, :binary, context}
{:error, reason} -> {:error, reason}
end
end
# left | []
defp of_shared({:|, _meta, [left_expr, []]} = expr, stack, context, fun) do
stack = push_expr_stack(expr, stack)
fun.(left_expr, stack, context)
end
# left | right
defp of_shared({:|, _meta, [left_expr, right_expr]} = expr, stack, context, fun) do
stack = push_expr_stack(expr, stack)
case fun.(left_expr, stack, context) do
{:ok, left, context} ->
case fun.(right_expr, stack, context) do
{:ok, {:list, right}, context} ->
{:ok, to_union([left, right], context), context}
{:ok, right, context} ->
{:ok, to_union([left, right], context), context}
{:error, reason} ->
{:error, reason}
end
{:error, reason} ->
{:error, reason}
end
end
# []
defp of_shared([], _stack, context, _fun) do
{:ok, {:list, :dynamic}, context}
end
# [expr, ...]
defp of_shared(exprs, stack, context, fun) when is_list(exprs) do
stack = push_expr_stack(exprs, stack)
case map_reduce_ok(exprs, context, &fun.(&1, stack, &2)) do
{:ok, types, context} -> {:ok, {:list, to_union(types, context)}, context}
{:error, reason} -> {:error, reason}
end
end
# left ++ right
defp of_shared(
{{:., _meta1, [:erlang, :++]}, _meta2, [left_expr, right_expr]} = expr,
stack,
context,
fun
) do
stack = push_expr_stack(expr, stack)
case fun.(left_expr, stack, context) do
{:ok, {:list, left}, context} ->
case fun.(right_expr, stack, context) do
{:ok, {:list, right}, context} ->
{:ok, {:list, to_union([left, right], context)}, context}
{:ok, right, context} ->
{:ok, {:list, to_union([left, right], context)}, context}
{:error, reason} ->
{:error, reason}
end
{:error, reason} ->
{:error, reason}
end
end
# {left, right}
defp of_shared({left, right}, stack, context, fun) do
of_shared({:{}, [], [left, right]}, stack, context, fun)
end
# {...}
defp of_shared({:{}, _meta, exprs} = expr, stack, context, fun) do
stack = push_expr_stack(expr, stack)
case map_reduce_ok(exprs, context, &fun.(&1, stack, &2)) do
{:ok, types, context} -> {:ok, {:tuple, length(types), types}, context}
{:error, reason} -> {:error, reason}
end
end
# %{...}
defp of_shared({:%{}, _meta, args} = expr, stack, context, fun) do
stack = push_expr_stack(expr, stack)
Of.open_map(args, stack, context, fun)
end
# %Struct{...}
defp of_shared({:%, meta1, [module, {:%{}, _meta2, args}]} = expr, stack, context, fun)
when is_atom(module) do
stack = push_expr_stack(expr, stack)
with {:ok, struct, context} <- Of.struct(module, meta1, context),
{:ok, map, context} <- Of.open_map(args, stack, context, fun) do
unify(map, struct, stack, context)
end
end
end
+5 -2
View File
@@ -166,8 +166,11 @@ defmodule Module.Types.Unify do
end
case unify_result do
{:ok, var_type, context} ->
context = refine_var(var, var_type, stack, context)
{:ok, {:var, ^var}, context} ->
{:ok, {:var, var}, context}
{:ok, res_type, context} ->
context = refine_var(var, res_type, stack, context)
{:ok, {:var, var}, context}
{:error, reason} ->
+4 -16
View File
@@ -573,27 +573,15 @@ defmodule Path do
@moduledoc false
def read_link_info(file) do
call({:read_link_info, file})
:file.read_link_info(file)
end
def list_dir(dir) do
case call({:list_dir, dir}) do
{:ok, files} ->
{:ok, for(file <- files, hd(file) != ?., do: file)}
other ->
other
case :file.list_dir(dir) do
{:ok, files} -> {:ok, for(file <- files, hd(file) != ?., do: file)}
other -> other
end
end
@compile {:inline, call: 1}
defp call(tuple) do
x = :erlang.dt_spread_tag(true)
y = :gen_server.call(:file_server_2, tuple)
:erlang.dt_restore_tag(x)
y
end
end
@doc """
+2 -2
View File
@@ -447,12 +447,12 @@ defmodule Record do
end
end
defp hoist_expressions(keyword, %{context: nil}) do
defp hoist_expressions(keyword, %{context: nil, module: module}) do
Enum.map_reduce(keyword, [], fn {key, expr}, acc ->
if simple_argument?(expr) do
{{key, expr}, acc}
else
var = Macro.var(key, __MODULE__)
var = Macro.unique_var(key, module)
{{key, var}, [{:=, [], [var, expr]} | acc]}
end
end)
+9 -1
View File
@@ -600,9 +600,17 @@ defmodule System do
"""
# TODO: Once Erlang/OTP 23 is required, remove conditional, and update @doc accordingly.
# The warning is emitted by the compiler - so a @doc annotation is enough
# Note Elixir may be compiled in an earlier Erlang version but runs on a
# newer one, so we need the check at compilation time and runtime.
@doc deprecated: "Use __STACKTRACE__ instead"
if function_exported?(:erlang, :get_stacktrace, 0) do
def stacktrace, do: apply(:erlang, :get_stacktrace, [])
def stacktrace do
if function_exported?(:erlang, :get_stacktrace, 0) do
apply(:erlang, :get_stacktrace, [])
else
[]
end
end
else
def stacktrace, do: []
end
+9 -2
View File
@@ -127,11 +127,18 @@ Conveniently, Elixir allows developers to hide modules and functions from the do
end
end
However, keep in mind that adding `@doc false` does not make the function private. The function above can still be invoked as `MyApp.Sample.add(1, 2)`. Not only that, if `MyApp.Sample` is imported, the `add/2` function will also be imported into the caller. For those reasons, be cautious when adding `@doc false` to functions, instead use one of these two options:
In case you don't want to hide a whole module, you can hide functions individually:
defmodule MyApp.Sample do
@doc false
def add(a, b), do: a + b
end
However, keep in mind `@moduledoc false` or `@doc false` do not make a function private. The function above can still be invoked as `MyApp.Sample.add(1, 2)`. Not only that, if `MyApp.Sample` is imported, the `add/2` function will also be imported into the caller. For those reasons, be cautious when adding `@doc false` to functions, instead use one of these two options:
* Move the undocumented function to a module with `@moduledoc false`, like `MyApp.Hidden`, ensuring the function won't be accidentally exposed or imported. Remember you can use `@moduledoc false` to hide a whole module and still document each function with `@doc`. Tools will still ignore the module.
* Start the function name with one or two underscores, for example, `__add__/2`, and add `@doc false`. The compiler does not import functions with leading underscores and they hint to anyone reading the code of their intended private usage.
* Start the function name with one or two underscores, for example, `__add__/2`. Functions starting with underscore are autoamtically treated as hidden, although you can also be explicit and add `@doc false`. The compiler does not import functions with leading underscores and they hint to anyone reading the code of their intended private usage.
## Code.fetch_docs/1
+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"])
+12
View File
@@ -0,0 +1,12 @@
canonical = System.fetch_env!("CANONICAL")
[
deps: [
eex: "https://hexdocs.pm/eex/#{canonical}",
elixir: "https://hexdocs.pm/elixir/#{canonical}",
ex_unit: "https://hexdocs.pm/ex_unit/#{canonical}",
iex: "https://hexdocs.pm/iex/#{canonical}",
logger: "https://hexdocs.pm/logger/#{canonical}",
mix: "https://hexdocs.pm/mix/#{canonical}"
]
]
+19 -3
View File
@@ -319,8 +319,13 @@ build_spec(Meta, _Size, Unit, Type, _Endianness, Sign, Spec, E) when Type == bin
build_spec(Meta, Size, Unit, Type, Endianness, Sign, Spec, E) when Type == integer; Type == float ->
NumberSize = number_size(Size, Unit),
if
Type == float, is_integer(NumberSize), NumberSize /= 32, NumberSize /= 64 ->
form_error(Meta, E, ?MODULE, {bittype_float_size, NumberSize});
Type == float, is_integer(NumberSize) ->
case valid_float_size(NumberSize) of
true ->
add_spec(Type, add_spec(Endianness, add_spec(Sign, Spec)));
false ->
form_error(Meta, E, ?MODULE, {bittype_float_size, NumberSize})
end;
Size == default, Unit /= default ->
form_error(Meta, E, ?MODULE, bittype_unit);
true ->
@@ -331,6 +336,12 @@ number_size(Size, default) when is_integer(Size) -> Size;
number_size(Size, Unit) when is_integer(Size) -> Size * Unit;
number_size(Size, _) -> Size.
%% TODO: Simplify when we require OTP 24
valid_float_size(16) -> erlang:system_info(otp_release) >= "24";
valid_float_size(32) -> true;
valid_float_size(64) -> true;
valid_float_size(_) -> false.
add_spec(default, Spec) -> Spec;
add_spec(Key, Spec) -> [{Key, [], []} | Spec].
@@ -372,7 +383,12 @@ format_error(bittype_signed) ->
format_error(bittype_unit) ->
"integer and float types require a size specifier if the unit specifier is given";
format_error({bittype_float_size, Other}) ->
io_lib:format("float requires size*unit to be 32 or 64 (default), got: ~p", [Other]);
Message =
case erlang:system_info(otp_release) >= "24" of
true -> "16, 32, or 64";
false -> "32 or 64"
end,
io_lib:format("float requires size*unit to be ~s (default), got: ~p", [Message, Other]);
format_error({invalid_literal, Literal}) ->
io_lib:format("invalid literal ~ts in <<>>", ['Elixir.Macro':to_string(Literal)]);
format_error({undefined_bittype, Expr}) ->
+29 -10
View File
@@ -94,6 +94,8 @@ handle_file_warning(true, _File, {_Line, sys_core_fold, {nomatch_shadow, _}}) ->
%% Those we implement ourselves
handle_file_warning(_, _File, {_Line, v3_core, {map_key_repeated, _}}) -> ok;
handle_file_warning(_, _File, {_Line, sys_core_fold, {ignored, useless_building}}) -> ok;
%% TODO: remove when we require Erlang/OTP 24
handle_file_warning(_, _File, {_Line, sys_core_fold, useless_building}) -> ok;
%% Ignore all linting errors (only come up on parse transforms)
@@ -112,11 +114,11 @@ handle_file_error(File, {Line, Module, Desc}) ->
elixir_errors:compile_error([{line, Line}], File, Message).
%% Mention the capture operator in make_fun
custom_format(sys_core_fold, {no_effect, {erlang, make_fun, 3}}) ->
custom_format(sys_core_fold, {ignored, {no_effect, {erlang, make_fun, 3}}}) ->
"the result of the capture operator & (:erlang.make_fun/3) is never used";
%% Make no_effect clauses pretty
custom_format(sys_core_fold, {no_effect, {erlang, F, A}}) ->
custom_format(sys_core_fold, {ignored, {no_effect, {erlang, F, A}}}) ->
{Fmt, Args} = case erl_internal:comp_op(F, A) of
true -> {"use of operator ~ts has no effect", [elixir_utils:erlang_comparison_op_to_elixir(F)]};
false ->
@@ -127,16 +129,11 @@ custom_format(sys_core_fold, {no_effect, {erlang, F, A}}) ->
end,
io_lib:format(Fmt, Args);
%% Rewrite nomatch_guard to be more generic it can happen inside if, unless, and the like
custom_format(sys_core_fold, nomatch_guard) ->
%% Rewrite nomatch to be more generic, it can happen inside if, unless, and the like
custom_format(sys_core_fold, {nomatch, X}) when X == guard; X == no_clause ->
"this check/guard will always yield the same result";
%% Handle literal eval failures
custom_format(sys_core_fold, {eval_failure, Error}) ->
#{'__struct__' := Struct} = 'Elixir.Exception':normalize(error, Error),
["this expression will fail with ", elixir_aliases:inspect(Struct)];
custom_format(sys_core_fold, {nomatch_shadow,Line,{ErlName,ErlArity}}) ->
custom_format(sys_core_fold, {nomatch, {shadow, Line, {ErlName, ErlArity}}}) ->
{Name, Arity} = elixir_utils:erl_fa_to_elixir_fa(ErlName, ErlArity),
io_lib:format(
@@ -144,6 +141,28 @@ custom_format(sys_core_fold, {nomatch_shadow,Line,{ErlName,ErlArity}}) ->
[Name, Arity, Line]
);
%% Handle literal eval failures
custom_format(sys_core_fold, {failed, {eval_failure, {Mod, Name, Arity}, Error}}) ->
#{'__struct__' := Struct} = 'Elixir.Exception':normalize(error, Error),
{ExMod, ExName, ExArgs} = elixir_rewrite:erl_to_ex(Mod, Name, lists:duplicate(Arity, nil)),
Call = 'Elixir.Exception':format_mfa(ExMod, ExName, length(ExArgs)),
Trimmed = case Call of
<<"Kernel.", Rest/binary>> -> Rest;
_ -> Call
end,
["the call to ", Trimmed, " will fail with ", elixir_aliases:inspect(Struct)];
%% TODO: remove when we require OTP 24
custom_format(sys_core_fold, {nomatch_shadow, Line, FA}) ->
custom_format(sys_core_fold, {nomatch, {shadow, Line, FA}});
custom_format(sys_core_fold, nomatch_guard) ->
custom_format(sys_core_fold, {nomatch, guard});
custom_format(sys_core_fold, {no_effect, X}) ->
custom_format(sys_core_fold, {ignored, {no_effect, X}});
custom_format(sys_core_fold, {eval_failure, Error}) ->
#{'__struct__' := Struct} = 'Elixir.Exception':normalize(error, Error),
["this expression will fail with ", elixir_aliases:inspect(Struct)];
custom_format([], Desc) ->
io_lib:format("~p", [Desc]);
+1 -1
View File
@@ -469,7 +469,7 @@ expand(Other, E) ->
escape_env_entries(Meta, #{current_vars := {Read, Write}, unused_vars := {Unused, Version}} = Env0) ->
Env1 = case Env0 of
#{function := nil} -> Env0;
_ -> Env0#{lexical_tracker := nil}
_ -> Env0#{lexical_tracker := nil, tracers := []}
end,
Current = {maybe_escape_map(Read), maybe_escape_map(Write)},
Env2 = Env1#{current_vars := Current, unused_vars := {maybe_escape_map(Unused), Version}},
+1 -1
View File
@@ -139,9 +139,9 @@ compile(Line, Module, Block, Vars, E) ->
},
Binary = elixir_erl:compile(ModuleMap),
warn_unused_attributes(File, DataSet, DataBag, PersistedAttributes),
autoload_module(Module, Binary, CompileOpts, NE),
eval_callbacks(Line, DataBag, after_compile, [NE, Binary], NE),
warn_unused_attributes(File, DataSet, DataBag, PersistedAttributes),
make_module_available(Module, Binary, ModuleMap),
{module, Module, Binary, Result}
catch
+4
View File
@@ -327,6 +327,10 @@ guard_rewrite(Receiver, DotMeta, Right, Meta, Args) ->
_ -> {error, {invalid_guard, Receiver, Right, length(Args)}}
end.
%% erlang:is_record/2-3 are compiler guards in Erlang which we
%% need to explicitly forbid as they are allowed in erl_internal.
allowed_guard(is_record, 2) -> false;
allowed_guard(is_record, 3) -> false;
allowed_guard(Right, Arity) ->
erl_internal:guard_bif(Right, Arity) orelse elixir_utils:guard_op(Right, Arity).
+8 -8
View File
@@ -477,8 +477,8 @@ tokenize([$:, H | T] = Original, Line, Column, Scope, Tokens) when ?is_quote(H)
tokenize([$: | String] = Original, Line, Column, Scope, Tokens) ->
case tokenize_identifier(String, Line, Column, Scope, false) of
{_Kind, Atom, Rest, Length, _Ascii, _Special} ->
NewScope = maybe_warn_for_ambiguous_bang_before_equals(atom, Atom, Rest, Scope, Line),
{_Kind, Unencoded, Atom, Rest, Length, _Ascii, _Special} ->
NewScope = maybe_warn_for_ambiguous_bang_before_equals(atom, Unencoded, Rest, Scope, Line),
Token = {atom, {Line, Column, nil}, Atom},
tokenize(Rest, Line, Column + 1 + Length, NewScope, [Token | Tokens]);
empty ->
@@ -559,7 +559,7 @@ tokenize([$. | T], Line, Column, Scope, Tokens) ->
tokenize(String, Line, Column, Scope, Tokens) ->
case tokenize_identifier(String, Line, Column, Scope, not previous_was_dot(Tokens)) of
{Kind, Atom, Rest, Length, Ascii, Special} ->
{Kind, Unencoded, Atom, Rest, Length, Ascii, Special} ->
HasAt = lists:member($@, Special),
case Rest of
@@ -583,7 +583,7 @@ tokenize(String, Line, Column, Scope, Tokens) ->
tokenize_alias(Rest, Line, Column, Atom, Length, Ascii, Special, Scope, Tokens);
_ when Kind == identifier ->
NewScope = maybe_warn_for_ambiguous_bang_before_equals(identifier, Atom, Rest, Scope, Line),
NewScope = maybe_warn_for_ambiguous_bang_before_equals(identifier, Unencoded, Rest, Scope, Line),
Token = check_call_identifier(Line, Column, Atom, Rest),
tokenize(Rest, Line, Column + Length, NewScope, [Token | Tokens]);
@@ -1142,7 +1142,7 @@ tokenize_identifier(String, Line, Column, Scope, MaybeKeyword) ->
{keyword, Atom, Type} ->
{keyword, Atom, Type, Rest, Length};
{ok, Atom} ->
{Kind, Atom, Rest, Length, Ascii, Special};
{Kind, Acc, Atom, Rest, Length, Ascii, Special};
{error, _Reason} = Error ->
Error
end;
@@ -1462,11 +1462,11 @@ maybe_warn_too_many_of_same_char(_Token, _Rest, _Line, Scope) ->
Scope.
%% TODO: Turn into an error on v2.0
maybe_warn_for_ambiguous_bang_before_equals(Kind, Atom, [$= | _], Scope, Line) ->
maybe_warn_for_ambiguous_bang_before_equals(Kind, Unencoded, [$= | _], Scope, Line) ->
{What, Identifier} =
case Kind of
atom -> {"atom", [$: | atom_to_list(Atom)]};
identifier -> {"identifier", atom_to_list(Atom)}
atom -> {"atom", [$: | Unencoded]};
identifier -> {"identifier", Unencoded}
end,
case lists:last(Identifier) of
+8
View File
@@ -224,6 +224,14 @@ defmodule CodeTest do
Code.string_to_quoted("there_is_no_such_var", static_atoms_encoder: encoder)
end
test "static_atoms_encoder ambiguities" do
encoder = fn string, _meta -> {:ok, {:atom, string}} end
# We check a=1 for precedence issues with a!=1, make sure it works
assert Code.string_to_quoted!("a = 1", static_atoms_encoder: encoder)
assert Code.string_to_quoted!("a=1", static_atoms_encoder: encoder)
end
test "static_atoms_encoder does not encode keywords" do
encoder = fn atom, _meta -> raise "shouldn't be invoked for #{atom}" end
+11
View File
@@ -116,6 +116,17 @@ defmodule EnumTest do
[[0], [1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
assert Enum.chunk_while([5, 7, 9, 11], [], chunk_fun, after_fun) == [[5, 7, 9]]
assert Enum.chunk_while([1, 2, 3, 5, 7], [], chunk_fun, after_fun) == [[1, 2], [3, 5, 7]]
chunk_fn2 = fn
-1, acc -> {:cont, acc, 0}
i, acc -> {:cont, acc + i}
end
after_fn2 = fn acc -> {:cont, acc, 0} end
assert Enum.chunk_while([1, -1, 2, 3, -1, 4, 5, 6], 0, chunk_fn2, after_fn2) == [1, 5, 15]
end
test "concat/1" do
+5 -4
View File
@@ -446,11 +446,12 @@ defmodule ExceptionTest do
* min/1
"""
assert blame_message(:erlang, & &1.gt_cookie()) == """
function :erlang.gt_cookie/0 is undefined or private. Did you mean one of:
assert blame_message(:erlang, & &1.hal()) == """
function :erlang.hal/0 is undefined or private. Did you mean one of:
* get_cookie/0
* set_cookie/2
* halt/0
* halt/1
* halt/2
"""
end
@@ -192,6 +192,11 @@ defmodule IO.ANSI.DocsTest do
assert result == " • one\n • two\n • three\n\e[0m"
end
test "* list is converted without ansi" do
result = format_markdown("* one\n* two\n* three\n", enabled: false)
assert result == " * one\n * two\n * three"
end
test "* list surrounded by text is converted" do
result = format_markdown("Count:\n\n* one\n* two\n* three\n\nDone")
assert result == "Count:\n\e[0m\n • one\n • two\n • three\n\e[0m\nDone\n\e[0m"
+2 -12
View File
@@ -692,16 +692,6 @@ defmodule Kernel.ErrorsTest do
end
end
test "match attribute in module" do
msg = "invalid write attribute syntax, you probably meant to use: @foo expression"
assert_raise ArgumentError, msg, fn ->
defmodule MatchAttributeInModule do
@foo = 42
end
end
end
test "invalid case clauses" do
assert_eval_raise CompileError,
"nofile:1: expected one argument for :do clauses (->) in \"case\"",
@@ -828,7 +818,7 @@ defmodule Kernel.ErrorsTest do
"nofile:3: function exit/1 imported from both :erlang and Kernel, call is ambiguous",
'''
defmodule Kernel.ErrorsTest.FunctionImportConflict do
import :erlang, warn: false
import :erlang, only: [exit: 1], warn: false
def foo, do: exit(:test)
end
'''
@@ -1202,7 +1192,7 @@ defmodule Kernel.ErrorsTest do
rescue
ArgumentError ->
assert [
{:erlang, :apply, [1, :foo, []], []},
{:erlang, :apply, [1, :foo, []], _},
{__MODULE__, :bad_remote_call, 1, [file: _, line: _]} | _
] = __STACKTRACE__
end
@@ -294,6 +294,7 @@ defmodule Kernel.ExpansionTest do
test "__ENV__" do
env = %{__ENV__ | line: 0}
assert expand_env(quote(do: __ENV__), env) == {Macro.escape(env), env}
assert %{lexical_tracker: nil, tracers: []} = __ENV__
end
test "__ENV__.accessor" do
@@ -302,6 +303,9 @@ defmodule Kernel.ExpansionTest do
assert expand_env(quote(do: __ENV__.unknown), env) ==
{quote(do: unquote(Macro.escape(env)).unknown), env}
assert __ENV__.lexical_tracker == nil
assert __ENV__.tracers == []
end
end
@@ -2383,17 +2387,49 @@ defmodule Kernel.ExpansionTest do
end
end
test "raises for invalid size * unit for floats" do
message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 128"
# TODO: Simplify when we require OTP 24
if System.otp_release() >= "24" do
test "16-bit floats" do
import Kernel, except: [-: 2]
assert_raise CompileError, message, fn ->
expand(quote(do: <<12.3::32*4>>))
assert expand(quote(do: <<12.3::float-16>>)) |> clean_meta([:alignment]) ==
quote(do: <<12.3::float()-size(16)>>)
end
message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 256"
test "raises for invalid size * unit for floats" do
message = ~r"float requires size\*unit to be 16, 32, or 64 \(default\), got: 128"
assert_raise CompileError, message, fn ->
expand(quote(do: <<12.3::256>>))
assert_raise CompileError, message, fn ->
expand(quote(do: <<12.3::32*4>>))
end
message = ~r"float requires size\*unit to be 16, 32, or 64 \(default\), got: 256"
assert_raise CompileError, message, fn ->
expand(quote(do: <<12.3::256>>))
end
end
else
test "16-bit floats" do
message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 16"
assert_raise CompileError, message, fn ->
expand(quote(do: <<12.3::16>>))
end
end
test "raises for invalid size * unit for floats" do
message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 128"
assert_raise CompileError, message, fn ->
expand(quote(do: <<12.3::32*4>>))
end
message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 256"
assert_raise CompileError, message, fn ->
expand(quote(do: <<12.3::256>>))
end
end
end
@@ -310,6 +310,22 @@ defmodule Kernel.GuardTest do
end
end
assert_raise CompileError,
~r"cannot invoke remote function :erlang\.is_record/2 inside guards",
fn ->
defmodule IsRecord2Usage do
defguard foo(rec) when :erlang.is_record(rec, :tag)
end
end
assert_raise CompileError,
~r"cannot invoke remote function :erlang\.is_record/3 inside guards",
fn ->
defmodule IsRecord3Usage do
defguard foo(rec) when :erlang.is_record(rec, :tag, 7)
end
end
assert_raise CompileError,
~r"cannot invoke remote function :erlang\.\+\+/2 inside guards",
fn ->
+17 -35
View File
@@ -931,26 +931,33 @@ defmodule Kernel.WarningTest do
purge(Sample)
end
# TODO: Simplify when we require OTP 24
if System.otp_release() >= "24" do
@argument_error_message "the call to :erlang.atom_to_binary/2"
@arithmetic_error_message "the call to +/2"
else
@argument_error_message "this expression"
@arithmetic_error_message "this expression"
end
test "eval failure warning" do
assert capture_err(fn ->
assert_raise ArgumentError, fn ->
Code.eval_string("""
defmodule Sample do
Atom.to_string "abc"
end
""")
Code.eval_string("""
defmodule Sample1 do
def foo, do: Atom.to_string "abc"
end
end) =~ ~r"this expression will fail with ArgumentError\n.*nofile:2"
""")
end) =~ "#{@argument_error_message} will fail with ArgumentError\n nofile:2"
assert capture_err(fn ->
Code.eval_string("""
defmodule Sample do
defmodule Sample2 do
def foo, do: 1 + nil
end
""")
end) =~ ~r"this expression will fail with ArithmeticError\n.*nofile:2"
end) =~ "#{@arithmetic_error_message} will fail with ArithmeticError\n nofile:2"
after
purge([Sample])
purge([Sample1, Sample2])
end
test "undefined function for behaviour" do
@@ -1147,31 +1154,6 @@ defmodule Kernel.WarningTest do
purge(Sample)
end
test "with and do clauses emit errors, else clauses do not" do
assert capture_err(fn ->
Code.compile_string("""
with {:first, int} when is_integer(int) <- {:second, Integer.gcd(2, 4)} do
int
end
""")
end) =~ "this clause cannot match"
assert capture_err(fn ->
Code.compile_string("""
with {:first, int1} when is_integer(int1) <- {:first, Integer.gcd(2, 4)},
{:second, int2} when is_integer(int2) <- {:second, Integer.gcd(2, 4)} do
{:ok, int1 + int2}
else
{:first, nil} -> {:error, "first number is not integer"}
{:second, nil} -> {:error, "second number is not integer"}
end
""")
end) == ""
after
purge(Sample1)
purge(Sample2)
end
test "warning on code point escape" do
assert capture_err(fn ->
Code.eval_string("? ")
+34 -10
View File
@@ -592,16 +592,6 @@ defmodule KernelTest do
_ = a
end
test "setting attribute with uppercase" do
message = "module attributes set via @ cannot start with an uppercase letter"
assert_raise ArgumentError, message, fn ->
defmodule UpcaseAttrSample do
@Upper
end
end
end
test "in module body" do
defmodule InSample do
@foo [:a, :b]
@@ -782,6 +772,40 @@ defmodule KernelTest do
end
end
describe "@" do
test "setting attribute with do-block" do
exception =
catch_error(
defmodule UpcaseAttrSample do
@foo quote do
:ok
end
end
)
assert exception.message =~ "expected 0 or 1 argument for @foo, got 2"
assert exception.message =~ "You probably want to wrap the argument value in parentheses"
end
test "setting attribute with uppercase" do
message = "module attributes set via @ cannot start with an uppercase letter"
assert_raise ArgumentError, message, fn ->
defmodule UpcaseAttrSample do
@Upper
end
end
end
test "matching attribute" do
assert_raise ArgumentError, ~r"invalid write attribute syntax", fn ->
defmodule MatchAttributeInModule do
@foo = 42
end
end
end
end
describe "defdelegate" do
defdelegate my_flatten(list), to: List, as: :flatten
@@ -1,4 +1,4 @@
Code.require_file("../../test_helper.exs", __DIR__)
Code.require_file("type_helper.exs", __DIR__)
defmodule Module.Types.IntegrationTest do
use ExUnit.Case
@@ -1,4 +1,4 @@
Code.require_file("../../test_helper.exs", __DIR__)
Code.require_file("type_helper.exs", __DIR__)
defmodule Module.Types.PatternTest do
use ExUnit.Case, async: true
@@ -1,4 +1,4 @@
Code.require_file("../../test_helper.exs", __DIR__)
Code.require_file("type_helper.exs", __DIR__)
defmodule Module.Types.TypesTest do
use ExUnit.Case, async: true
@@ -13,23 +13,21 @@ defmodule Module.Types.TypesTest do
expr = TypeHelper.expand_expr(patterns, guards, body, __CALLER__)
quote do
unquote(Macro.escape(expr))
|> Module.Types.TypesTest.__expr__()
|> to_warning()
Module.Types.TypesTest.__expr__(unquote(Macro.escape(expr)))
end
end
def __expr__({patterns, guards, body}) do
with {:ok, _types, context} <-
Pattern.of_head(patterns, guards, TypeHelper.new_stack(), TypeHelper.new_context()),
{:ok, type, context} <- Expr.of_expr(body, TypeHelper.new_stack(), context) do
{:ok, _type, context} <- Expr.of_expr(body, TypeHelper.new_stack(), context) do
case context.warnings do
[warning] -> {:warning, warning}
_ -> flunk("expexted error, got: #{inspect(Types.lift_type(type, context))}")
[warning] -> to_message(:warning, warning)
_ -> :none
end
else
{:error, {type, reason, context}} ->
{:error, {type, reason, context}}
to_message(:error, {type, reason, context})
end
end
@@ -51,13 +49,13 @@ defmodule Module.Types.TypesTest do
min
end
defp to_warning({:warning, {module, warning, _location}}) do
defp to_message(:warning, {module, warning, _location}) do
warning
|> module.format_warning()
|> IO.iodata_to_binary()
end
defp to_warning({:error, {type, reason, context}}) do
defp to_message(:error, {type, reason, context}) do
{Module.Types, error, _location} = Module.Types.error_to_warning(type, reason, context)
error
@@ -472,4 +470,20 @@ defmodule Module.Types.TypesTest do
"""
end
end
describe "regressions" do
test "recursive map fields" do
assert warning(
[queried],
with(
true <- is_nil(queried.foo.bar),
_ = queried.foo
) do
%{foo: %{other_id: _other_id} = foo} = queried
%{other_id: id} = foo
%{id: id}
end
) == :none
end
end
end
@@ -1,4 +1,4 @@
Code.require_file("../../test_helper.exs", __DIR__)
Code.require_file("type_helper.exs", __DIR__)
defmodule Module.Types.UnifyTest do
use ExUnit.Case, async: true
@@ -350,11 +350,22 @@ defmodule Module.Types.UnifyTest do
assert {{:var, 2}, var_context} = new_var({:baz, [version: 2], nil}, var_context)
assert {:ok, {:var, _}, context} = unify({:var, 0}, {:var, 1}, var_context)
assert {:ok, {:var, _}, _context} = unify({:var, 1}, {:var, 0}, context)
assert {:ok, {:var, _}, context} = unify({:var, 1}, {:var, 0}, context)
assert context.types[0] == {:var, 1}
assert context.types[1] == {:var, 0}
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 {: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 {:ok, {:var, _}, context} = unify({:var, 0}, {:var, 1}, var_context)
+7 -8
View File
@@ -95,17 +95,16 @@ defmodule ModuleTest do
end
test "supports read access to module from __after_compile__/2" do
contents =
quote do
@after_compile __MODULE__
@foo 42
defmodule ModuleTest.NoRaise do
@after_compile __MODULE__
@foo 42
def __after_compile__(%Macro.Env{module: module}, bin) when is_binary(bin) do
Module.get_attribute(module, :foo)
end
def __after_compile__(%Macro.Env{module: module}, bin) when is_binary(bin) do
send(self(), Module.get_attribute(module, :foo))
end
end
Module.create(ModuleTest.NoRaise, contents, __ENV__)
assert_received 42
end
test "in memory modules are tagged as so" do
+14 -1
View File
@@ -156,7 +156,20 @@ defmodule RecordTest do
)
assert user == {RecordTest, :name, {RecordTest, :inner_name, :inner_age}}
assert Process.info(self(), :messages) == {:messages, [:inner_age, :inner_name, :name]}
assert for(_ <- 1..3, do: assert_receive(_)) == [:inner_age, :inner_name, :name]
user =
user(
name: send(self(), :name),
age:
user(
age: send(self(), :inner_age),
name: send(self(), :inner_name)
)
)
assert user == {RecordTest, :name, {RecordTest, :inner_name, :inner_age}}
assert for(_ <- 1..3, do: assert_receive(_)) == [:name, :inner_age, :inner_name]
end
Record.defrecord(
+8 -2
View File
@@ -70,8 +70,14 @@ defmodule ExUnit.Diff do
diff_quoted(number, right, env)
end
defp diff_quoted({:++, _, _} = left, right, env) when is_list(right) do
diff_maybe_improper_list(left, right, env)
defp diff_quoted({:++, meta, [prefix, suffix]}, right, env) when is_list(right) do
case prefix do
{_, [expanded: expanded] ++ _, _} ->
diff_maybe_improper_list({:++, meta, [expanded, suffix]}, right, env)
_ ->
diff_maybe_improper_list({:++, meta, [prefix, suffix]}, right, env)
end
end
defp diff_quoted({:{}, _, left}, right, env) when is_tuple(right) do
+7 -2
View File
@@ -345,7 +345,6 @@ defmodule ExUnit.DiffTest do
refute_diff([:a, :b] ++ [:c] = [:a, :b], "[:a, :b] ++ [-:c-]", "[:a, :b]")
refute_diff([:a, :c] ++ [:b] = [:a, :b], "[:a, -:c-] ++ [:b]", "[:a, :b]")
refute_diff([:a] ++ [:b] ++ [:c] = [:a, :b], "[:a] ++ [:b] ++ [-:c-]", "[:a, :b]")
assert_diff([:a] ++ :b = [:a | :b], [])
@@ -353,10 +352,16 @@ defmodule ExUnit.DiffTest do
refute_diff([:a, :b] ++ :c = [:a, :b, :c], "[:a, :b] ++ -:c-", "[:a, :b, +:c+]")
refute_diff([:a] ++ [:b] ++ :c = [:a, :b, :c], "[:a] ++ [:b] ++ -:c-", "[:a, :b, +:c+]")
refute_diff([:a] ++ [:b] = :a, "-[:a] ++ [:b]-", "+:a+")
end
@a [:a]
test "concat lists with module attributes" do
assert_diff(@a ++ [:b] = [:a, :b], [])
refute_diff(@a ++ [:b] = [:a], "[:a] ++ [-:b-]", "[:a]")
refute_diff(@a ++ [:b] = [:b], "[-:a-] ++ [:b]", "[:b]")
end
test "mixed lists" do
refute_diff([:a | :b] = [:a, :b], "[:a | -:b-]", "[:a, +:b+]")
refute_diff([:a, :b] = [:a | :b], "[:a, -:b-]", "[:a | +:b+]")
+10 -3
View File
@@ -357,15 +357,22 @@ defmodule Logger.Translator do
[]
end
defp child_debug(:debug, restart_type: restart, shutdown: shutdown, child_type: type) do
["\nRestart: ", inspect(restart), "\nShutdown: ", inspect(shutdown)] ++
["\nType: ", inspect(type)]
defp child_debug(:debug, opts) do
for {key, value} <- opts do
child_debug_key(key, value)
end
end
defp child_debug(_min_level, _child) do
[]
end
defp child_debug_key(:restart_type, value), do: ["\nRestart: " | inspect(value)]
defp child_debug_key(:shutdown, value), do: ["\nShutdown: " | inspect(value)]
defp child_debug_key(:child_type, value), do: ["\nType: " | inspect(value)]
defp child_debug_key(:significant, value), do: if(value, do: "\nSignificant: true", else: [])
defp child_debug_key(_, _), do: []
defp report_crash(min_level, [[{:initial_call, initial_call} | crashed], linked]) do
mfa = initial_call_to_mfa(initial_call)
report_crash(min_level, crashed, [{:initial_call, mfa}], linked)
@@ -25,6 +25,12 @@ defmodule Mix.Compilers.ApplicationTracer do
:ok
end
# Also skip __impl__ calls inside protocols as they are meant
# to invert dependencies.
def trace({_, _, _, :__impl__, _}, _env) do
:ok
end
def trace({type, meta, module, function, arity}, env)
when type in [:remote_function, :remote_macro, :imported_function, :imported_macro] do
# Unknown modules need to be looked up and filtered later
@@ -110,8 +116,8 @@ defmodule Mix.Compilers.ApplicationTracer do
def format_warning({:undefined_app, app, module, function, arity}) do
"""
#{Exception.format_mfa(module, function, arity)} defined in application :#{app} \
is used by the current application but the current application does not directly \
depend on :#{app}. To fix this, you must do one of:
is used by the current application but the current application does not depend \
on :#{app}. To fix this, you must do one of:
1. If :#{app} is part of Erlang/Elixir, you must include it under \
:extra_applications inside "def application" in your mix.exs
@@ -120,7 +126,7 @@ defmodule Mix.Compilers.ApplicationTracer do
in your mix.exs
3. In case you don't want to add a requirement to :#{app}, you may \
optionally skip this warning by adding [xref: [exclude: #{inspect(module)}]] \
optionally skip this warning by adding [xref: [exclude: [#{inspect(module)}]]] \
to your "def project" in mix.exs
"""
end
+7 -2
View File
@@ -271,7 +271,7 @@ defmodule Mix.Compilers.Erlang do
defp to_diagnostics(warnings_or_errors, severity) do
for {file, issues} <- warnings_or_errors,
{line, module, data} <- issues do
position = if is_integer(line) and line >= 1, do: line
position = line(line)
%Mix.Task.Compiler.Diagnostic{
file: Path.absname(file),
@@ -288,7 +288,12 @@ defmodule Mix.Compilers.Erlang do
for {_, warnings} <- entries,
{file, issues} <- warnings,
{line, module, message} <- issues do
IO.puts("#{file}:#{line}: Warning: #{module.format_error(message)}")
IO.puts("#{file}:#{line(line)}: Warning: #{module.format_error(message)}")
end
end
defp line({line, _column}) when is_integer(line) and line >= 1, do: line
# TODO: remove when we require OTP 24
defp line(line) when is_integer(line) and line >= 1, do: line
defp line(_), do: nil
end
+6 -2
View File
@@ -176,7 +176,11 @@ defmodule Mix.Config do
"""
@doc deprecated: "Use Config.Reader.read_imports!/2 instead"
def eval!(file, imported_paths \\ []) do
Config.Reader.read_imports!(file, imports: imported_paths)
Config.Reader.read_imports!(file,
imports: imported_paths,
env: Mix.env(),
target: Mix.target()
)
end
@doc """
@@ -195,7 +199,7 @@ defmodule Mix.Config do
@doc deprecated: "Use Config.Reader.read!/2 instead"
@spec read!(Path.t(), [Path.t()]) :: keyword
def read!(file, imported_paths \\ []) do
Config.Reader.read!(file, imports: imported_paths)
Config.Reader.read!(file, imports: imported_paths, env: Mix.env(), target: Mix.target())
end
@doc """
+13 -2
View File
@@ -36,7 +36,7 @@ defmodule Mix.Local do
def path_for(:escript), do: Mix.path_for(:escripts)
@doc """
Appends archives paths into Erlang code path.
Appends archive paths to the Erlang code path.
"""
def append_archives do
for archive <- archives_ebins() do
@@ -48,7 +48,18 @@ defmodule Mix.Local do
end
@doc """
Appends Mix paths into Erlang code path.
Removes archive paths from Erlang code path.
"""
def remove_archives do
for archive <- archives_ebins() do
Code.delete_path(archive)
end
:ok
end
@doc """
Appends Mix paths to the Erlang code path.
"""
def append_paths do
Enum.each(mix_paths(), &Code.append_path(&1))
+1 -5
View File
@@ -808,11 +808,7 @@ defmodule Mix.Release do
{:ok, {_, chunks}} ->
chunks = for {name, chunk} <- chunks, is_binary(chunk), do: {name, chunk}
{:ok, binary} = :beam_lib.build_module(chunks)
{:ok, fd} = :ram_file.open(binary, [:write, :binary])
{:ok, _} = :ram_file.compress(fd)
{:ok, binary} = :ram_file.get_file(fd)
:ok = :ram_file.close(fd)
{:ok, binary}
{:ok, :zlib.gzip(binary)}
{:error, _, _} = error ->
error
+26 -16
View File
@@ -355,12 +355,12 @@ defmodule Mix.Task do
alias = Mix.Project.config()[:aliases][String.to_atom(task)]
cond do
is_nil(alias) ->
run_task(proj, task, args)
Mix.TasksServer.run({:alias, task, proj}) ->
alias && Mix.TasksServer.run({:alias, task, proj}) ->
run_alias(List.wrap(alias), args, proj, task, :ok)
!Mix.TasksServer.get({:task, task, proj}) ->
run_task(proj, task, args)
true ->
:noop
end
@@ -371,8 +371,8 @@ defmodule Mix.Task do
# 2. Otherwise we compile and load dependencies
# 3. Finally, we compile the current project in hope it is available.
module =
get_task_or_run(proj, task, fn -> Mix.Task.run("deps.loadpaths") end) ||
get_task_or_run(proj, task, fn -> Mix.Task.run("compile", []) end) ||
get_task_or_run(proj, task, fn -> run("deps.loadpaths") end) ||
get_task_or_run(proj, task, fn -> run("compile", []) end) ||
get!(task)
recursive = recursive(module)
@@ -387,15 +387,16 @@ defmodule Mix.Task do
Mix.ProjectStack.on_recursing_root(fn -> run(task, args) end)
Mix.TasksServer.run({:task, task, proj}) ->
if Mix.debug?(), do: output_task_debug_info(task, args, proj)
run_requirements(module)
try do
module.run(args)
rescue
e in OptionParser.ParseError ->
Mix.raise("Could not invoke task #{inspect(task)}: " <> Exception.message(e))
end
with_debug(task, args, proj, fn ->
try do
module.run(args)
rescue
e in OptionParser.ParseError ->
Mix.raise("Could not invoke task #{inspect(task)}: " <> Exception.message(e))
end
end)
true ->
:noop
@@ -409,8 +410,16 @@ defmodule Mix.Task do
end)
end
defp output_task_debug_info(task, args, proj) do
Mix.shell().info("** Running mix " <> task_to_string(task, args) <> project_to_string(proj))
defp with_debug(task, args, proj, fun) do
if Mix.debug?() do
shell = Mix.shell()
shell.info(["-> Running mix ", task_to_string(task, args), project_to_string(proj)])
{time, res} = :timer.tc(fun)
shell.info(["<- Ran mix ", task, " in ", Integer.to_string(div(time, 1000)), "ms"])
res
else
fun.()
end
end
defp project_to_string(nil), do: ""
@@ -450,7 +459,8 @@ defmodule Mix.Task do
run_alias(t, alias_args, proj, original_task, res)
end
defp run_alias([], _alias_task, _proj, _original_task, res) do
defp run_alias([], _alias_args, proj, original_task, res) do
Mix.TasksServer.put({:task, original_task, proj})
res
end
+10 -5
View File
@@ -103,12 +103,13 @@ defmodule Mix.Tasks.Compile.Elixir do
manifest = manifest()
configs = [Mix.Project.config_mtime() | Mix.Tasks.Compile.Erlang.manifests()]
force = opts[:force] || Mix.Utils.stale?(configs, [manifest])
{tracers, opts} = pop_tracers(opts)
opts =
(project[:elixirc_options] || [])
|> Keyword.merge(opts)
|> xref_exclude_opts(project)
|> tracers_opts()
|> tracers_opts(tracers)
|> profile_opts()
Mix.Compilers.Elixir.compile(manifest, srcs, dest, [:ex], force, opts)
@@ -134,17 +135,21 @@ defmodule Mix.Tasks.Compile.Elixir do
end
end
defp tracers_opts(opts) do
defp pop_tracers(opts) do
case Keyword.pop_values(opts, :tracer) do
{[], opts} ->
opts
{[], opts}
{tracers, opts} ->
tracers = Enum.map(tracers, &Module.concat([&1]))
Keyword.update(opts, :tracers, tracers, &(tracers ++ &1))
{Enum.map(tracers, &Module.concat([&1])), opts}
end
end
defp tracers_opts(opts, tracers) do
tracers = tracers ++ Code.get_compiler_option(:tracers)
Keyword.update(opts, :tracers, tracers, &(tracers ++ &1))
end
defp profile_opts(opts) do
case Keyword.fetch(opts, :profile) do
{:ok, "time"} -> Keyword.put(opts, :profile, :time)
+3 -2
View File
@@ -100,9 +100,10 @@ defmodule Mix.Tasks.Compile.Erlang do
file = Erlang.to_erl_file(Path.rootname(input, ".erl"))
case :compile.file(file, erlc_options) do
{:error, :badarg} ->
# TODO: Don't handle {:error, :badarg} when we require OTP 24
error when error == :error or error == {:error, :badarg} ->
message =
"Compiling Erlang #{inspect(file)} failed with ArgumentError, probably because of invalid :erlc_options"
"Compiling Erlang file #{inspect(file)} failed, probably because of invalid :erlc_options"
Mix.raise(message)
+2 -8
View File
@@ -197,14 +197,8 @@ defmodule Mix.Tasks.Deps.Compile do
config_path = Path.join(dep_path, "mix.rebar.config")
lib_path = Path.join(config[:env_path], "lib/*/ebin")
# REBAR_BARE_COMPILER_OUTPUT_DIR is only honored by rebar3 >= 3.14
env = [
{"REBAR_CONFIG", config_path},
{"TERM", "dumb"},
{"REBAR_BARE_COMPILER_OUTPUT_DIR", dep_path}
]
cmd = "#{rebar_cmd(dep)} bare compile --paths #{lib_path}"
env = [{"REBAR_CONFIG", config_path}, {"TERM", "dumb"}]
cmd = "#{rebar_cmd(dep)} bare compile --paths=#{inspect(lib_path)}"
File.mkdir_p!(dep_path)
File.write!(config_path, rebar_config(dep))
+2 -1
View File
@@ -252,7 +252,8 @@ defmodule Mix.Tasks.Xref do
{:ok, {_, [debug_info: debug_info]}} <- :beam_lib.chunks(path, [:debug_info]),
{:debug_info_v1, backend, data} <- debug_info,
{:ok, %{definitions: defs}} <- backend.debug_info(:elixir_v1, module, data, []),
do: walk_definitions(module, source, defs)
do: walk_definitions(module, source, defs),
else: (_ -> [])
end
defp walk_definitions(module, file, definitions) do
+4
View File
@@ -21,6 +21,10 @@ defmodule Mix.TasksServer do
Agent.update(@name, &Map.put(&1, tuple, true), @timeout)
end
def get(tuple) do
Agent.get(@name, &Map.get(&1, tuple), @timeout)
end
def delete_many(many) do
Agent.update(@name, &Map.drop(&1, many), @timeout)
end
+10 -2
View File
@@ -10,11 +10,14 @@ defmodule Mix.AliasesTest do
h: "hello",
p: &inspect/1,
compile: "hello",
cmd: &call_cmd/1,
help: ["help", "hello"],
"nested.h": [&Mix.shell().info(inspect(&1)), "h foo bar"]
]
]
end
defp call_cmd(args), do: Mix.Task.run("cmd", args)
end
setup do
@@ -50,13 +53,18 @@ defmodule Mix.AliasesTest do
assert Mix.Task.run("compile", []) == :noop
end
test "run alias override with recursion" do
test "run alias override with name-recursion" do
assert Mix.Task.rerun("help", []) == "Hello, World!"
assert_received {:mix_shell, :info, ["mix test" <> _]}
# Arguments are passed to the recursive task and not the last one.
assert ExUnit.CaptureIO.capture_io(fn ->
Mix.Task.rerun("help", ["test"]) == "Hello, World!"
Mix.Task.rerun("help", ["test"])
end) =~ "mix test"
end
test "run alias override with code-recursion" do
assert Mix.Task.rerun("cmd", ["echo", "hello"]) == :ok
assert_received {:mix_shell, :run, ["hello" <> _]}
end
end
+2 -2
View File
@@ -60,11 +60,11 @@ defmodule Mix.CLITest do
assert contents =~ "This won't appear"
contents = mix(~w[my_hello], [{"MIX_DEBUG", "1"}])
assert contents =~ "** Running mix my_hello (inside MyProject)"
assert contents =~ "-> Running mix my_hello (inside MyProject)"
assert contents =~ "** (Mix.Error) oops"
contents = mix(~w[my_hello], [{"MIX_DEBUG", "0"}])
refute contents =~ "** Running mix my_hello (inside MyProject)"
refute contents =~ "-> Running mix my_hello (inside MyProject)"
refute contents =~ "** (Mix.Error) oops"
end)
end
+2 -1
View File
@@ -59,7 +59,8 @@ defmodule Mix.TaskTest do
Mix.shell(Mix.Shell.IO)
Mix.debug(true)
assert ExUnit.CaptureIO.capture_io(fn -> Mix.Task.run("hello") end) =~ "** Running mix hello"
assert ExUnit.CaptureIO.capture_io(fn -> Mix.Task.run("hello") end) =~
~r"-> Running mix hello\n<- Ran mix hello in \d+ms"
after
Mix.shell(Mix.Shell.Process)
Mix.debug(false)
+5 -1
View File
@@ -19,7 +19,11 @@ defmodule Mix.Tasks.ArchiveTest do
File.rm_rf!(tmp_path("userhome"))
System.put_env("MIX_ARCHIVES", tmp_path("userhome/.mix/archives/"))
Mix.Project.push(ArchiveProject)
:ok
on_exit(fn ->
Mix.Local.remove_archives()
System.delete_env("MIX_ARCHIVES")
end)
end
test "archive build" do
+14 -1
View File
@@ -57,6 +57,19 @@ defmodule Mix.Tasks.Compile.ElixirTest do
Code.put_compiler_option(:tracers, [])
end
test "compiles a project with a previously set custom tracer" do
Process.register(self(), __MODULE__)
Code.put_compiler_option(:tracers, [__MODULE__])
in_fixture("no_mixfile", fn ->
Mix.Tasks.Compile.Elixir.run([])
assert_received {:alias_reference, _meta, A}
assert_received {:alias_reference, _meta, B}
end)
after
Code.put_compiler_option(:tracers, [])
end
test "warns when Logger is used but not depended on" do
in_fixture("no_mixfile", fn ->
File.write!("lib/a.ex", """
@@ -67,7 +80,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
""")
message =
"Logger.info/1 defined in application :logger is used by the current application but the current application does not directly depend on :logger"
"Logger.info/1 defined in application :logger is used by the current application but the current application does not depend on :logger"
assert capture_io(:stderr, fn ->
Mix.Task.run("compile", [])
@@ -14,7 +14,7 @@ defmodule Mix.Tasks.Compile.ErlangTest do
@tag erlc_options: [{:d, 'foo', 'bar'}]
test "raises on invalid erlc_options" do
in_fixture("compile_erlang", fn ->
assert_raise Mix.Error, ~r"failed with ArgumentError", fn ->
assert_raise Mix.Error, ~r"Compiling Erlang file '.*' failed", fn ->
capture_io(fn ->
Mix.Tasks.Compile.Erlang.run([])
end)
+3 -1
View File
@@ -23,10 +23,12 @@ defmodule Mix.Tasks.Compile.YeccTest do
assert %Mix.Task.Compiler.Diagnostic{
compiler_name: "yecc",
file: ^file,
message: "syntax error before: '.'",
message: message,
position: 1,
severity: :error
} = diagnostic
assert message =~ "syntax error before: "
end)
assert File.regular?("src/test_ok.erl")
+19 -1
View File
@@ -69,11 +69,29 @@ defmodule Mix.Tasks.XrefTest do
assert_all_calls(files, output)
end
defp assert_all_calls(files, expected) do
test "returns empty on cover compiled modules" do
files = %{
"lib/a.ex" => """
defmodule A do
def a, do: A.a()
end
"""
}
assert_all_calls(files, [], fn ->
:cover.start()
:cover.compile_beam_directory(to_charlist(Mix.Project.compile_path()))
end)
after
:cover.stop()
end
defp assert_all_calls(files, expected, after_compile \\ fn -> :ok end) do
in_fixture("no_mixfile", fn ->
generate_files(files)
Mix.Task.run("compile")
after_compile.()
assert Enum.sort(Mix.Tasks.Xref.calls()) == Enum.sort(expected)
end)
end