Compare commits

...
36 Commits
Author SHA1 Message Date
José Valim dfe6612e54 Release v1.18.5 2026-08-28 12:19:42 +02:00
José Valim 5230d73968 Fix recursion on charlist error path 2026-08-28 12:18:24 +02:00
sabiwara ef36b163b3 Add potential breaking change for unquote to changelog 2026-06-13 23:29:52 +09:00
Jonatan Männchen a622a8ef92 Correct builds.hex.pm Publish Condition in CI (#14772) 2025-09-15 23:15:03 +02:00
Jonatan Männchen 66bb6da847 Fix backport of workload identity & CI release scope (#14745)
Backported PRs:
* https://github.com/elixir-lang/elixir/pull/14604
* https://github.com/elixir-lang/elixir/pull/14627

The commits are based on the SBoM PR (#14241) which changed the
actions.
2025-09-05 09:12:16 +02:00
Jonatan Männchen eee55adcd0 Use Workload Identity Federation for Windows Trusted Signing (#14604) 2025-09-03 22:31:31 +02:00
Jonatan Männchen cbd5ffce3e tighten CI secret scope and move AWS config to environment vars (#14627)
* Add `environment: release` to the "publish-to-hex" job so that only
  workflows explicitly targeting the release environment can read
  sensitive values.
* Gate the job behind `if: ${{ vars.HEX_AWS_REGION }}` to avoid noisy
  failures in forks where the variable is not configured.
* Replace `${{ secrets.HEX_AWS_REGION }}` / `${{ secrets.HEX_AWS_S3_BUCKET }}`
  references with `${{ vars.* }}`.  These are not credentials, so
  environment-level *variables* are a better fit and keep them readable
  only by jobs that declare the environment.
* Remove Fastly secrets from the job-wide `env:` block and inject them
  only into the Fastly purge step, following the principle of least
  privilege.  Other steps no longer see these tokens.

Restricting secret visibility to an environment and to the exact step
that needs them reduces the blast radius of a compromised workflow run,
blocks accidental exposure in logs of unrelated steps, and stops forks
from obtaining privileged data.
2025-09-03 22:16:28 +02:00
José Valim a3dda22035 Do not mention OTP 28 yet on v1.18 2025-07-22 11:55:07 +02:00
José Valim 7184679788 Reduce filesystem names on Mix.install, see #14543 2025-05-29 17:17:00 +02:00
José Valim aeb2ea1dda Clarify regexes in OTP 28 2025-05-21 15:50:59 +02:00
José Valim 7b20c281d5 Release v1.18.4 2025-05-21 13:47:42 +02:00
José Valim c69f4407fd Fix ExUnit suite on Erlang/OTP 28 2025-05-21 12:50:21 +02:00
Wojtek Mach 88f65ae1a6 Install Hex and Rebar per OTP release (Elixir v1.18) (#14509) 2025-05-21 12:45:52 +02:00
José Valim 14d406fcd9 Raise nice error message when escaping maps/structs with references, closes #14497 2025-05-20 10:35:26 +02:00
Ahmed Elawad 08a9d4b9fc docs: Debugging page typo (#14502) 2025-05-18 09:00:36 +02:00
José Valim b3ab3696ec Update CI 2025-05-09 13:23:30 +02:00
José Valim afe4704666 Trace on definition callbacks, closes #14427 2025-05-09 09:37:20 +02:00
José Valim c51acfcbc5 Trace on load functions, see #14427 2025-05-09 09:37:16 +02:00
José Valim 332ff949a0 Ensure git checkout works with untracked files (#14428)
Closes #14425.
2025-05-09 09:37:12 +02:00
José Valim ab17a3c050 Emit trace events for super calls, closes #14395 2025-05-09 09:37:09 +02:00
Steffen Deusch 2522bba1bf IEx.Helpers.process_info/1 (#14418) 2025-05-09 09:37:05 +02:00
ruslandoga d00f172c59 Retry mix httpc with disabled middlebox_comp_mode (#14357)
Closes #14356
2025-05-09 09:36:58 +02:00
José Valim 0eb5289cd0 Add --no-listeners to mix compile and mix run 2025-05-08 10:18:50 +02:00
José Valim 7e01628b5d Compute beam location early 2025-04-25 20:14:08 +02:00
José Valim 6432e8655c Write modules before verification so modules can be found during verification 2025-04-25 20:14:01 +02:00
Jean Klingler e34495e304 Optimize map unions to avoid building long lists (#14215) 2025-04-08 23:39:53 +02:00
José Valim 64c4ecf9c7 Do not add duplicates on maps and tuples intersections 2025-04-08 23:35:17 +02:00
José Valim ef002e2b15 Properly track imported function calls in tracer
Closes #13878.
2025-04-08 09:28:50 +02:00
José Valim f89c5076f9 Do not crash on nested bitstrings
Closes #14391.
2025-04-03 11:51:53 +02:00
Fabian Becker 18d367c01b Remove deprecation warning check for 1.18 (#14382) 2025-03-31 17:02:48 +02:00
José Valim 65baed8681 Update Windows CI 2025-03-30 16:25:38 +02:00
José Valim 76c64a0f32 Recompile regexes when escaped from module attributes (#14381) 2025-03-30 16:13:40 +02:00
José Valim 6ac1d10f77 Do not run listeners when not checking the deps 2025-03-26 17:05:04 +01:00
Jean Klingler ca57cfe6f3 Handle non-binary bitstring in struct default values (#14363) 2025-03-25 18:08:02 +09:00
Jean Klingler c32f72581a Fix handling of ErlangError when :general key is chardata (#14329) 2025-03-15 19:19:08 +09:00
José Valim 8115bd38a1 Preserve backwards compatibility in elixir_erl, closes #14323 2025-03-14 20:13:13 +01:00
51 changed files with 949 additions and 207 deletions
+4 -4
View File
@@ -3,12 +3,12 @@ name: CI for Markdown content
on:
push:
branches:
- 'main'
- "main"
paths:
- 'lib/**/*.md'
- "lib/**/*.md"
pull_request:
paths:
- 'lib/**/*.md'
- "lib/**/*.md"
workflow_dispatch:
jobs:
@@ -18,7 +18,7 @@ jobs:
strategy:
fail-fast: false
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- name: Check out the repository
+1 -1
View File
@@ -81,7 +81,7 @@ jobs:
name: Windows Server 2019, Erlang/OTP ${{ matrix.otp_version }}
strategy:
matrix:
otp_version: ["25.3", "26.2", "27.1"]
otp_version: ["25.3", "26.2", "27.3"]
runs-on: windows-2022
steps:
- name: Configure Git
+3 -3
View File
@@ -10,7 +10,7 @@ permissions:
jobs:
notify:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
name: Notify
steps:
- uses: actions/checkout@v4
@@ -18,8 +18,8 @@ jobs:
fetch-depth: 50
- uses: erlef/setup-beam@v1
with:
otp-version: '25.0'
elixir-version: '1.14.0'
otp-version: "27.3"
elixir-version: "1.18.3"
- name: Run Elixir script
env:
ELIXIR_FORUM_TOKEN: ${{ secrets.ELIXIR_FORUM_TOKEN }}
+30 -12
View File
@@ -118,6 +118,7 @@ jobs:
sign:
needs: [build]
environment: release
strategy:
fail-fast: true
matrix:
@@ -129,21 +130,30 @@ jobs:
runs-on: ${{ matrix.flavor == 'linux' && 'ubuntu-22.04' || 'windows-2022' }}
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: actions/download-artifact@v4
with:
name: build-${{ matrix.flavor }}-elixir-otp-${{ matrix.otp }}
- name: Log in to Azure
if: ${{ matrix.flavor == 'windows' && vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: "Sign files with Trusted Signing"
if: github.repository == 'elixir-lang/elixir' && matrix.flavor == 'windows'
uses: azure/trusted-signing-action@v0.5.0
uses: azure/trusted-signing-action@0d74250c661747df006298d0fb49944c10f16e03 # v0.5.1
if: ${{ matrix.flavor == 'windows' && vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
with:
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
endpoint: https://eus.codesigning.azure.net/
trusted-signing-account-name: trusted-signing-elixir
certificate-profile-name: Elixir
trusted-signing-account-name: ${{ vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
certificate-profile-name: ${{ vars.AZURE_CERTIFICATE_PROFILE_NAME }}
files-folder: ${{ github.workspace }}
files-folder-filter: exe
file-digest: SHA256
@@ -223,16 +233,20 @@ jobs:
needs: [build, sign]
runs-on: ubuntu-22.04
concurrency: builds-hex-pm
environment: release
env:
AWS_ACCESS_KEY_ID: ${{ secrets.HEX_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.HEX_AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.HEX_AWS_REGION }}
AWS_S3_BUCKET: ${{ secrets.HEX_AWS_S3_BUCKET }}
FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }}
FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }}
FASTLY_KEY: ${{ secrets.HEX_FASTLY_KEY }}
AWS_REGION: ${{ vars.HEX_AWS_REGION }}
AWS_S3_BUCKET: ${{ vars.HEX_AWS_S3_BUCKET }}
OTP_GENERIC_VERSION: "25"
steps:
- name: "Check if variables are set up"
if: "${{ ! vars.HEX_AWS_REGION }}"
run: |
echo "Required variables for uploading to hex.pm are not set up, skipping..."
exit 1
- uses: actions/download-artifact@v4
with:
pattern: "{sign-*-elixir-otp-*,Docs}"
@@ -337,3 +351,7 @@ jobs:
for key in $(cat purge_keys.txt); do
purge "${key}"
done
env:
FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }}
FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }}
FASTLY_KEY: ${{ secrets.HEX_FASTLY_KEY }}
+60
View File
@@ -223,6 +223,60 @@ You may also prefer to write using guards:
def foo(x, y, z) when x == y and y == z
## v1.18.5 (2026-08-28)
### 1. Security
* [List] Avoid recursion when invalid charlists are given to `List.to_string/1` or `List.to_charlist/1` (CVE-2026-75758, GHSA-jf5q-v438-665c)
## v1.18.4 (2025-05-21)
This release includes initial support for Erlang/OTP 28, for those who want to try it out. In such cases, you may use Elixir v1.18.4 precompiled for Erlang/OTP 27, as it is binary compatible with Erlang/OTP 28. Note, however, that Erlang/OTP 28 no longer allows regexes to be defined in the module body and interpolated into an attribute. If you do this:
```elixir
@some_attribute ~r/foo/
def some_fun, do: @some_attribute
```
You must rewrite it to:
```elixir
def some_fun, do: ~r/foo/
```
### 1. Enhancements
#### IEx
* [IEx.Helpers] Add `IEx.Helpers.process_info/1` which prints process information
#### Mix
* [mix compile] Support the `--no-listeners` option
* [mix local] Retry HTTP requests with disabled middlebox comp mode depending on the failure reason
* [mix local.hex] Install Hex per OTP release
* [mix local.rebar] Install Hex per OTP release
* [mix run] Support the `--no-listeners` option
### 2. Bug fixes
#### Elixir
* [Kernel] Emit trace events for `@on_definition` callbacks
* [Kernel] Emit trace events for `@on_load` callbacks
* [Kernel] Emit trace events for `super` calls
* [Kernel] Emit trace events for imported function calls
* [Kernel] Optimize map unions to avoid building long lists
* [Kernel] Do not crash when type checking nested bitstrings in patterns
* [Kernel] Do not crash when non-binary bitstring is given as struct default value
* [Kernel] Recompile regexes when escaped from module attributes for Erlang/OTP 28 compatibility
* [Kernel] Preserve backwards compatibility in `elixir_erl`
#### Mix
* [mix deps.get] Ensure git checkout works when there are untracked files in the dependency
* [mix loadpaths] Do not run listeners when not checking the deps
## v1.18.3 (2025-03-06)
### 1. Enhancements
@@ -440,6 +494,12 @@ You may also prefer to write using guards:
* [mix test] `:warnings_as_errors` configuration in `:test_elixirc_options` is deprecated. Instead pass the `--warnings-as-errors` flag to `mix test`. Alternatively, you might alias the task: `aliases: [test: "test --warnings-as-errors"]`
* [Mix.Tasks.Compile] Deprecate `compilers/0` in favor of `Mix.Task.Compiler.compilers/0`
### 5. Potential breaking changes
#### Elixir
* [Kernel] Using unquote is deprecated when quote is used inside a pattern or guard
## v1.17
The CHANGELOG for v1.17 releases can be found [in the v1.17 branch](https://github.com/elixir-lang/elixir/blob/v1.17/CHANGELOG.md).
+1 -1
View File
@@ -1 +1 @@
1.18.3
1.18.5
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/sh
set -e
ELIXIR_VERSION=1.18.3
ELIXIR_VERSION=1.18.5
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
cat <<USAGE >&2
+1 -1
View File
@@ -1,6 +1,6 @@
@echo off
set ELIXIR_VERSION=1.18.3
set ELIXIR_VERSION=1.18.5
if ""%1""=="""" if ""%2""=="""" goto documentation
if /I ""%1""==""--help"" if ""%2""=="""" goto documentation
+1 -1
View File
@@ -2530,7 +2530,7 @@ defmodule ErlangError do
{:ok, reason, IO.iodata_to_binary([":\n\n" | Enum.map(args_errors, &arg_error/1)])}
general = extra[:general] ->
{:ok, reason, ": " <> general}
{:ok, reason, ": " <> IO.chardata_to_string(general)}
true ->
:error
+25 -5
View File
@@ -343,13 +343,20 @@ defimpl Inspect, for: List do
close = color_doc("]", :list, opts)
cond do
lists == :as_charlists or (lists == :infer and List.ascii_printable?(term, printable_limit)) ->
inspected =
case Identifier.escape(IO.chardata_to_string(term), ?", printable_limit) do
{escaped, ""} -> [?~, ?c, ?", escaped, ?"]
{escaped, _} -> [?~, ?c, ?", escaped, ?", " ++ ..."]
(lists == :as_charlists and unicode_list?(term, printable_limit)) or
(lists == :infer and List.ascii_printable?(term, printable_limit)) ->
{split, tail} =
if is_integer(printable_limit) do
case Enum.split(term, printable_limit) do
{split, []} -> {split, []}
{split, _} -> {split, " ++ ..."}
end
else
{term, []}
end
{escaped, _} = Identifier.escape(IO.chardata_to_string(split), ?")
inspected = [?~, ?c, ?", escaped, ?" | tail]
color_doc(IO.iodata_to_binary(inspected), :charlist, opts)
keyword?(term) ->
@@ -360,6 +367,19 @@ defimpl Inspect, for: List do
end
end
defp unicode_list?(_, 0), do: true
defp unicode_list?([char | rest], counter)
when char in 0..0xD7FF or char in 0xE000..0x10FFFF,
do: unicode_list?(rest, decrement(counter))
defp unicode_list?([], _counter), do: true
defp unicode_list?(_, _counter), do: false
@compile {:inline, decrement: 1}
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
@doc false
def keyword({key, value}, opts) do
key = color_doc(Macro.inspect_atom(:key, key), :atom, opts)
+2 -2
View File
@@ -17,8 +17,8 @@ defmodule Inspect.Opts do
is `:decimal` and if it is printable, otherwise in bit syntax. See
`String.printable?/1` to learn when a string is printable.
* `:charlists` - when `:as_charlists` all lists will be printed as charlists,
non-printable elements will be escaped.
* `:charlists` - when `:as_charlists` all charlists will be printed as charlists,
non-printable code points will be escaped. Other lists will be printed as lists.
When `:as_lists` all lists will be printed as lists.
+24 -13
View File
@@ -3716,26 +3716,25 @@ defmodule Kernel do
case function? do
true ->
value =
case Module.__get_attribute__(env.module, name, line, false) do
{_, doc} when doc_attr? -> doc
other -> other
end
case Module.__get_attribute__(env.module, name, line, false) do
{_, doc} when doc_attr? ->
do_at_escape(name, doc)
try do
:elixir_quote.escape(value, :none, false)
rescue
ex in [ArgumentError] ->
raise ArgumentError,
"cannot inject attribute @#{name} into function/macro because " <>
Exception.message(ex)
%{__struct__: Regex, source: source, opts: opts} = regex ->
case :erlang.system_info(:otp_release) < [?2, ?8] do
true -> do_at_escape(name, regex)
false -> quote(do: Regex.compile!(unquote(source), unquote(opts)))
end
value ->
do_at_escape(name, value)
end
false when doc_attr? ->
quote do
case Module.__get_attribute__(__MODULE__, unquote(name), unquote(line), false) do
{_, doc} -> doc
other -> other
value -> value
end
end
@@ -3770,6 +3769,17 @@ defmodule Kernel do
raise ArgumentError, "expected 0 or 1 argument for @#{name}, got: #{length(args)}"
end
defp do_at_escape(name, value) do
try do
:elixir_quote.escape(value, :none, false)
rescue
ex in [ArgumentError] ->
raise ArgumentError,
"cannot inject attribute @#{name} into function/macro because " <>
Exception.message(ex)
end
end
# Those are always compile-time dependencies, so we can skip the trace.
defp collect_traces(:before_compile, arg, _env), do: {arg, []}
defp collect_traces(:after_compile, arg, _env), do: {arg, []}
@@ -6461,6 +6471,7 @@ defmodule Kernel do
end
defp compile_regex(binary_or_tuple, options) do
# TODO: Remove this when we require Erlang/OTP 28+
case is_binary(binary_or_tuple) and :erlang.system_info(:otp_release) < [?2, ?8] do
true ->
Macro.escape(Regex.compile!(binary_or_tuple, :binary.list_to_bin(options)))
+3 -8
View File
@@ -258,13 +258,6 @@ defmodule Kernel.ParallelCompiler do
{status, modules_or_errors, info} =
try do
spawn_workers(schedulers, cache, files, output, options)
else
{:ok, outcome, info} ->
beam_timestamp = Keyword.get(options, :beam_timestamp)
{:ok, write_module_binaries(outcome, output, beam_timestamp), info}
{:error, errors, info} ->
{:error, errors, info}
after
Module.ParallelChecker.stop(cache)
end
@@ -288,6 +281,7 @@ defmodule Kernel.ParallelCompiler do
{outcome, state} =
spawn_workers(files, %{}, %{}, [], %{}, [], [], %{
beam_timestamp: Keyword.get(options, :beam_timestamp),
dest: Keyword.get(options, :dest),
each_cycle: Keyword.get(options, :each_cycle, fn -> {:runtime, [], []} end),
each_file: Keyword.get(options, :each_file, fn _, _ -> :ok end) |> each_file(),
@@ -345,9 +339,10 @@ defmodule Kernel.ParallelCompiler do
## Verification
defp verify_modules(result, compile_warnings, dependent_modules, state) do
modules = write_module_binaries(result, state.output, state.beam_timestamp)
runtime_warnings = maybe_check_modules(result, dependent_modules, state)
info = %{compile_warnings: Enum.reverse(compile_warnings), runtime_warnings: runtime_warnings}
{{:ok, result, info}, state}
{{:ok, modules, info}, state}
end
defp maybe_check_modules(result, runtime_modules, state) do
+2 -2
View File
@@ -1133,7 +1133,7 @@ defmodule List do
Please check the given list or call inspect/1 to get the list representation, got:
#{inspect(list)}
#{inspect(list, charlists: :as_lists)}
"""
else
result when is_binary(result) ->
@@ -1185,7 +1185,7 @@ defmodule List do
Please check the given list or call inspect/1 to get the list representation, got:
#{inspect(list)}
#{inspect(list, charlists: :as_lists)}
"""
else
result when is_list(result) ->
+138 -24
View File
@@ -1264,8 +1264,115 @@ defmodule Module.Types.Descr do
defp map_only?(descr), do: empty?(Map.delete(descr, :map))
# Union is list concatenation
defp map_union(dnf1, dnf2), do: dnf1 ++ (dnf2 -- dnf1)
defp map_union(dnf1, dnf2) do
# Union is just concatenation, but we rely on some optimization strategies to
# avoid the list to grow when possible
# first pass trying to identify patterns where two maps can be fused as one
with [map1] <- dnf1,
[map2] <- dnf2,
optimized when optimized != nil <- maybe_optimize_map_union(map1, map2) do
[optimized]
else
# otherwise we just concatenate and remove structural duplicates
_ -> dnf1 ++ (dnf2 -- dnf1)
end
end
defp maybe_optimize_map_union({tag1, pos1, []} = map1, {tag2, pos2, []} = map2) do
case map_union_optimization_strategy(tag1, pos1, tag2, pos2) do
:all_equal ->
map1
:any_map ->
{:open, %{}, []}
{:one_key_difference, key, v1, v2} ->
new_pos = Map.put(pos1, key, union(v1, v2))
{tag1, new_pos, []}
:left_subtype_of_right ->
map2
:right_subtype_of_left ->
map1
nil ->
nil
end
end
defp maybe_optimize_map_union(_, _), do: nil
defp map_union_optimization_strategy(tag1, pos1, tag2, pos2)
defp map_union_optimization_strategy(tag, pos, tag, pos), do: :all_equal
defp map_union_optimization_strategy(:open, empty, _, _) when empty == %{}, do: :any_map
defp map_union_optimization_strategy(_, _, :open, empty) when empty == %{}, do: :any_map
defp map_union_optimization_strategy(tag, pos1, tag, pos2)
when map_size(pos1) == map_size(pos2) do
:maps.iterator(pos1)
|> :maps.next()
|> do_map_union_optimization_strategy(pos2, :all_equal)
end
defp map_union_optimization_strategy(:open, pos1, _, pos2)
when map_size(pos1) <= map_size(pos2) do
:maps.iterator(pos1)
|> :maps.next()
|> do_map_union_optimization_strategy(pos2, :right_subtype_of_left)
end
defp map_union_optimization_strategy(_, pos1, :open, pos2)
when map_size(pos1) >= map_size(pos2) do
:maps.iterator(pos2)
|> :maps.next()
|> do_map_union_optimization_strategy(pos1, :right_subtype_of_left)
|> case do
:right_subtype_of_left -> :left_subtype_of_right
nil -> nil
end
end
defp map_union_optimization_strategy(_, _, _, _), do: nil
defp do_map_union_optimization_strategy(:none, _, status), do: status
defp do_map_union_optimization_strategy({key, v1, iterator}, pos2, status) do
with %{^key => v2} <- pos2,
next_status when next_status != nil <- map_union_next_strategy(key, v1, v2, status) do
do_map_union_optimization_strategy(:maps.next(iterator), pos2, next_status)
else
_ -> nil
end
end
defp map_union_next_strategy(key, v1, v2, status)
# structurally equal values do not impact the ongoing strategy
defp map_union_next_strategy(_key, same, same, status), do: status
defp map_union_next_strategy(key, v1, v2, :all_equal) do
if key != :__struct__, do: {:one_key_difference, key, v1, v2}
end
defp map_union_next_strategy(_key, v1, v2, {:one_key_difference, _, d1, d2}) do
# we have at least two key differences now, we switch strategy
# if both are subtypes in one direction, keep checking
cond do
subtype?(d1, d2) and subtype?(v1, v2) -> :left_subtype_of_right
subtype?(d2, d1) and subtype?(v2, v1) -> :right_subtype_of_left
true -> nil
end
end
defp map_union_next_strategy(_key, v1, v2, :left_subtype_of_right) do
if subtype?(v1, v2), do: :left_subtype_of_right
end
defp map_union_next_strategy(_key, v1, v2, :right_subtype_of_left) do
if subtype?(v2, v1), do: :right_subtype_of_left
end
# Given two unions of maps, intersects each pair of maps.
defp map_intersection(dnf1, dnf2) do
@@ -1275,7 +1382,16 @@ defmodule Module.Types.Descr do
acc ->
try do
{tag, fields} = map_literal_intersection(tag1, pos1, tag2, pos2)
[{tag, fields, negs1 ++ negs2} | acc]
entry = {tag, fields, negs1 ++ negs2}
# Imagine a, b, c, where a is closed and b and c are open with
# no keys in common. The result in both cases will be a and we
# want to avoid adding duplicates, especially as intersection
# is a cartesian product.
case :lists.member(entry, acc) do
true -> acc
false -> [entry | acc]
end
catch
:empty -> acc
end
@@ -1738,29 +1854,19 @@ defmodule Module.Types.Descr do
defp map_non_negated_fuse(maps) do
Enum.reduce(maps, [], fn map, acc ->
case Enum.split_while(acc, &non_fusible_maps?(map, &1)) do
{_, []} ->
[map | acc]
{others, [match | rest]} ->
fused = map_non_negated_fuse_pair(map, match)
others ++ [fused | rest]
end
fuse_with_first_fusible(map, acc)
end)
end
# Two maps are fusible if they differ in at most one element.
defp non_fusible_maps?({_, fields1, []}, {_, fields2, []}) do
Enum.count_until(fields1, fn {key, value} -> Map.fetch!(fields2, key) != value end, 2) > 1
end
defp fuse_with_first_fusible(map, []), do: [map]
defp map_non_negated_fuse_pair({tag, fields1, []}, {_, fields2, []}) do
fields =
symmetrical_merge(fields1, fields2, fn _k, v1, v2 ->
if v1 == v2, do: v1, else: union(v1, v2)
end)
{tag, fields, []}
defp fuse_with_first_fusible(map, [candidate | rest]) do
if fused = maybe_optimize_map_union(map, candidate) do
# we found a fusible candidate, we're done
[fused | rest]
else
[candidate | fuse_with_first_fusible(map, rest)]
end
end
# If all fields are the same except one, we can optimize map difference.
@@ -1931,8 +2037,16 @@ defmodule Module.Types.Descr do
reduce: [] do
acc ->
case tuple_literal_intersection(tag1, elements1, tag2, elements2) do
{tag, fields} -> [{tag, fields, negs1 ++ negs2} | acc]
:empty -> acc
{tag, elements} ->
entry = {tag, elements, negs1 ++ negs2}
case :lists.member(entry, acc) do
true -> acc
false -> [entry | acc]
end
:empty ->
acc
end
end
|> case do
+4
View File
@@ -335,6 +335,10 @@ defmodule Module.Types.Pattern do
{type, context}
end
def of_match_var({:<<>>, _meta, args}, _expected, _expr, stack, context) do
{binary(), Of.binary(args, :match, stack, context)}
end
def of_match_var(ast, expected, expr, stack, context) do
of_match(ast, expected, expr, :default, stack, context)
end
@@ -110,7 +110,7 @@ When using `IEx`, you may pass `--dbg pry` as an option to "stop" the code execu
$ iex --dbg pry
```
Or to debug inside a of a project:
Or to debug inside of a project:
```console
$ iex --dbg pry -S mix
+6 -3
View File
@@ -222,7 +222,7 @@ store_definition(Meta, Kind, CheckClauses, Name, Arity, DefaultsArgs, Guards, Bo
[store_definition(none, Kind, Meta, Name, length(DefaultArgs), File,
Module, 0, [Default]) || {_, DefaultArgs, _, _} = Default <- Defaults],
run_on_definition_callbacks(Kind, Module, Name, DefaultsArgs, Guards, Body, E),
run_on_definition_callbacks(Meta, Kind, Module, Name, DefaultsArgs, Guards, Body, E),
Tuple.
env_for_expansion(Kind, Tuple, E) when Kind =:= defmacro; Kind =:= defmacrop ->
@@ -267,10 +267,13 @@ def_to_clauses(Kind, Meta, Args, Guards, Body, E) ->
elixir_errors:file_error(Meta, E, elixir_expand, {missing_option, Kind, [do]})
end.
run_on_definition_callbacks(Kind, Module, Name, Args, Guards, Body, E) ->
run_on_definition_callbacks(Meta, Kind, Module, Name, Args, Guards, Body, E) ->
{_, Bag} = elixir_module:data_tables(Module),
Callbacks = ets:lookup_element(Bag, {accumulate, on_definition}, 2),
_ = [Mod:Fun(E, Kind, Name, Args, Guards, Body) || {Mod, Fun} <- lists:reverse(Callbacks)],
_ = [begin
elixir_env:trace({remote_function, Meta, Mod, Fun, 6}, E),
Mod:Fun(E, Kind, Name, Args, Guards, Body)
end || {Mod, Fun} <- lists:reverse(Callbacks)],
ok.
store_definition(CheckClauses, Kind, Meta, Name, Arity, File, Module, Defaults, Clauses)
+3 -1
View File
@@ -204,7 +204,9 @@ do_expand_import(Result, Meta, Name, Arity, Module, E, Trace) ->
{import, Receiver} ->
case expand_require(true, Meta, Receiver, Name, Arity, E, Trace) of
{macro, _, _} = Response -> Response;
error -> {function, Receiver, Name}
error ->
Trace andalso elixir_env:trace({remote_function, Meta, Receiver, Name, Arity}, E),
{function, Receiver, Name}
end;
false when Module == ?kernel ->
case elixir_rewrite:inline(Module, Name, Arity) of
+10
View File
@@ -92,6 +92,9 @@ elixir_to_erl(Tree, Ann) when is_binary(Tree) ->
%% considers a string in a binary to be encoded in latin1, so the bytes
%% are not changed in any fashion.
{bin, Ann, [{bin_element, Ann, {string, Ann, binary_to_list(Tree)}, default, default}]};
elixir_to_erl(Tree, Ann) when is_bitstring(Tree) ->
Segments = [elixir_to_erl_bitstring_segment(X, Ann) || X <- bitstring_to_list(Tree)],
{bin, Ann, Segments};
elixir_to_erl(Tree, Ann) when is_function(Tree) ->
case (erlang:fun_info(Tree, type) == {type, external}) andalso
(erlang:fun_info(Tree, env) == {env, []}) of
@@ -111,6 +114,13 @@ elixir_to_erl(Tree, Ann) ->
elixir_to_erl_cons([H | T], Ann) -> {cons, Ann, elixir_to_erl(H, Ann), elixir_to_erl_cons(T, Ann)};
elixir_to_erl_cons(T, Ann) -> elixir_to_erl(T, Ann).
elixir_to_erl_bitstring_segment(Int, Ann) when is_integer(Int) ->
{bin_element, Ann, {integer, Ann, Int}, default, [integer]};
elixir_to_erl_bitstring_segment(Rest, Ann) when is_bitstring(Rest) ->
Size = bit_size(Rest),
<<Int:Size>> = Rest,
{bin_element, Ann, {integer, Ann, Int}, {integer, Ann, Size}, [integer]}.
%% Returns a scope for translation.
scope(_Meta, ExpandCaptures) ->
+9 -1
View File
@@ -20,7 +20,15 @@ reduce_clauses([], Acc, OldStack, SAcc, _S) ->
{lists:reverse(Acc), SAcc#elixir_erl{stacktrace=OldStack}}.
each_clause({'catch', Meta, Raw, Expr}, S) ->
{Match, Guards} = elixir_utils:extract_splat_guards(Raw),
{Args, Guards} = elixir_utils:extract_splat_guards(Raw),
Match =
%% Elixir v1.17 and earlier emitted single argument
%% and may still be processed via debug_info
case Args of
[X] -> [throw, X];
[X, Y] -> [X, Y]
end,
{{clause, Line, [TKind, TMatches], TGuards, TBody}, TS} =
elixir_erl_clauses:clause(?ann(Meta), fun elixir_erl_pass:translate_args/3, Match, Expr, Guards, S),
+5 -4
View File
@@ -293,7 +293,7 @@ expand({'&', Meta, [{'/', ArityMeta, [{super, SuperMeta, Context}, Arity]} = Exp
case resolve_super(Meta, Arity, E) of
{Kind, Name, _} when Kind == def; Kind == defp ->
{{'&', Meta, [{'/', ArityMeta, [{Name, SuperMeta, Context}, Arity]}]}, S, E};
expand({'&', Meta, [{'/', ArityMeta, [{Name, SuperMeta, Context}, Arity]}]}, S, E);
_ ->
expand_fn_capture(Meta, Expr, S, E)
end;
@@ -343,7 +343,9 @@ expand({with, Meta, [_ | _] = Args}, S, E) ->
expand({super, Meta, Args}, S, E) when is_list(Args) ->
assert_no_match_or_guard_scope(Meta, "super", S, E),
{Kind, Name, _} = resolve_super(Meta, length(Args), E),
Arity = length(Args),
{Kind, Name, _} = resolve_super(Meta, Arity, E),
elixir_env:trace({local_function, Meta, Name, Arity}, E),
{EArgs, SA, EA} = expand_args(Args, S, E),
{{super, [{super, {Kind, Name}} | Meta], EArgs}, SA, EA};
@@ -899,8 +901,7 @@ expand_local(Meta, Name, Args, S, #{function := Function, context := Context} =
module_error(Meta, E, ?MODULE, {invalid_local_invocation, elixir_utils:guard_info(S), {Name, Meta, Args}});
nil ->
Arity = length(Args),
elixir_env:trace({local_function, Meta, Name, Arity}, E)
elixir_env:trace({local_function, Meta, Name, length(Args)}, E)
end,
{EArgs, SA, EA} = expand_args(Args, S, E),
+5 -3
View File
@@ -155,6 +155,7 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
put_compiler_modules([Module | CompilerModules]),
{Result, ModuleE, CallbackE} = eval_form(Line, Module, DataBag, Block, Vars, Prune, E),
CheckerInfo = checker_info(),
BeamLocation = beam_location(ModuleAsCharlist),
{Binary, PersistedAttributes, Autoload} =
elixir_erl_compiler:spawn(fun() ->
@@ -177,7 +178,7 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
'Elixir.Module':'__check_attributes__'(E, DataSet, DataBag),
AfterVerify = bag_lookup_element(DataBag, {accumulate, after_verify}, 2),
[elixir_env:trace({remote_function, [], VerifyMod, VerifyFun, 1}, CallbackE) ||
[elixir_env:trace({remote_function, [{line, Line}], VerifyMod, VerifyFun, 1}, CallbackE) ||
{VerifyMod, VerifyFun} <- AfterVerify],
%% Ensure there are no errors before we infer types
@@ -219,7 +220,7 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
{Binary, PersistedAttributes, Autoload}
end),
Autoload andalso code:load_binary(Module, beam_location(ModuleAsCharlist), Binary),
Autoload andalso code:load_binary(Module, BeamLocation, Binary),
put_compiler_modules(CompilerModules),
eval_callbacks(Line, DataBag, after_compile, [CallbackE, Binary], CallbackE),
elixir_env:trace({on_module, Binary, none}, ModuleE),
@@ -294,7 +295,8 @@ validate_on_load_attribute({on_load, Def}, Defs, Bag, Line, E) ->
elixir_errors:module_error([{line, Line}], E, ?MODULE, {undefined_function, on_load, Def});
{_Def, Kind, _Meta, _Clauses} when Kind == defmacro; Kind == defmacrop ->
elixir_errors:module_error([{line, Line}], E, ?MODULE, {bad_macro, on_load, Def});
{_Def, Kind, _Meta, _Clauses} ->
{{Name, Arity}, Kind, _Meta, _Clauses} ->
elixir_env:trace({local_function, [{line, Line}], Name, Arity}, E),
(Kind == defp) andalso ets:insert(Bag, {used_private, Def})
end;
validate_on_load_attribute(false, _Defs, _Bag, _Line, _E) -> ok.
+16 -4
View File
@@ -160,10 +160,19 @@ do_escape(BitString, _) when is_bitstring(BitString) ->
end;
do_escape(Map, Q) when is_map(Map) ->
TT = do_quote(lists:sort(maps:to_list(Map)), Q),
TT =
[if
is_reference(V) ->
argument_error(<<('Elixir.Kernel':inspect(Map, []))/binary, " contains a reference (",
('Elixir.Kernel':inspect(V, []))/binary, ") and therefore it cannot be escaped ",
"(it must be defined within a function instead). ", (bad_escape_hint())/binary>>);
true ->
{do_quote(K, Q), do_quote(V, Q)}
end || {K, V} <- lists:sort(maps:to_list(Map))],
{'%{}', [], TT};
do_escape([], _) -> [];
do_escape([], _) ->
[];
do_escape([H | T], #elixir_quote{unquote=false} = Q) ->
do_quote_simple_list(T, do_quote(H, Q), Q);
@@ -195,8 +204,11 @@ do_escape(Other, _) ->
bad_escape(Arg) ->
argument_error(<<"cannot escape ", ('Elixir.Kernel':inspect(Arg, []))/binary, ". ",
"The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, ",
"PIDs and remote functions in the format &Mod.fun/arity">>).
(bad_escape_hint())/binary>>).
bad_escape_hint() ->
<<"The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, ",
"PIDs and remote functions in the format &Mod.fun/arity">>.
%% Quote entry points
+10
View File
@@ -46,6 +46,12 @@ defmodule ExceptionTest do
{:io, :put_chars, [self(), <<222>>],
[error_info: %{module: __MODULE__, function: :dummy_error_extras}]}
])
assert %ErlangError{original: {:failed_load_cacerts, :enoent}, reason: ": this is chardata"} =
Exception.normalize(:error, {:failed_load_cacerts, :enoent}, [
{:pubkey_os_cacerts, :get, 0,
[error_info: %{module: __MODULE__, function: :dummy_error_chardata}]}
])
end
test "format/2 without stacktrace" do
@@ -1026,4 +1032,8 @@ defmodule ExceptionTest do
end
def dummy_error_extras(_exception, _stacktrace), do: %{general: "foo"}
def dummy_error_chardata(_exception, _stacktrace) do
%{general: ~c"this is " ++ [~c"chardata"], reason: ~c"this " ++ [~c"too"]}
end
end
+16 -1
View File
@@ -278,10 +278,15 @@ defmodule Inspect.ListTest do
test "printable limit" do
assert inspect(~c"hello world", printable_limit: 4) == ~s(~c"hell" ++ ...)
assert inspect(~c"hello", printable_limit: :infinity) == ~s(~c"hello")
# Non printable characters after the limit don't matter
assert inspect(~c"hello world" ++ [0], printable_limit: 4) == ~s(~c"hell" ++ ...)
# Non printable strings aren't affected by printable limit
assert inspect([0, 1, 2, 3, 4], printable_limit: 3) == ~s([0, 1, 2, 3, 4])
# Ensure a non-printable value after limit does not crash
assert inspect(~c"hello world" ++ [nil], printable_limit: 4) == ~s(~c"hell" ++ ...)
# Ensure a non-printable value after default limit does not hang
assert String.ends_with?(inspect(List.duplicate(?a, 5000) ++ [nil]), "\" ++ ...")
end
test "keyword" do
@@ -306,10 +311,20 @@ defmodule Inspect.ListTest do
assert inspect([0], charlists: :infer) == "[0]"
end
test "opt as strings" do
test "opt as charlists" do
assert inspect(~c"john" ++ [0] ++ ~c"doe", charlists: :as_charlists) == ~s(~c"john\\0doe")
assert inspect(~c"john", charlists: :as_charlists) == ~s(~c"john")
assert inspect([0], charlists: :as_charlists) == ~s(~c"\\0")
assert inspect([nil], charlists: :as_charlists) == "[nil]"
assert inspect([0xD800], charlists: :as_charlists) == "[55296]"
assert inspect([1_114_112], charlists: :as_charlists) == "[1114112]"
assert inspect([[?a]], charlists: :as_charlists) == ~s([~c"a"])
assert inspect([?a, ?b, ?c | nil], charlists: :as_charlists, printable_limit: 4) ==
"[97, 98, 99 | nil]"
assert inspect([?a, ?b, ?c | nil], charlists: :as_charlists, printable_limit: 3) ==
~s(~c"abc" ++ ...)
end
test "opt as lists" do
@@ -517,5 +517,55 @@ defmodule Kernel.LexicalTrackerTest do
refute URI in exports
refute URI in runtime
end
test "imported functions from quote adds dependencies" do
{{compile, exports, runtime, _}, _binding} =
Code.eval_string("""
defmodule Kernel.LexicalTrackerTest.QuotedFun do
import URI
defmacro parse_root() do
quote do
parse("/")
end
end
end
defmodule Kernel.LexicalTrackerTest.UsingQuotedFun do
require Kernel.LexicalTrackerTest.QuotedFun, as: QF
QF.parse_root()
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
end |> elem(3)
""")
assert URI in compile
refute URI in exports
refute URI in runtime
end
test "imported macro from quote adds dependencies" do
{{compile, exports, runtime, _}, _binding} =
Code.eval_string("""
defmodule Kernel.LexicalTrackerTest.QuotedMacro do
import Config
defmacro config_env() do
quote do
config_env()
end
end
end
defmodule Kernel.LexicalTrackerTest.UsingQuotedMacro do
require Kernel.LexicalTrackerTest.QuotedMacro, as: QM
def fun(), do: QM.config_env()
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
end |> elem(3)
""")
assert Config in compile
refute Config in exports
refute Config in runtime
end
end
end
+80 -2
View File
@@ -10,11 +10,15 @@ defmodule Kernel.TracersTest do
end
def trace(event, %Macro.Env{} = env) do
send(self(), {event, env})
for {pid, _} <- Registry.lookup(__MODULE__, :tracers) do
send(pid, {event, env})
end
:ok
end
setup_all do
start_supervised!({Registry, keys: :duplicate, name: __MODULE__})
Code.put_compiler_option(:tracers, [__MODULE__])
on_exit(fn ->
@@ -22,6 +26,11 @@ defmodule Kernel.TracersTest do
end)
end
setup do
Registry.register(__MODULE__, :tracers, :unused)
:ok
end
test "traces start and stop" do
compile_string("""
Foo
@@ -255,7 +264,7 @@ defmodule Kernel.TracersTest do
assert meta[:from_brackets]
compile_string("""
defmodule Foo do
defmodule TracerBracketAccess do
@foo %{bar: 3}
def a() do
@foo[:bar]
@@ -274,6 +283,75 @@ defmodule Kernel.TracersTest do
assert meta[:from_brackets]
end
test "traces on_load" do
compile_string("""
defmodule TracerOnLoad do
@on_load :init
def init, do: :ok
end
""")
assert_received {{:local_function, meta, :init, 0}, _}
assert meta[:line] == 1
end
def __before_compile__(_), do: :ok
def __after_compile__(_, _), do: :ok
def __after_verify__(_), do: :ok
def __on_definition__(_, _, _, _, _, _), do: :ok
test "traces compile time attributes" do
compile_string("""
defmodule TracerCompileAttributes do
@before_compile Kernel.TracersTest
@after_compile Kernel.TracersTest
@on_definition Kernel.TracersTest
@after_verify Kernel.TracersTest
def hello, do: :world
end
""")
assert_received {{:remote_function, meta, __MODULE__, :__before_compile__, 1}, _}
assert meta[:line] == 1
assert_received {{:remote_function, meta, __MODULE__, :__after_compile__, 2}, _}
assert meta[:line] == 1
assert_received {{:remote_function, meta, __MODULE__, :__after_verify__, 1}, _}
assert meta[:line] == 1
assert_received {{:remote_function, meta, __MODULE__, :__on_definition__, 6}, _}
assert meta[:line] == 6
end
test "traces super" do
compile_string("""
defmodule TracerOverridable do
def local(x), do: x
defoverridable [local: 1]
def local(x), do: super(x)
defmacro macro(x), do: x
defoverridable [macro: 1]
defmacro macro(x), do: super(x)
def capture(x), do: x
defoverridable [capture: 1]
def capture(x), do: tap(x, &super/1)
def capture_arg(x), do: x
defoverridable [capture_arg: 1]
def capture_arg(x), do: tap(x, &super(&1))
end
""")
assert_received {{:local_function, _, :"local (overridable 1)", 1}, _}
assert_received {{:local_function, _, :"macro (overridable 1)", 1}, _}
assert_received {{:local_function, _, :"capture (overridable 1)", 1}, _}
assert_received {{:local_function, _, :"capture_arg (overridable 1)", 1}, _}
refute_received {{:local_function, _, _, _}, _}
end
test "does not trace bind quoted twice" do
compile_string("""
quote bind_quoted: [foo: List.flatten([])] do
+12
View File
@@ -327,6 +327,12 @@ defmodule ListTest do
assert_raise ArgumentError, ~r"cannot convert the given list to a string", fn ->
List.to_string([:a, :b])
end
invalid = List.duplicate(?a, 4096) ++ [nil]
assert_raise ArgumentError, ~r/got:\n\n\[97, 97, .*\.\.\.\]\n\z/s, fn ->
List.to_string(invalid)
end
end
test "to_charlist/1" do
@@ -345,6 +351,12 @@ defmodule ListTest do
assert_raise ArgumentError, ~r"cannot convert the given list to a charlist", fn ->
List.to_charlist([:a, :b])
end
invalid = List.duplicate(?a, 4096) ++ [nil]
assert_raise ArgumentError, ~r/got:\n\n\[97, 97, .*\.\.\.\]\n\z/s, fn ->
List.to_charlist(invalid)
end
end
describe "myers_difference/2" do
+6
View File
@@ -136,6 +136,12 @@ defmodule MacroTest do
test "does not add context to quote" do
assert Macro.escape({:quote, [], [[do: :foo]]}) == {:{}, [], [:quote, [], [[do: :foo]]]}
end
test "inspects container when a reference cannot be escaped" do
assert_raise ArgumentError, ~r"~r/foo/ contains a reference", fn ->
Macro.escape(%{~r/foo/ | re_pattern: make_ref()})
end
end
end
describe "expand_once/2" do
+9
View File
@@ -395,6 +395,15 @@ defmodule MapTest do
assert quoted == {:%, [], [User, {:%{}, [], [{:foo, 1}]}]}
end
test "structs with bitstring defaults" do
defmodule WithBitstring do
defstruct bitstring: <<255, 127::7>>
end
info = Macro.struct_info!(WithBitstring, __ENV__)
assert info == [%{default: <<255, 127::7>>, field: :bitstring}]
end
test "defstruct can only be used once in a module" do
message =
"defstruct has already been called for TestMod, " <>
@@ -101,6 +101,46 @@ defmodule Module.Types.DescrTest do
assert union(difference(list(term()), list(integer())), list(integer()))
|> equal?(list(term()))
end
test "optimizations" do
# The tests are checking the actual implementation, not the semantics.
# This is why we are using structural comparisons.
# It's fine to remove these if the implementation changes, but breaking
# these might have an important impact on compile times.
# Optimization one: same tags, all but one key are structurally equal
assert union(
open_map(a: float(), b: atom()),
open_map(a: integer(), b: atom())
) == open_map(a: union(float(), integer()), b: atom())
assert union(
closed_map(a: float(), b: atom()),
closed_map(a: integer(), b: atom())
) == closed_map(a: union(float(), integer()), b: atom())
# Optimization two: we can tell that one map is a trivial subtype of the other:
assert union(
closed_map(a: term(), b: term()),
closed_map(a: float(), b: binary())
) == closed_map(a: term(), b: term())
assert union(
open_map(a: term()),
closed_map(a: float(), b: binary())
) == open_map(a: term())
assert union(
closed_map(a: float(), b: binary()),
open_map(a: term())
) == open_map(a: term())
assert union(
closed_map(a: term(), b: tuple([term(), term()])),
closed_map(a: float(), b: tuple([atom(), binary()]))
) == closed_map(a: term(), b: tuple([term(), term()]))
end
end
describe "intersection" do
@@ -251,6 +251,10 @@ defmodule Module.Types.PatternTest do
assert typecheck!([<<x::utf8>>], x) == integer()
end
test "nested" do
assert typecheck!([<<0, <<x::bitstring>>::binary>>], x) == binary()
end
test "error" do
assert typeerror!([<<x::binary-size(2), x::float>>], x) == ~l"""
incompatible types assigned to "x":
+17
View File
@@ -5,6 +5,23 @@ defmodule RegexTest do
doctest Regex
if System.otp_release() >= "28" do
test "module attribute" do
defmodule ModAttr do
@regex ~r/example/
def regex, do: @regex
@bare_regex :erlang.term_to_binary(@regex)
def bare_regex, do: :erlang.binary_to_term(@bare_regex)
# We don't rewrite outside of functions
assert @regex.re_pattern == :erlang.binary_to_term(@bare_regex).re_pattern
end
assert ModAttr.regex().re_pattern != ModAttr.bare_regex().re_pattern
end
end
test "multiline" do
refute Regex.match?(~r/^b$/, "a\nb\nc")
assert Regex.match?(~r/^b$/m, "a\nb\nc")
+14 -14
View File
@@ -178,38 +178,38 @@ defmodule ExUnit.CaptureIOTest do
test "with get password" do
capture_io(fn ->
assert :io.get_password() == :eof
assert :io.get_password(Process.group_leader()) == :eof
end)
capture_io("", fn ->
assert :io.get_password() == :eof
assert :io.get_password(Process.group_leader()) == :eof
end)
capture_io("abc", fn ->
assert :io.get_password() == "abc"
assert :io.get_password() == :eof
assert :io.get_password(Process.group_leader()) == "abc"
assert :io.get_password(Process.group_leader()) == :eof
end)
capture_io("abc\n", fn ->
assert :io.get_password() == "abc\n"
assert :io.get_password() == :eof
assert :io.get_password(Process.group_leader()) == "abc\n"
assert :io.get_password(Process.group_leader()) == :eof
end)
capture_io("\n", fn ->
assert :io.get_password() == "\n"
assert :io.get_password() == :eof
assert :io.get_password(Process.group_leader()) == "\n"
assert :io.get_password(Process.group_leader()) == :eof
end)
capture_io("a\nb", fn ->
assert :io.get_password() == "a\n"
assert :io.get_password() == "b"
assert :io.get_password() == :eof
assert :io.get_password(Process.group_leader()) == "a\n"
assert :io.get_password(Process.group_leader()) == "b"
assert :io.get_password(Process.group_leader()) == :eof
end)
capture_io("あい\nう", fn ->
assert :io.get_password() == "あい\n"
assert :io.get_password() == "う"
assert :io.get_password() == :eof
assert :io.get_password(Process.group_leader()) == "あい\n"
assert :io.get_password(Process.group_leader()) == "う"
assert :io.get_password(Process.group_leader()) == :eof
end)
end
+163
View File
@@ -40,6 +40,7 @@ defmodule IEx.Helpers do
* `pid/3` - creates a PID with the 3 integer arguments passed
* `port/1` - creates a port from a string
* `port/2` - creates a port with the 2 non-negative integers passed
* `process_info/1` - returns information about the given process
* `pwd/0` - prints the current working directory
* `r/1` - recompiles the given module's source file
* `recompile/0` - recompiles the current project
@@ -817,6 +818,168 @@ defmodule IEx.Helpers do
defp pad_key(key), do: String.pad_trailing(key, 21, " ")
@process_info_keys_and_labels [
{:initial_call, "Initial call"},
{:dictionary, "Dictionary"},
{:registered_name, "Registered name"},
{:current_function, "Current function"},
{:status, "Status"},
{:message_queue_len, "Message queue length"},
{:trap_exit, "Trap exit"},
{:priority, "Priority"},
{:group_leader, "Group leader"},
{:reductions, "Reductions"},
{:links, "Links"},
{:monitors, "Monitors"},
{:memory, "Memory"},
{:total_heap_size, "Total heap size"},
{:heap_size, "Heap size"},
{:stack_size, "Stack size"},
{:current_stacktrace, "Current stacktrace"}
]
@process_info_keys Enum.map(@process_info_keys_and_labels, fn {key, _} -> key end)
@process_info_label_mapping Map.new(@process_info_keys_and_labels)
@doc """
Prints information about the given process.
Includes a generic overview and details such as the linked and monitored processes,
the memory usage and the current stacktrace.
## Examples
iex> process_info(self())
...
iex> process_info({:via, Registry, {MyApp.Registry, :name}})
...
"""
@doc since: "1.19.0"
def process_info(pid) do
with pid when is_pid(pid) <- GenServer.whereis(pid),
info when is_list(info) <-
:erpc.call(node(pid), :erlang, :process_info, [pid, @process_info_keys]) do
info = Map.new(info)
IO.puts(IEx.color(:eval_result, ["\n# Process ", inspect(pid)]))
print_process_overview(info)
print_process_links(info[:links])
print_process_monitors(info[:monitors])
print_process_memory(info)
print_process_stacktrace(info[:current_stacktrace])
else
_ ->
IO.puts(
IEx.color(
:eval_error,
"Failed to get process info. Either the process was not found or is not alive."
)
)
end
dont_display_result()
end
defp print_process_overview(info) do
print_pane("Overview")
for key <- [
:initial_call,
:current_function,
:registered_name,
:status,
:message_queue_len,
:group_leader,
:priority,
:trap_exit,
:reductions
] do
print_entry(
@process_info_label_mapping[key],
inspect(info[key], printable_limit: 256, limit: 5)
)
end
end
defp print_process_links([]), do: :ok
defp print_process_links(ports_and_pids) do
print_pane("Links")
for link <- ports_and_pids do
print_entry(inspect(link), pid_or_port_details(link))
end
end
defp print_process_monitors([]), do: :ok
defp print_process_monitors(monitors) do
print_pane("Monitors")
for {_, pid_or_port} <- monitors do
print_entry(inspect(pid_or_port), pid_or_port_details(pid_or_port))
end
end
defp print_process_memory(info) do
print_pane("Memory")
for key <- [:memory, :total_heap_size, :heap_size, :stack_size] do
print_entry(@process_info_label_mapping[key], format_bytes(info[key]))
end
end
defp print_process_stacktrace([]), do: :ok
defp print_process_stacktrace(stacktrace) do
print_pane("Current stacktrace")
IO.puts(IEx.color(:eval_info, Exception.format_stacktrace(stacktrace)))
end
defp pid_or_port_details(pid) when is_pid(pid), do: to_process_details(pid)
defp pid_or_port_details(name) when is_atom(name), do: to_process_details(name)
defp pid_or_port_details(port) when is_port(port), do: to_port_details(port)
defp pid_or_port_details(reference) when is_reference(reference), do: reference
defp to_process_details(pid) when is_pid(pid) do
case Process.info(pid, [:initial_call, :dictionary, :registered_name]) do
[{:initial_call, initial_call}, {:dictionary, dictionary}, {:registered_name, name}] ->
initial_call = Keyword.get(dictionary, :"$initial_call", initial_call)
format_registered_name(name) ||
format_process_label(Keyword.get(dictionary, :"$process_label")) ||
format_initial_call(initial_call)
_ ->
"-"
end
end
defp to_process_details(name) when is_atom(name) do
Process.whereis(name)
|> to_process_details()
end
defp format_process_label(nil), do: nil
defp format_process_label(label) when is_binary(label), do: label
defp format_process_label(label), do: inspect(label)
defp format_registered_name([]), do: nil
defp format_registered_name(name), do: inspect(name)
defp format_initial_call({:supervisor, mod, arity}), do: Exception.format_mfa(mod, :init, arity)
defp format_initial_call({m, f, a}), do: Exception.format_mfa(m, f, a)
defp format_initial_call(nil), do: nil
defp to_port_details(port) when is_port(port) do
case Port.info(port, :name) do
{:name, name} -> name
_ -> "-"
end
end
@doc """
Clears out all messages sent to the shell's inbox and prints them out.
"""
+31
View File
@@ -1625,6 +1625,37 @@ defmodule IEx.HelpersTest do
end
end
describe "process_info/1" do
test "returns information about a process" do
result = capture_io(fn -> process_info(self()) end)
assert result =~ "Process #{inspect(self())}"
assert result =~ "## Overview"
assert result =~ "Initial call"
assert result =~ "## Memory"
assert result =~ "## Current stacktrace"
assert result =~ "IEx.Helpers.process_info/1"
refute result =~ "## Links"
refute result =~ "## Monitors"
end
test "includes process links and monitors" do
pid =
spawn_link(fn ->
Process.register(self(), :iex_process_info_link_test)
Process.sleep(:infinity)
end)
Process.monitor(pid)
result = capture_io(fn -> process_info(self()) end)
assert result =~ "## Links"
assert result =~ "## Monitors"
assert result =~ ":iex_process_info_link_test"
end
end
defp test_module_code do
"""
defmodule Sample do
+2 -2
View File
@@ -894,7 +894,7 @@ defmodule Mix do
{deps, config, system_env, consolidate_protocols?}
|> :erlang.term_to_binary()
|> :erlang.md5()
|> Base.encode16(case: :lower)
|> Base.url_encode64(case: :lower, padding: false)
force? = System.get_env("MIX_INSTALL_FORCE") in ["1", "true"] or Keyword.fetch!(opts, :force)
@@ -1068,7 +1068,7 @@ defmodule Mix do
System.get_env("MIX_INSTALL_DIR") ||
Path.join(Mix.Utils.mix_cache(), "installs")
version = "elixir-#{System.version()}-erts-#{:erlang.system_info(:version)}"
version = "ex-#{System.version()}-erl-#{:erlang.system_info(:version)}"
Path.join([install_root, version, cache_id])
end
+18 -23
View File
@@ -232,40 +232,35 @@ defmodule Mix.Local do
defp find_latest_eligible_version(entries, artifact_version) do
elixir_version = Version.parse!(System.version())
otp_release = System.otp_release()
entries
|> Enum.reverse()
|> find_version(artifact_version, elixir_version)
|> find_version(artifact_version, elixir_version, otp_release)
end
defp find_version(entries, _artifact_version = nil, elixir_version) do
Enum.find_value(entries, &find_by_elixir_version(&1, elixir_version))
defp find_version(entries, artifact_version, elixir_version, otp_release) do
entries =
if artifact_version do
Enum.filter(entries, &(hd(&1) == artifact_version))
else
entries
end
Enum.find_value(entries, &find_by_elixir_version(&1, elixir_version, otp_release))
end
defp find_version(entries, artifact_version, elixir_version) do
Enum.find_value(entries, &find_by_artifact_version(&1, artifact_version, elixir_version))
end
defp find_by_elixir_version([artifact_version, digest | versions], elixir_version) do
if version = Enum.find(versions, &(Version.compare(&1, elixir_version) != :gt)) do
{version, artifact_version, digest}
end
end
defp find_by_artifact_version(
[artifact_version, digest | versions],
artifact_version,
elixir_version
defp find_by_elixir_version(
[artifact_version, digest, hex_elixir_version, hex_otp_release | _],
elixir_version,
otp_release
) do
if version = Enum.find(versions, &(Version.compare(&1, elixir_version) != :gt)) do
{version, artifact_version, digest}
if Version.compare(hex_elixir_version, elixir_version) != :gt and
hex_otp_release <= otp_release do
{hex_elixir_version, artifact_version, digest, hex_otp_release}
end
end
defp find_by_artifact_version(_entry, _artifact_version, _elixir_version) do
nil
end
## Public keys
@doc """
+8 -2
View File
@@ -49,12 +49,18 @@ defmodule Mix.Rebar do
@doc """
Returns the path supposed to host the local copy of `rebar`.
The rebar3 installation is specific to the Elixir version,
The rebar3 installation is specific to the Elixir version and OTP release,
in order to force updates when new Elixir versions come out.
"""
def local_rebar_path(:rebar3) do
[major, minor | _] = String.split(System.version(), ".")
Path.join([Mix.Utils.mix_home(), "elixir", "#{major}-#{minor}", "rebar3"])
Path.join([
Mix.Utils.mix_home(),
"elixir",
"#{major}-#{minor}-otp-#{System.otp_release()}",
"rebar3"
])
end
@doc """
+1 -1
View File
@@ -137,7 +137,7 @@ defmodule Mix.SCM.Git do
# Migrate the Git repo
rev = lock_rev || get_origin_opts_rev(opts) || default_branch()
git!(["--git-dir=.git", "checkout", "--quiet", rev])
git!(["--git-dir=.git", "checkout", "--force", "--quiet", rev])
if opts[:submodules] do
git!(~w[-c core.hooksPath='' --git-dir=.git submodule update --init --recursive])
+7 -1
View File
@@ -65,7 +65,13 @@ defmodule Mix.Tasks.Clean do
# Loadpaths without checks because compilers may be defined in deps.
defp loadpaths! do
options = ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
options = [
"--no-elixir-version-check",
"--no-deps-check",
"--no-archives-check",
"--no-listeners"
]
Mix.Task.run("loadpaths", options)
Mix.Task.reenable("loadpaths")
Mix.Task.reenable("deps.loadpaths")
+8 -1
View File
@@ -67,6 +67,7 @@ defmodule Mix.Tasks.Compile do
* `--no-compile` - does not actually compile, only loads code and perform checks
* `--no-deps-check` - skips checking of dependencies
* `--no-elixir-version-check` - does not check Elixir version
* `--no-listeners` - does not start Mix listeners
* `--no-optional-deps` - does not compile or load optional deps. Useful for testing
if a library still successfully compiles without optional dependencies (which is the
default case with dependencies)
@@ -86,7 +87,13 @@ defmodule Mix.Tasks.Compile do
@impl true
def run(["--list"]) do
# Loadpaths without checks because compilers may be defined in deps.
args = ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
args = [
"--no-elixir-version-check",
"--no-deps-check",
"--no-archives-check",
"--no-listeners"
]
Mix.Task.run("loadpaths", args)
Mix.Task.reenable("loadpaths")
Mix.Task.reenable("deps.loadpaths")
+7 -1
View File
@@ -119,7 +119,13 @@ defmodule Mix.Tasks.Help do
# Loadpaths without checks because tasks may be defined in deps.
defp loadpaths! do
args = ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
args = [
"--no-elixir-version-check",
"--no-deps-check",
"--no-archives-check",
"--no-listeners"
]
Mix.Task.run("loadpaths", args)
Mix.Task.reenable("loadpaths")
Mix.Task.reenable("deps.loadpaths")
+5 -8
View File
@@ -1,8 +1,8 @@
defmodule Mix.Tasks.Local.Hex do
use Mix.Task
@hex_list_path "/installs/hex-1.x.csv"
@hex_archive_path "/installs/[ELIXIR_VERSION]/hex-[HEX_VERSION].ez"
@hex_list_path "/installs/hex.csv"
@hex_archive_path "/installs/[ELIXIR_VERSION]/hex-[HEX_VERSION]-otp-[OTP_RELEASE].ez"
@shortdoc "Installs Hex locally"
@@ -68,16 +68,13 @@ defmodule Mix.Tasks.Local.Hex do
defp run_install(version, argv) do
hex_url = Mix.Hex.url()
{elixir_version, hex_version, sha512} =
Mix.Local.find_matching_versions_from_signed_csv!(
"Hex",
version,
hex_url <> @hex_list_path
)
{elixir_version, hex_version, sha512, otp_release} =
Mix.Local.find_matching_versions_from_signed_csv!("Hex", version, hex_url <> @hex_list_path)
url =
(hex_url <> @hex_archive_path)
|> String.replace("[ELIXIR_VERSION]", elixir_version)
|> String.replace("[OTP_RELEASE]", otp_release)
|> String.replace("[HEX_VERSION]", hex_version)
# Unload the Hex module we loaded earlier to avoid conflicts when Hex is updated
+4 -3
View File
@@ -1,8 +1,8 @@
defmodule Mix.Tasks.Local.Rebar do
use Mix.Task
@rebar3_list_url "/installs/rebar3-1.x.csv"
@rebar3_escript_url "/installs/[ELIXIR_VERSION]/rebar3-[REBAR_VERSION]"
@rebar3_list_url "/installs/rebar.csv"
@rebar3_escript_url "/installs/[ELIXIR_VERSION]/rebar3-[REBAR_VERSION]-otp-[OTP_RELEASE]"
@shortdoc "Installs Rebar locally"
@@ -110,13 +110,14 @@ defmodule Mix.Tasks.Local.Rebar do
hex_url = Mix.Hex.url()
list_url = hex_url <> list_url
{elixir_version, rebar_version, sha512} =
{elixir_version, rebar_version, sha512, otp_release} =
Mix.Local.find_matching_versions_from_signed_csv!("Rebar", _version = nil, list_url)
url =
(hex_url <> escript_url)
|> String.replace("[ELIXIR_VERSION]", elixir_version)
|> String.replace("[REBAR_VERSION]", rebar_version)
|> String.replace("[OTP_RELEASE]", otp_release)
install_from_path(manager, url, Keyword.put(opts, :sha512, sha512))
end
+2
View File
@@ -53,6 +53,7 @@ defmodule Mix.Tasks.Run do
* `--no-deps-check` - does not check dependencies
* `--no-elixir-version-check` - does not check the Elixir version from mix.exs
* `--no-halt` - does not halt the system after running the command
* `--no-listeners` - does not start Mix listeners
* `--no-mix-exs` - allows the command to run even if there is no mix.exs
* `--no-start` - does not start applications after compilation
@@ -73,6 +74,7 @@ defmodule Mix.Tasks.Run do
halt: :boolean,
compile: :boolean,
deps_check: :boolean,
listeners: :boolean,
start: :boolean,
archives_check: :boolean,
elixir_version_check: :boolean,
+5
View File
@@ -692,6 +692,11 @@ defmodule Mix.Utils do
:httpc.set_options([ipfamily: fallback(inet)], :mix)
request |> httpc_request(http_options) |> httpc_response()
{:error, {:failed_connect, [{:to_address, _}, {inet, _, reason}]}}
when inet in [:inet, :inet6] and elem(reason, 0) == :tls_alert ->
http_options = put_in(http_options, [:ssl, :middlebox_comp_mode], false)
request |> httpc_request(http_options) |> httpc_response()
response ->
httpc_response(response)
end
+63 -52
View File
@@ -3,67 +3,70 @@ Code.require_file("../test_helper.exs", __DIR__)
defmodule Mix.LocalTest do
use MixTest.Case
# openssl rsa -in elixirest.pem -pubout > elixirest.pub
# openssl rsa -in elixirtest.pem -pubout > elixirtest.pub
@public_key """
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA37moKP1dGGLhsP3d8Fwv
W25SoYZUY2K+Iq7A0OBV36Rnb8yW3BWjfh5YtmPvUCfYUbNCW2HTMMgBntkQ4YmN
B9tHVZazl2uX9lGCfZZPFc/9umvKRojCPkMN81MfTxqnY0oaLHr6DB86RsWHB+ld
782Xf+nd9q3LFdUl8SGlKX7uzfVWd4EWYNcL7aLeLSupZWeNg8uVmY3zua0EgIlQ
XryalIOZb/R+pwprWZoftCl+20FGYi/mJpo/idFtXsR0sJKF4X0W3NORT9RIRbs9
WdjiFi+eIP7Nm8KSF4pbaXCqSmVf9cgvUuGTxc9/P5GcIPAlkcsSrE5peLyUCk5f
2QIDAQAB
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkIaIn8uhwSVp9d+aV+GL
tFjMt0TylxDu6x5jwjDvXCD3Of6VAriqcdXDJAqGPN1C4HNTxfihYpLZB+qXCNQM
oEa/I1h6OB5CXb8zIOl2Yriforr9LS+EkP+1xVmz7w7lNAagD0rJyJrTDbYDV+2Y
EnRq84FpZc/+7z6ojc3RMh9x/5t9jDE4Ft3NCujCmGsy2AcBlMpAQkcNveyplNmu
fZH2vId8h8t6rABwEZgSazHObkcHwE3bndynFO7zXvBu6ebNUDuU3DrDJ1Iepy9I
jEuZ6h13j7hL5l+j+9bGRQtasWgwvCkCGD+/2Lz4Ehox58voDCzAK09rsCwQ6gAT
CQIDAQAB
-----END PUBLIC KEY-----
"""
# openssl genrsa -aes256 -out elixirtest.pem -passout stdin 2048
# openssl genrsa -aes256 -out elixirtest.pem -passout pass:secret12 2048
@private_key """
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,48BA5153DA2F120ECE063B33C1204A49
5gp3daNWujH7o9S/dJQEt9TYTRP0pPZtU55PlZrzWt52optr7XHW/ENOm84g5J70
QCPELp12jfQsNiPwbVWXKy2zD3QlNiAelf65hqLWJTWli7XIXfdP46VXOu67OKf9
Ziw4HQ+AdBEwFt20wJst77iy17sNlyxp5DhNDonnSizzIowgUAJkoNI5aBUU6D8X
KTSIftZW35Z4SudkazdoHepEfItZTI8mB4rvfn71Q4oOBA1rAuUUmdPWoPBfUHDa
hvIp2T2Q8zZYqm0+SjDxZUYOOreE7fuf5NSLhHHt7+jyWQmtaVxnOWms72G+9xT0
NGmOEB0WEg1kBsUbYOXXwyCAZhNA6MaKCtgjQczRTK+geS1xNaFc9FDEk3ZjN4Z8
PxrKQoqo+2aQGVcatZWCom80Dci3bIv7iZNA/y1rjfBn+MeitMOGscP7/CBrJAbI
bh1mvCu0McSnqlN0a+EuCVfJQYFMzjibpRVzKAST0QeaxXd5QxHfcPFPBLOpiVWc
NjHaZsHORyoJbUKGA4rgOiSB63mv7SDRA2mvxWpwV/+6MuwBah6t6CGoEsAr1Hbn
1ySt5w27bw3QEf2KTiuxDubo8UrF0eYzP5A9MH8vRpSRZHg8T3SBVfPJ/pM16Lnn
5BaMUdxDFJeet5HUYoke9Zm3udh2BvwGiKhzc9Pbw/EcsCcvChMimRTasqTaRf+S
uIm0Un7o+7kTuvBo2y87j2urCEUzft5QqEynbkR7p3vZnwoLLj+supXh3V8ivW4s
Z6ql+ukRcWd/ode+lbSiYfAJCLc1tCqJ3kTnMnADJBlL0TX7YnwBwWuwwPuZgeAv
F6nnBE1SBQ1WK+bjSVzIqmNFqsZw34wgpnz2heX0q8msF5pzd6EIeA+uz86k8XYh
4eVZYGXxa4Exodh/MqEpRuN1ytWDXvHULh0gml7xwZC3R50UD8uBNt5RGjXUkjXc
V0atKuvgzVlsB4xbDhVP7EVYHBF02NfNOsvo7kh0Yl1IcT/42UaCGYuU1o9zotPv
9b3SHz/HOmBVj2uCdR5XZ4EolP5Iv9vqIDt9DsuDpOyO+AFOww0FnJNCQ1Hmfb0T
qBYPv994oSPYLCGR4a8i/xfmmV8KbAIVEgK3AMbz8RxKr3WBWXWnzQdr4+y4EG24
hSnR52XQ42edv/fkqf9ez+fKNQ9i7PtlPE96Q21NeLMNKHh43X8hJFDh+oPz3Aio
YSNMCZnoyRdrjBRCsVBpnyoLmuhWwG9RlcrEj3G0BxYPh/weaBOAKAHjSr28yuUj
yIa8uddszC6XHSiVUgu7SGO8gQmq++eNdckjX/pEug5MjcWLUqaUg6+YLFWY6NLf
uDPOYuivq7ErtKTvP2xl3TBEDKhdfqxA2+RFxbBDmKjffZnRkcknQsxhlzAdbg22
Jwa2B1nrfjJpX5F+1Av2jHQGbIKMqZzv8fo1binMKpptFzokbWEOjcPCb3tPuomG
ZRkW3qO2pdyYX2N7VXYG9tGi2HrN/oFrWnHPoYF23v85V8WxNkODOCpTz85e6R5v
PVu+FCNFj5weEOTRhtEQyJo7mU5qIRwYeZvVxiC6W+XeFs95wdBE/Lvpg8yZ8D9d
-----END RSA PRIVATE KEY-----
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQjNQQlREZJuQveXPa
0ACpEwICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEFoBWTpEyO8y6xdG
fuud2AIEggTQDQRXrxBpj1yIbnLcP7i43Gat2tEM+XXkvqbzwL+CdjfEzaqiBGwc
FEyWnJj5hENWtNq/AAwzcEALa3QdLJ2W+tX/hc5r1vILpy2oW0vPKfv4yadNyPWq
UzolKKU3MbREP4KXsuAVYepolRWAcWSZKozYBm9HUPwsSyM6E9K0aWmNr/YBbZPE
UEA949Pq6buQvwT2Gj/6FQlutEvJMuY8woM2I/RKSVHeSoB8FWJGSNd7ZY2DN7DG
o6kmhK0OVjxx0SYQwUm++4gh3yFRX6vnTf8ZADOwCOJE/hjMfqivNelJHGojYAD2
0c3zdo0HGvVghg2bzUxhc0HJqUFiUDNW1W3AVBrUJiQgHQb/ENYaMrd6klrslHDV
cnPkz+dZAq/WC3KFqufkSmHxU97dDs9G6hKdm80JVEdPG4aMJvHzVL6SvS+g/e7r
IJGZCFGch9SbikmESKegIfyLJYQrfjuLdXWPHx9J+LAmtRTA1xPwLH/aqCr9ahBB
JCl4RaezF0BH2GYLAV+Ailpy2jqsC3v9RW8vpQmU227H8ERFu9ZcWhZITbRhbjRB
oh3ERnOmvU3VWIXQ0j1cm9Qjl52y8HZJxef2dYsW8nmTSRj32gi9UnEsyOFL9FB7
KcFDq6o+go4nIfsmaAEOF1Hh01JZkbuX1SgmHvGRbNksTuljP/XZn7Wzz7fTcVZR
NsTgMbBl1+ctDHUBT7YzsHeEx8tZvLPDP8lO23aEnmRb6bHD4hVorUEM/kOpSm6g
DNP0buYGEAu4rZrp+5s46Y/RVAIhU4+663jqzmbmwOwEcuOJJCJn8eUGVweAgWX3
1PQTfpkk7RLxxuLeMHxEIynxyZNpSgfX0VGKe5UtplwcTQL8VgeZHayDxnhFwtSm
lBfPIM/i4kj7RonuehyPjuBA6n1CyqRVqc49c/3kVMgovqvbUQxQ7TtSfzfkhVyV
yRnRxcek+6Gctoahas5DPQl7y8jnwgg5qvUjYo6GIwBoxLUzIw+0MBn7gdMYHNmh
Xd9XZpRilAMJF5ynr2QyaE95XaQ3yK62FPgmIDdzfnN7es169O4SiXsgUyaSWsx9
VxOHP4r0j6epWLH4W11MQEj4Bb8wG//pCD5Kz2xvwhWaKjkqzuqGDaXSOTKV0FQM
NG5GbNNELNCy/ocvlsaC+acWcOfsx4f6yovCGVTpTr+wjbDqd1kEBoRTuyTPY+6P
0OU1Vf2GpMA2NDw0ZUOAllGVLrgD+326bu7ipePgiT8SiPNNsagMJNCSk5d3C4+K
bc0hakF/MIjPs3a+5/2mwtxk3QYYCIHITN8SIj+kFd7WMXALyClgWt3tcKwLTgMa
BSatAvwsoi2kiBu2A49msvka7YD8fENhtUl8vU12ZZdTih1chyPDifgfRgOfkZFW
a1XiLKjnWqeBqvJ3hBKuXweN6P7nvZidBYSESl3yrM00pA97Eta2in7ok80MRYH3
bsqzC636h43Mu8PbTKGpK66j1ts6uY9YONAGfGCcfj/wyBWuqNViv7vK38th8wj8
AG4rKyIlKxbFwo4Oh3jZTbfVF1Azl8pr0rK2P3W/EhfvWIrZVfB4Upy0wL6MjysE
RZ/57N2wFq4KBb9x9hCMFxignWmgyzPK+1J0iPRkSmpn9P/SzywDBIg=
-----END ENCRYPTED PRIVATE KEY-----
"""
@csv """
1.2.5,ABC,0.9.0
1.2.3,DEF,1.0.0
1.2.4,GHI,1.0.0
1.2.5,ABC,0.9.0,25
1.2.5,ABC,0.9.0,26
1.2.3,DEF,1.0.0,25
1.2.3,DEF,1.0.0,26
1.2.4,GHI,1.0.0,25
1.2.4,GHI,1.0.0,26
"""
# openssl dgst -sha512 -sign elixirtest.pem hex-1.x.csv | openssl base64 > elixirtest.csv.signed
@csv_signed """
VRydmXOdEXQcKJu/SK/nKnE00T+s/T4mpXrYROMSXhD/s8ClvdimnGg61ie3YBS6
LXOjlEhbtMHRM2rTOUvv4z7FcyzwvSxSjunlVi2g3c1pVOZ78MonnYhGb44tZw/q
SOVmV+jJhc9EZFMIAAM3plMoyssyw2pMh7ZB/DxCQTIem3Qf0Ujzc2bYkLVlw7R+
1Rn6dcYEgCzyldVkAUMaYBwieyweWALA+YVDCMudJJK2J7p1OnuoPSVV+N3OkB/Z
T6Jj5ljD+54XnuxAMcgCoF9lpOwXscnw/Ma+8JqIoWo0jNFE3ji+8dGCUzQUdSe8
llLXgJJE2tGpDhEXBA3idg==
CVkhTiuCAfooYPhjyynDq40QhmDwLAEJvpwYytPCf6mpLXVrLXo/d/A2L8iBRJVx
uk4PNVksLRZ1ChBzGFvEqaFjrH+ndQAYLbwqcaMIn743YNUjGNVfTZkU47nBybtJ
BwDSBaAsow0Iitsl+UkDN/QvVoOLiX/x2cpnwCMrCgbTMroTzhH07vfLo3uCf8iY
cncImd07ffCewt77AsVPpKgJNOLzn+EBnvh4LbGWQya8EkgyQKuMBuNU86MYtFiW
NVpR7vbvqgWpEyr1XeknxKkhzYpna3+irXdMxGZw65WvFNWGJKnpuBTNHnHL+wX8
oNQLUfakH8/VMV/8v6Irbg==
"""
# We don't actually use it but it exists for documentation purposes.
@@ -83,8 +86,10 @@ defmodule Mix.LocalTest do
File.write!("csv", @csv)
File.write!("csv.signed", @csv_signed)
assert {"1.0.0", "1.2.4", "GHI"} =
assert {"1.0.0", "1.2.4", "GHI", otp_release} =
Mix.Local.find_matching_versions_from_signed_csv!("name", nil, "csv")
assert otp_release <= System.otp_release()
end)
end
@@ -94,12 +99,16 @@ defmodule Mix.LocalTest do
File.write!("csv", @csv)
File.write!("csv.signed", @csv_signed)
assert {"0.9.0", "1.2.5", "ABC"} =
assert {"0.9.0", "1.2.5", "ABC", otp_release} =
Mix.Local.find_matching_versions_from_signed_csv!("name", "1.2.5", "csv")
assert {"1.0.0", "1.2.3", "DEF"} =
assert otp_release <= System.otp_release()
assert {"1.0.0", "1.2.3", "DEF", otp_release} =
Mix.Local.find_matching_versions_from_signed_csv!("name", "1.2.3", "csv")
assert otp_release <= System.otp_release()
assert_raise Mix.Error, "Could not find a version of name matching: 1.3.0", fn ->
Mix.Local.find_matching_versions_from_signed_csv!("name", "1.3.0", "csv")
end
@@ -109,7 +118,9 @@ defmodule Mix.LocalTest do
@tag :tmp_dir
test "raise on bad signature", %{tmp_dir: tmp_dir} do
File.cd!(tmp_dir, fn ->
csv_signed = String.replace(@csv_signed, "VRy", "BAD")
<<_, _, _>> <> rest = @csv_signed
csv_signed = "BAD" <> rest
File.write!("csv", @csv)
File.write!("csv.signed", csv_signed)
+10
View File
@@ -396,9 +396,14 @@ defmodule Mix.Tasks.DepsGitTest do
Mix.State.clear_cache()
purge([DepsOnGitRepo.MixProject])
# Write to the checkout location to ensure it is replaced
File.mkdir_p!("deps/deps_on_git_repo/lib")
File.write!("deps/deps_on_git_repo/lib/deps_on_git_repo.ex", "# WILL BE OVERRIDDEN")
Mix.Tasks.Deps.Update.run(["deps_on_git_repo"])
assert File.exists?("deps/git_repo/lib/git_repo.ex")
assert File.read!("mix.lock") =~ last
assert File.read!("deps/deps_on_git_repo/lib/deps_on_git_repo.ex") =~ "GitRepo.hello()"
refute File.exists?("_build/dev/lib/git_repo/.mix/compile.fetch")
end)
after
@@ -423,10 +428,15 @@ defmodule Mix.Tasks.DepsGitTest do
Mix.State.clear_cache()
purge([DepsOnGitRepo.MixProject])
# Write to the checkout location to ensure it is replaced
File.mkdir_p!("deps/deps_on_git_repo/lib")
File.write!("deps/deps_on_git_repo/lib/deps_on_git_repo.ex", "# WILL BE OVERRIDDEN")
Mix.Dep.Lock.write(%{deps_on_git_repo: {:git, fixture_path("deps_on_git_repo"), last, []}})
Mix.Tasks.Deps.Get.run([])
assert File.exists?("deps/git_repo/lib/git_repo.ex")
assert File.read!("mix.lock") =~ last
assert File.read!("deps/deps_on_git_repo/lib/deps_on_git_repo.ex") =~ "GitRepo.hello()"
refute File.exists?("_build/dev/lib/git_repo/.mix/compile.fetch")
end)
after
+2 -1
View File
@@ -254,7 +254,8 @@ end
rebar3_source = System.get_env("REBAR3") || Path.expand("fixtures/rebar3", __DIR__)
[major, minor | _] = String.split(System.version(), ".")
rebar3_target = Path.join([mix, "elixir", "#{major}-#{minor}", "rebar3"])
version_dir = "#{major}-#{minor}-otp-#{System.otp_release()}"
rebar3_target = Path.join([mix, "elixir", version_dir, "rebar3"])
File.mkdir_p!(Path.dirname(rebar3_target))
File.cp!(rebar3_source, rebar3_target)