Compare commits

...
39 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
60 changed files with 981 additions and 432 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
+47
View File
@@ -243,6 +243,53 @@ 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
+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.2
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}
]
+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
@@ -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
+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
@@ -2387,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
+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
@@ -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
@@ -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)
@@ -116,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
@@ -126,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
+18 -9
View File
@@ -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: ""
+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
+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
@@ -80,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