Compare commits

...
19 Commits
Author SHA1 Message Date
José Valim e35ffc5a90 Release v1.18.3 2025-03-06 10:55:08 +01:00
José Valim 178643f9eb Do not discard nil on protocol concat, closes #14311 (#14314) 2025-03-06 10:41:12 +01:00
José Valim 75677b9dab Encode any JSON key to string, closes #14305 (#14309) 2025-03-05 09:48:05 +01:00
Łukasz Samson 25ab648502 Fix autocomplete crash when expanding struct with __MODULE__ (#14308) 2025-03-05 09:48:04 +01:00
José Valim 8493f1934a Do not raise when Stream.cycle is explicitly halted, closes #14307 2025-03-03 12:55:37 +01:00
José Valim c63aeb9f77 Clarify order on uniq/uniq_by, closes #14304 2025-03-03 12:55:36 +01:00
José Valim d89fabe5e4 Fix date in CHANGELOG, closes #14303 2025-02-27 08:00:03 +01:00
Graham Preston d51153b56e Fix typo (#14302) 2025-02-26 17:57:17 +01:00
Jean Klingler 69990a5d1d Fix regression when diffing nested improper lists (#14292)
Close https://github.com/elixir-lang/elixir/issues/14291
2025-02-21 23:09:32 +09:00
José Valim 930ec69739 Support --no-listeners in loadpaths 2025-02-21 11:01:51 +01:00
José Valim d7478095e0 Support Erlang/OTP 28 2025-02-20 18:12:08 +01:00
José Valim b823f9efdf Allow <<_::3*8>> in typespecs 2025-02-20 06:12:13 +01:00
José Valim 6ecb430614 Do not purge on recompile if IEx is not running, closes #14260 2025-02-12 10:03:17 +01:00
Ben Murden b615c8435a Improved help on deprecation warning (#14239) 2025-01-29 08:59:50 +01:00
cevado 120467f83c Add section to Process.exit/2 with differences to Kernel.exit/1 (#14238) 2025-01-29 08:59:48 +01:00
José Valim 4b09a836e0 Validate signature for forward compatibility (#14235) 2025-01-28 10:32:10 +01:00
tea☆ 55c05d943e Fix --no-color not setting :ansi_enabled to false (#14229) 2025-01-26 14:10:20 +01:00
Jean Klingler 8e61baacab Fix String.split/3 example in guide and add note in docs (#14223) 2025-01-25 07:12:09 +09:00
José Valim 7426acb1b9 Use exdoc:loaded to reload mermaid graphs 2025-01-24 19:58:58 +01:00
40 changed files with 282 additions and 218 deletions
+41 -13
View File
@@ -162,17 +162,15 @@ Both encoder and decoder fully conform to [RFC 8259](https://tools.ietf.org/html
Encoding can be done via `JSON.encode!/1` and `JSON.encode_to_iodata!/1` functions. The default encoding rules are applied as follows:
| **Elixir** | **JSON** |
|------------------------|----------|
| `integer() \| float()` | Number |
| `true \| false ` | Boolean |
| `nil` | Null |
| `binary()` | String |
| `atom()` | String |
| `list()` | Array |
| `%{binary() => _}` | Object |
| `%{atom() => _}` | Object |
| `%{integer() => _}` | Object |
| **Elixir** | **JSON** |
|-----------------------------|----------|
| `integer() \| float()` | Number |
| `true \| false ` | Boolean |
| `nil` | Null |
| `binary()` | String |
| `atom()` | String |
| `list()` | Array |
| `%{String.Chars.t() => _}` | Object |
You may also implement the `JSON.Encoder` protocol for custom data structures. Elixir already implements the protocol for all Calendar types.
@@ -225,7 +223,37 @@ You may also prefer to write using guards:
def foo(x, y, z) when x == y and y == z
## v1.18.2 (2024-01-22)
## v1.18.3 (2025-03-06)
### 1. Enhancements
#### Elixir
* [JSON] Encode any JSON key to string
* [Kernel] Allow `<<_::3*8>>` in typespecs
#### Mix
* [mix loadpaths] Support `--no-listeners` option
### 2. Bug fixes
#### Elixir
* [CLI] Fix `--no-color` not setting `:ansi_enabled` to false
* [Protocol] Return correct implementation for an invalid struct pointing to `nil`
* [Stream] Do not raise when `Stream.cycle/1` is explicitly halted
#### ExUnit
* [ExUnit.Diff] Fix regression when diffing nested improper lists
#### IEx
* [IEx.Autocomplete] Fix autocomplete crash when expanding struct with `__MODULE__`
* [IEx.Helpers] Do not purge on `recompile` if IEx is not running
## v1.18.2 (2025-01-22)
### 1. Enhancements
@@ -252,7 +280,7 @@ You may also prefer to write using guards:
#### IEx
* [IEx.Autocomplete] Fix crashing whhen autocompleting structs with runtime values
* [IEx.Autocomplete] Fix crashing when autocompleting structs with runtime values
#### Mix
+1 -1
View File
@@ -2,7 +2,7 @@
## Shipping a new version
1. Update version in /VERSION, bin/elixir, bin/elixir.bat, and bin/elixir.ps1
1. Update version in /VERSION, bin/elixir, and bin/elixir.bat
2. Ensure /CHANGELOG.md is updated, versioned and add the current date
+1 -1
View File
@@ -1 +1 @@
1.18.2
1.18.3
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/sh
set -e
ELIXIR_VERSION=1.18.2
ELIXIR_VERSION=1.18.3
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.2
set ELIXIR_VERSION=1.18.3
if ""%1""=="""" if ""%2""=="""" goto documentation
if /I ""%1""==""--help"" if ""%2""=="""" goto documentation
+5 -2
View File
@@ -283,7 +283,6 @@ defmodule Code.Typespec do
end
defp typespec_to_quoted({:type, anno, :binary, [arg1, arg2]}) do
[arg1, arg2] = for arg <- [arg1, arg2], do: typespec_to_quoted(arg)
line = meta(anno)[:line]
case {typespec_to_quoted(arg1), typespec_to_quoted(arg2)} do
@@ -329,10 +328,14 @@ defmodule Code.Typespec do
{erl_to_ex_var(var), meta(anno), nil}
end
defp typespec_to_quoted({:op, anno, op, arg}) do
defp typespec_to_quoted({:op, anno, op, arg}) when op in [:+, :-] do
{op, meta(anno), [typespec_to_quoted(arg)]}
end
defp typespec_to_quoted({:op, anno, :*, arg1, arg2}) do
{:*, meta(anno), [typespec_to_quoted(arg1), typespec_to_quoted(arg2)]}
end
defp typespec_to_quoted({:remote_type, anno, [mod, name, args]}) do
remote_type(anno, mod, name, args)
end
+5 -1
View File
@@ -3837,6 +3837,9 @@ defmodule Enum do
@doc """
Enumerates the `enumerable`, removing all duplicate elements.
The first occurrence of each element is kept and all following
duplicates are removed. The overall order is preserved.
## Examples
iex> Enum.uniq([1, 2, 3, 3, 2, 1])
@@ -3862,7 +3865,8 @@ defmodule Enum do
considered duplicates if the return value of `fun` is equal for
both of them.
The first occurrence of each element is kept.
The first occurrence of each element is kept and all following
duplicates are removed. The overall order is preserved.
## Example
+33 -13
View File
@@ -146,8 +146,30 @@ end
defimpl JSON.Encoder, for: Map do
def encode(value, encoder) do
:elixir_json.encode_map(value, encoder)
case :maps.next(:maps.iterator(value)) do
:none ->
"{}"
{key, value, iterator} ->
[?{, key(key, encoder), ?:, encoder.(value, encoder) | next(iterator, encoder)]
end
end
defp next(iterator, encoder) do
case :maps.next(iterator) do
:none ->
"}"
{key, value, iterator} ->
[?,, key(key, encoder), ?:, encoder.(value, encoder) | next(iterator, encoder)]
end
end
# Erlang supports only numbers, binaries, and atoms as keys,
# we support anything that implements the String.Chars protocol.
defp key(key, encoder) when is_atom(key), do: encoder.(Atom.to_string(key), encoder)
defp key(key, encoder) when is_binary(key), do: encoder.(key, encoder)
defp key(key, encoder), do: encoder.(String.Chars.to_string(key), encoder)
end
defimpl JSON.Encoder, for: [Date, Time, NaiveDateTime, DateTime, Duration] do
@@ -175,17 +197,15 @@ defmodule JSON do
Elixir built-in data structures are encoded to JSON as follows:
| **Elixir** | **JSON** |
|------------------------|----------|
| `integer() \| float()` | Number |
| `true \| false ` | Boolean |
| `nil` | Null |
| `binary()` | String |
| `atom()` | String |
| `list()` | Array |
| `%{binary() => _}` | Object |
| `%{atom() => _}` | Object |
| `%{integer() => _}` | Object |
| **Elixir** | **JSON** |
|----------------------------|----------|
| `integer() \| float()` | Number |
| `true \| false ` | Boolean |
| `nil` | Null |
| `binary()` | String |
| `atom()` | String |
| `list()` | Array |
| `%{String.Chars.t() => _}` | Object |
You may also implement the `JSON.Encoder` protocol for custom data structures.
@@ -403,7 +423,7 @@ defmodule JSON do
do: :elixir_json.encode_list(value, encoder)
def protocol_encode(%{} = value, encoder) when not is_map_key(value, :__struct__),
do: :elixir_json.encode_map(value, encoder)
do: JSON.Encoder.Map.encode(value, encoder)
def protocol_encode(value, encoder),
do: JSON.Encoder.encode(value, encoder)
+1 -1
View File
@@ -301,7 +301,7 @@ defmodule Kernel.CLI do
end
defp parse_argv([~c"--no-color" | t], config) do
Application.put_env(:elixir, :ansi_enabled, true)
Application.put_env(:elixir, :ansi_enabled, false)
parse_argv(t, config)
end
+18
View File
@@ -493,6 +493,24 @@ defmodule Kernel.Typespec do
state}
end
defp typespec(
{:<<>>, meta, [{:"::", _, [{:_, _, ctx1}, {:*, prod_meta, [size, unit]}]}]},
_,
_,
state
)
when is_atom(ctx1) and is_integer(size) and size >= 0 and unit in 1..256 do
location = location(meta)
prod_location = location(prod_meta)
{{:type, location, :binary,
[
{:op, prod_location, :*, {:integer, prod_location, size},
{:integer, prod_location, unit}},
{:integer, location, 0}
]}, state}
end
defp typespec({:<<>>, meta, [{:"::", size_meta, [{:_, _, ctx}, size]}]}, _, _, state)
when is_atom(ctx) and is_integer(size) and size >= 0 do
location = location(meta)
+5 -1
View File
@@ -946,7 +946,8 @@ defmodule Module do
@doc """
Concatenates two aliases and returns a new alias.
It handles binaries and atoms.
It handles binaries and atoms. If one of the aliases
is nil, it is discarded.
## Examples
@@ -956,6 +957,9 @@ defmodule Module do
iex> Module.concat(Foo, "Bar")
Foo.Bar
iex> Module.concat(Foo, nil)
Foo
"""
@spec concat(binary | atom, binary | atom) :: atom
def concat(left, right)
+8 -2
View File
@@ -439,10 +439,16 @@ defmodule Module.ParallelChecker do
defp cache_chunk(table, module, exports) do
Enum.each(exports, fn {{fun, arity}, info} ->
# TODO: Match on signature directly in Elixir v1.22+
sig =
case info do
%{sig: {:strong, _, _} = sig} -> sig
%{sig: {:infer, _} = sig} -> sig
_ -> :none
end
:ets.insert(
table,
{{module, {fun, arity}}, Map.get(info, :deprecated), Map.get(info, :sig, :none)}
{{module, {fun, arity}}, Map.get(info, :deprecated), sig}
)
end)
+10
View File
@@ -219,6 +219,16 @@ defmodule Process do
Inlined by the compiler.
> #### Differences to `Kernel.exit/1` {: .info }
>
> The functions `Kernel.exit/1` and `Process.exit/2` are
> named similarly but provide very different functionalities. The
> `Kernel:exit/1` function should be used when the intent is to stop the current
> process while `Process.exit/2` should be used when the intent is to send an
> exit signal to another process. Note also that `Kernel.exit/1` can be caught
> with `try/1` while `Process.exit/2` can only be handled by trapping exits and
> when the signal is different than `:kill`.
## Examples
Process.exit(pid, :kill)
+25 -8
View File
@@ -347,7 +347,7 @@ defmodule Protocol do
end
defp assert_impl!(protocol, base, extra) do
impl = Module.concat(protocol, base)
impl = Protocol.__concat__(protocol, base)
try do
Code.ensure_compiled!(impl)
@@ -678,7 +678,7 @@ defmodule Protocol do
end
defp load_impl(protocol, for) do
Module.concat(protocol, for)
Protocol.__concat__(protocol, for)
end
# Finally compile the module and emit its bytecode.
@@ -831,7 +831,7 @@ defmodule Protocol do
# Define the implementation for built-ins
:lists.foreach(
fn {guard, mod} ->
target = Module.concat(__MODULE__, mod)
target = Protocol.__concat__(__MODULE__, mod)
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
case Code.ensure_compiled(unquote(target)) do
@@ -875,7 +875,7 @@ defmodule Protocol do
# Internal handler for Structs
Kernel.defp struct_impl_for(struct) do
case Code.ensure_compiled(Module.concat(__MODULE__, struct)) do
case Code.ensure_compiled(Protocol.__concat__(__MODULE__, struct)) do
{:module, module} -> module
{:error, _} -> unquote(any_impl_for)
end
@@ -948,7 +948,7 @@ defmodule Protocol do
quote do
protocol = unquote(protocol)
for = unquote(for)
name = Module.concat(protocol, for)
name = Protocol.__concat__(protocol, for)
Protocol.assert_protocol!(protocol)
Protocol.__ensure_defimpl__(protocol, for, __ENV__)
@@ -994,7 +994,7 @@ defmodule Protocol do
else
# TODO: Deprecate this on Elixir v1.22+
assert_impl!(protocol, Any, extra)
{Module.concat(protocol, Any), [for, Macro.struct!(for, env), opts]}
{Protocol.__concat__(protocol, Any), [for, Macro.struct!(for, env), opts]}
end
# Clean up variables from eval context
@@ -1006,7 +1006,7 @@ defmodule Protocol do
else
__ensure_defimpl__(protocol, for, env)
assert_impl!(protocol, Any, extra)
impl = Module.concat(protocol, Any)
impl = Protocol.__concat__(protocol, Any)
funs =
for {fun, arity} <- protocol.__protocol__(:functions) do
@@ -1031,7 +1031,11 @@ defmodule Protocol do
def __impl__(:for), do: unquote(for)
end
Module.create(Module.concat(protocol, for), [quoted | funs], Macro.Env.location(env))
Module.create(
Protocol.__concat__(protocol, for),
[quoted | funs],
Macro.Env.location(env)
)
end
end)
end
@@ -1070,4 +1074,17 @@ defmodule Protocol do
is_reference: Reference
]
end
@doc false
def __concat__(left, right) do
String.to_atom(
ensure_prefix(Atom.to_string(left)) <> "." <> remove_prefix(Atom.to_string(right))
)
end
defp ensure_prefix("Elixir." <> _ = left), do: left
defp ensure_prefix(left), do: "Elixir." <> left
defp remove_prefix("Elixir." <> right), do: right
defp remove_prefix(right), do: right
end
+6 -9
View File
@@ -209,17 +209,14 @@ defmodule Regex do
## Examples
iex> Regex.compile("foo")
{:ok, ~r/foo/}
Regex.compile("foo")
#=> {:ok, ~r/foo/}
iex> Regex.compile("*foo")
{:error, {~c"nothing to repeat", 0}}
Regex.compile("foo", "i")
#=>{:ok, ~r/foo/i}
iex> Regex.compile("foo", "i")
{:ok, ~r/foo/i}
iex> Regex.compile("foo", [:caseless])
{:ok, Regex.compile!("foo", [:caseless])}
Regex.compile("*foo")
#=> {:error, {~c"nothing to repeat", 0}}
"""
@spec compile(binary, binary | [term]) :: {:ok, t} | {:error, term}
+1 -1
View File
@@ -1444,7 +1444,7 @@ defmodule Stream do
defp check_cycle_first_element(reduce) do
fn acc ->
case reduce.(acc) do
{state, []} when state in [:done, :halted] ->
{state, []} when state in [:done, :halted] and elem(acc, 0) != :halt ->
raise ArgumentError, "cannot cycle over an empty enumerable"
other ->
+9
View File
@@ -488,6 +488,15 @@ defmodule String do
iex> String.split(String.normalize("é", :nfc), "e")
["é"]
When using both the `:trim` and the `:parts` option, the empty values
are removed as the parts are computed (if any). No trimming happens
after all parts are computed:
iex> String.split(" a b c ", " ", trim: true, parts: 2)
["a", " b c "]
iex> String.split(" a b c ", " ", trim: true, parts: 3)
["a", "b", " c "]
"""
@spec split(t, pattern | Regex.t(), keyword) :: [t]
def split(string, pattern, options \\ [])
@@ -11,36 +11,40 @@ Keyword lists are a data-structure used to pass options to functions. Let's see
Imagine you want to split a string of numbers. Initially, we can invoke `String.split/2` passing two strings as arguments:
```elixir
iex> String.split("1 2 3", " ")
["1", "2", "3"]
iex> String.split("1 2 3 4", " ")
["1", "2", "3", "4"]
```
However, what happens if there is an additional space between the numbers:
What if you only want to split at most 2 times? The `String.split/3` function allows the `parts` option to be set to the maximum number of entries in the result:
```elixir
iex> String.split("1 2 3", " ")
["1", "", "2", "", "3"]
iex> String.split("1 2 3 4", " ", [parts: 3])
["1", "2", "3 4"]
```
As you can see, there are now empty strings in our results. Luckily, the `String.split/3` function allows the `trim` option to be set to true:
As you can see, we got 3 parts, the last one containing the remaining of the input without splitting it.
Now imagine that some of the inputs you must split on contains additional spaces between the numbers:
```elixir
iex> String.split("1 2 3", " ", [trim: true])
["1", "2", "3"]
iex> String.split("1 2 3 4", " ", [parts: 3])
["1", "", "2 3 4"]
```
We can also use options to limit the splitting algorithm to a maximum number of parts, as shown next:
As you can see, the additional spaces lead to empty entries in the output. Luckily, we can also set the `trim` option to `true` to remove them:
```elixir
iex> String.split("1 2 3", " ", [trim: true, parts: 2])
["1", "2 3"]
iex> String.split("1 2 3 4", " ", [parts: 3, trim: true])
["1", "2", " 3 4"]
```
`[trim: true]` and `[trim: true, parts: 2]` are keyword lists. When a keyword list is the last argument of a function, we can skip the brackets and write:
Once again we got 3 parts, with the last one containing the leftovers.
`[parts: 3]` and `[parts: 3, trim: true]` are keyword lists. When a keyword list is the last argument of a function, we can skip the brackets and write:
```elixir
iex> String.split("1 2 3", " ", trim: true, parts: 2)
["1", "2 3"]
iex> String.split("1 2 3 4", " ", parts: 3, trim: true)
["1", "2", " 3 4"]
```
As shown in the example above, keyword lists are mostly used as optional arguments to functions.
@@ -48,7 +52,7 @@ As shown in the example above, keyword lists are mostly used as optional argumen
As the name implies, keyword lists are simply lists. In particular, they are lists consisting of 2-item tuples where the first element (the key) is an atom and the second element can be any value. Both representations are the same:
```elixir
iex> [{:trim, true}, {:parts, 2}] == [trim: true, parts: 2]
iex> [{:parts, 3}, {:trim, true}] == [parts: 3, trim: true]
true
```
+13 -7
View File
@@ -191,12 +191,19 @@ canonical = System.fetch_env!("CANONICAL")
before_closing_body_tag: fn
:html ->
"""
<script defer src="https://cdn.jsdelivr.net/npm/mermaid@10.2.3/dist/mermaid.min.js"></script>
<script>
function mermaidLoaded() {
mermaid.initialize({
startOnLoad: false,
theme: document.body.className.includes("dark") ? "dark" : "default"
});
let initialized = false;
window.addEventListener("exdoc:loaded", () => {
if (!initialized) {
mermaid.initialize({
startOnLoad: false,
theme: document.body.className.includes("dark") ? "dark" : "default"
});
initialized = true;
}
let id = 0;
for (const codeEl of document.querySelectorAll("pre code.mermaid")) {
const preEl = codeEl.parentElement;
@@ -210,9 +217,8 @@ canonical = System.fetch_env!("CANONICAL")
preEl.remove();
});
}
}
});
</script>
<script async src="https://cdn.jsdelivr.net/npm/mermaid@10.2.3/dist/mermaid.min.js" onload="mermaidLoaded();"></script>
"""
_ ->
+6 -1
View File
@@ -165,7 +165,12 @@ dynamic_form(#{module := Module, relative_file := RelativeFile,
{Def, Defmacro, Macros, Exports, Functions} =
split_definition(Definitions, Unreachable, Line, [], [], [], [], {[], []}),
FilteredOpts = lists:filter(fun({no_warn_undefined, _}) -> false; (_) -> true end, Opts),
FilteredOpts = lists:filter(fun(
{no_warn_undefined, _}) -> false;
(debug_info) -> false;
(_) -> true
end, Opts),
Location = {elixir_utils:characters_to_list(RelativeFile), Line},
Prefix = [{attribute, Line, file, Location},
+12
View File
@@ -1517,6 +1517,18 @@ defmodule EnumTest do
assert Enum.zip([], []) == []
end
test "zip/2 with infinite streams" do
assert Enum.zip([], Stream.cycle([1, 2])) == []
assert Enum.zip([], Stream.cycle(1..2)) == []
assert Enum.zip(.., Stream.cycle([1, 2])) == []
assert Enum.zip(.., Stream.cycle(1..2)) == []
assert Enum.zip(Stream.cycle([1, 2]), ..) == []
assert Enum.zip(Stream.cycle(1..2), ..) == []
assert Enum.zip(Stream.cycle([1, 2]), ..) == []
assert Enum.zip(Stream.cycle(1..2), ..) == []
end
test "zip/1" do
assert Enum.zip([[:a, :b], [1, 2], ["foo", "bar"]]) == [{:a, 1, "foo"}, {:b, 2, "bar"}]
@@ -1,29 +0,0 @@
defmodule Dialyzer.ProtocolOpaque do
def circus() do
duck = Dialyzer.ProtocolOpaque.Duck.new()
Dialyzer.ProtocolOpaque.Entity.speak(duck)
end
end
defprotocol Dialyzer.ProtocolOpaque.Entity do
@fallback_to_any true
def speak(entity)
end
defmodule Dialyzer.ProtocolOpaque.Duck do
@opaque t :: %__MODULE__{feathers: :white_and_grey}
defstruct feathers: :white_and_grey
@spec new :: t
def new(), do: %__MODULE__{}
defimpl Dialyzer.ProtocolOpaque.Entity do
def speak(%Dialyzer.ProtocolOpaque.Duck{}), do: "Quack!"
end
end
defimpl Dialyzer.ProtocolOpaque.Entity, for: Any do
def speak(_any) do
"I can be anything"
end
end
+4 -4
View File
@@ -33,8 +33,8 @@ defmodule JSONTest do
end
test "maps" do
assert JSON.encode!(%{1 => 2, 3.0 => 4.0, key: :bar}) ==
"{\"1\":2,\"3.0\":4.0,\"key\":\"bar\"}"
assert JSON.encode!(%{1 => 2, 3.0 => 4.0, ~c"list" => ~c"list", key: :bar}) ==
"{\"1\":2,\"3.0\":4.0,\"key\":\"bar\",\"list\":[108,105,115,116]}"
end
test "lists" do
@@ -80,8 +80,8 @@ defmodule JSONTest do
end
test "maps" do
assert protocol_encode(%{1 => 2, 3.0 => 4.0, key: :bar}) ==
"{\"1\":2,\"3.0\":4.0,\"key\":\"bar\"}"
assert protocol_encode(%{1 => 2, 3.0 => 4.0, ~c"list" => ~c"list", key: :bar}) ==
"{\"1\":2,\"3.0\":4.0,\"key\":\"bar\",\"list\":[108,105,115,116]}"
end
test "lists" do
@@ -119,23 +119,6 @@ defmodule Kernel.DialyzerTest do
assert_dialyze_no_warnings!(context)
end
@tag warnings: [:specdiffs]
test "no warnings on protocol calls with opaque types", context do
alias Dialyzer.ProtocolOpaque
copy_beam!(context, ProtocolOpaque)
copy_beam!(context, ProtocolOpaque.Entity)
copy_beam!(context, ProtocolOpaque.Entity.Any)
copy_beam!(context, ProtocolOpaque.Duck)
assert_dialyze_no_warnings!(context)
# Also ensure no warnings after consolidation.
Code.prepend_path(context.base_dir)
{:ok, binary} = Protocol.consolidate(ProtocolOpaque.Entity, [ProtocolOpaque.Duck, Any])
File.write!(Path.join(context.outdir, "#{ProtocolOpaque.Entity}.beam"), binary)
assert_dialyze_no_warnings!(context)
end
test "no warnings on and/2 and or/2", context do
copy_beam!(context, Dialyzer.BooleanCheck)
assert_dialyze_no_warnings!(context)
@@ -428,10 +428,9 @@ end
defmodule OptionsParserDeprecationsTest do
use ExUnit.Case, async: true
@warning ~r[not passing the :switches or :strict option to OptionParser is deprecated]
def assert_deprecated(fun) do
assert ExUnit.CaptureIO.capture_io(:stderr, fun) =~ @warning
assert ExUnit.CaptureIO.capture_io(:stderr, fun) =~
"not passing the :switches or :strict option to OptionParser is deprecated"
end
test "parses boolean option" do
+5 -2
View File
@@ -106,15 +106,18 @@ defmodule ProtocolTest do
assert Sample.impl_for(%ImplStruct{}) == Sample.ProtocolTest.ImplStruct
assert Sample.impl_for(%ImplStructExplicitFor{}) == Sample.ProtocolTest.ImplStructExplicitFor
assert Sample.impl_for(%NoImplStruct{}) == nil
assert is_nil(Sample.impl_for(%{__struct__: nil}))
end
test "protocol implementation with Any and struct fallbacks" do
assert WithAny.impl_for(%NoImplStruct{}) == WithAny.Any
# Derived
assert WithAny.impl_for(%ImplStruct{}) == ProtocolTest.WithAny.ProtocolTest.ImplStruct
assert WithAny.impl_for(%{__struct__: nil}) == WithAny.Any
assert WithAny.impl_for(%{__struct__: "foo"}) == WithAny.Map
assert WithAny.impl_for(%{}) == WithAny.Map
assert WithAny.impl_for(self()) == WithAny.Any
# Derived
assert WithAny.impl_for(%ImplStruct{}) == ProtocolTest.WithAny.ProtocolTest.ImplStruct
end
test "protocol not implemented" do
-70
View File
@@ -3,39 +3,6 @@ Code.require_file("test_helper.exs", __DIR__)
defmodule RegexTest do
use ExUnit.Case, async: true
@re_21_3_little %Regex{
re_pattern:
{:re_pattern, 1, 0, 0,
<<69, 82, 67, 80, 94, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255,
255, 99, 0, 0, 0, 0, 0, 1, 0, 0, 0, 64, 0, 6, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 102, 111, 111, 0, 131, 0, 20, 29, 99, 133,
0, 7, 0, 1, 29, 100, 119, 0, 5, 29, 101, 120, 0, 12, 120, 0, 20, 0>>},
re_version: {"8.42 2018-03-20", :little},
source: "c(?<foo>d|e)"
}
@re_21_3_big %Regex{
re_pattern:
{:re_pattern, 1, 0, 0,
<<80, 67, 82, 69, 0, 0, 0, 86, 0, 0, 0, 0, 0, 0, 0, 17, 255, 255, 255, 255, 255, 255, 255,
255, 0, 99, 0, 0, 0, 0, 0, 1, 0, 0, 0, 56, 0, 6, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 102, 111, 111, 0, 131, 0, 20, 29, 99, 133, 0, 7, 0, 1, 29, 100, 119,
0, 5, 29, 101, 120, 0, 12, 120, 0, 20, 0>>},
re_version: {"8.42 2018-03-20", :big},
source: "c(?<foo>d|e)"
}
@re_19_3_little %Regex{
re_pattern:
{:re_pattern, 1, 0, 0,
<<69, 82, 67, 80, 94, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255,
255, 99, 0, 0, 0, 0, 0, 1, 0, 0, 0, 64, 0, 6, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 102, 111, 111, 0, 125, 0, 20, 29, 99, 127,
0, 7, 0, 1, 29, 100, 113, 0, 5, 29, 101, 114, 0, 12, 114, 0, 20, 0>>},
re_version: {"8.33 2013-05-29", :little},
source: "c(?<foo>d|e)"
}
doctest Regex
test "multiline" do
@@ -68,16 +35,9 @@ defmodule RegexTest do
test "literal source" do
assert Regex.source(Regex.compile!("foo")) == "foo"
assert Regex.source(~r"foo") == "foo"
assert Regex.re_pattern(Regex.compile!("foo")) == Regex.re_pattern(~r"foo")
assert Regex.source(Regex.compile!("\a\b\d\e\f\n\r\s\t\v")) == "\a\b\d\e\f\n\r\s\t\v"
assert Regex.source(~r<\a\b\d\e\f\n\r\s\t\v>) == "\\a\\b\\d\\e\\f\\n\\r\\s\\t\\v"
assert Regex.re_pattern(Regex.compile!("\a\b\d\e\f\n\r\s\t\v")) ==
Regex.re_pattern(~r"\x07\x08\x7F\x1B\x0C\x0A\x0D\x20\x09\x0B")
assert Regex.re_pattern(Regex.compile!("\\a\\b\\d\e\f\\n\\r\\s\\t\\v")) ==
Regex.re_pattern(~r"\a\b\d\e\f\n\r\s\t\v")
end
test "Unicode" do
@@ -116,16 +76,6 @@ defmodule RegexTest do
end
end
test "recompile/1" do
new_regex = ~r/foo/
{:ok, %Regex{}} = Regex.recompile(new_regex)
assert %Regex{} = Regex.recompile!(new_regex)
old_regex = Map.delete(~r/foo/, :re_version)
{:ok, %Regex{}} = Regex.recompile(old_regex)
assert %Regex{} = Regex.recompile!(old_regex)
end
test "opts/1" do
assert Regex.opts(Regex.compile!("foo", "i")) == [:caseless]
assert Regex.opts(Regex.compile!("foo", [:ucp])) == [:ucp]
@@ -179,16 +129,6 @@ defmodule RegexTest do
assert Regex.run(~r"bar", "foobar", offset: 2, return: :index) == [{3, 3}]
end
test "run/3 with regexes compiled in different systems" do
assert Regex.run(@re_21_3_little, "abcd abce", capture: :all_names) == ["d"]
assert Regex.run(@re_21_3_big, "abcd abce", capture: :all_names) == ["d"]
assert Regex.run(@re_19_3_little, "abcd abce", capture: :all_names) == ["d"]
end
test "run/3 with regexes with options compiled in different systems" do
assert Regex.run(%{~r/foo/i | re_version: "bad version"}, "FOO") == ["FOO"]
end
test "scan/2" do
assert Regex.scan(~r"c(d|e)", "abcd abce") == [["cd", "d"], ["ce", "e"]]
assert Regex.scan(~r"c(?:d|e)", "abcd abce") == [["cd"], ["ce"]]
@@ -207,16 +147,6 @@ defmodule RegexTest do
assert Regex.scan(~r"^foo", "foobar", offset: 1) == []
end
test "scan/2 with regexes compiled in different systems" do
assert Regex.scan(@re_21_3_little, "abcd abce", capture: :all_names) == [["d"], ["e"]]
assert Regex.scan(@re_21_3_big, "abcd abce", capture: :all_names) == [["d"], ["e"]]
assert Regex.scan(@re_19_3_little, "abcd abce", capture: :all_names) == [["d"], ["e"]]
end
test "scan/2 with regexes with options compiled in different systems" do
assert Regex.scan(%{~r/foo/i | re_version: "bad version"}, "FOO") == [["FOO"]]
end
test "split/2,3" do
assert Regex.split(~r",", "") == [""]
assert Regex.split(~r",", "", trim: true) == []
+6 -6
View File
@@ -393,6 +393,7 @@ defmodule TypespecTest do
@type size :: <<_::3>>
@type unit :: <<_::_*8>>
@type size_and_unit :: <<_::3, _::_*8>>
@type size_prod_unit :: <<_::3*8>>
end
assert [
@@ -401,17 +402,15 @@ defmodule TypespecTest do
type: {:size, {:type, _, :binary, [{:integer, _, 3}, {:integer, _, 0}]}, []},
type:
{:size_and_unit, {:type, _, :binary, [{:integer, _, 3}, {:integer, _, 8}]}, []},
type:
{:size_prod_unit,
{:type, _, :binary,
[{:op, _, :*, {:integer, _, 3}, {:integer, _, 8}}, {:integer, _, 0}]}, []},
type: {:unit, {:type, _, :binary, [{:integer, _, 0}, {:integer, _, 8}]}, []}
] = types(bytecode)
end
test "@type with invalid binary spec" do
assert_raise Kernel.TypespecError, ~r"invalid binary specification", fn ->
test_module do
@type my_type :: <<_::3*8>>
end
end
assert_raise Kernel.TypespecError, ~r"invalid binary specification", fn ->
test_module do
@type my_type :: <<_::atom()>>
@@ -1217,6 +1216,7 @@ defmodule TypespecTest do
quote(do: @type(binary_type1() :: <<_::_*8>>)),
quote(do: @type(binary_type2() :: <<_::3>>)),
quote(do: @type(binary_type3() :: <<_::3, _::_*8>>)),
quote(do: @type(binary_type4() :: <<_::3*8>>)),
quote(do: @type(tuple_type() :: {integer()})),
quote(do: @type(ftype() :: (-> any()) | (-> integer()) | (integer() -> integer()))),
quote(do: @type(cl() :: charlist())),
+1
View File
@@ -1156,6 +1156,7 @@ defmodule ExUnit.Diff do
else
other
|> Map.to_list()
|> Enum.map(&escape_pair/1)
|> build_map_or_struct(struct)
end
end
+2 -2
View File
@@ -707,7 +707,7 @@ defmodule ExUnit.AssertionsTest do
rescue
error in [ExUnit.AssertionError] ->
"foo" = error.left
~r{a} = error.right
%Regex{} = error.right
end
end
@@ -722,7 +722,7 @@ defmodule ExUnit.AssertionsTest do
rescue
error in [ExUnit.AssertionError] ->
"foo" = error.left
~r"o" = error.right
%Regex{} = error.right
end
end
+2
View File
@@ -330,6 +330,8 @@ defmodule ExUnit.DiffTest do
"[[[[], \"Hello-,- \"] | \"world\"] | \"!\"]",
"[[[[], \"Hello \"] | \"world\"] | \"!\"]"
)
refute_diff(:foo = %{bar: [:a | :b]}, "", "")
end
test "proper lists" do
@@ -503,6 +503,17 @@ defmodule ExUnit.FormatterTest do
"""
end
test "formats assertions with nested improper list diffing" do
failure = [{:error, catch_assertion(assert :foo = %{bar: [1 | 2]}), []}]
assert format_test_all_failure(test_module(), failure, 1, :infinity, &diff_formatter/2) =~ """
match (=) failed
code: assert :foo = %{bar: [1 | 2]}
left: :foo
right: %{bar: [1 | 2]}
"""
end
defmodule BadInspect do
defstruct key: 0
+5 -3
View File
@@ -378,7 +378,8 @@ defmodule IEx.Autocomplete do
case Code.Fragment.container_cursor_to_quoted(code) do
{:ok, quoted} ->
case Macro.path(quoted, &match?({:__cursor__, _, []}, &1)) do
[cursor, {:%{}, _, pairs}, {:%, _, [{:__aliases__, _, aliases}, _map]} | _] ->
[cursor, {:%{}, _, pairs}, {:%, _, [{:__aliases__, _, aliases = [h | _]}, _map]} | _]
when is_atom(h) ->
container_context_struct(cursor, pairs, aliases, shell)
[
@@ -386,8 +387,9 @@ defmodule IEx.Autocomplete do
pairs,
{:|, _, _},
{:%{}, _, _},
{:%, _, [{:__aliases__, _, aliases}, _map]} | _
] ->
{:%, _, [{:__aliases__, _, aliases = [h | _]}, _map]} | _
]
when is_atom(h) ->
container_context_struct(cursor, pairs, aliases, shell)
[cursor, pairs, {:|, _, [{variable, _, nil} | _]}, {:%{}, _, _} | _] ->
+9 -1
View File
@@ -12,10 +12,18 @@ defmodule IEx.MixListener do
@doc """
Unloads all modules invalidated by external compilations.
Returns `:noop` if there is no module to purge or if
the listener is not running (it may happen when connecting
via --remsh to a node that was started without IEx).
"""
@spec purge :: :ok | :noop
def purge do
GenServer.call(@name, :purge, :infinity)
if Process.whereis(@name) do
GenServer.call(@name, :purge, :infinity)
else
:noop
end
end
@impl true
+3
View File
@@ -416,6 +416,9 @@ defmodule IEx.AutocompleteTest do
assert {:yes, ~c"ry: ", []} = expand(~c"%URI{path: \"foo\", que")
assert {:no, [], []} = expand(~c"%URI{path: \"foo\", unkno")
assert {:no, [], []} = expand(~c"%Unknown{path: \"foo\", unkno")
assert {:yes, [], _} = expand(~c"%__MODULE__{")
assert {:yes, [], _} = expand(~c"%__MODULE__.Some{")
end
test "completion for struct keys in update syntax" do
+1 -1
View File
@@ -349,7 +349,7 @@ defmodule IEx.HelpersTest do
assert captured =~ "-spec sleep(Time) -> ok when Time :: timeout()."
else
assert captured =~ "sleep(Time)"
assert captured =~ "@spec sleep(time) :: :ok when time: timeout()"
assert captured =~ "@spec sleep(time) :: :ok when time: "
end
end
+1 -1
View File
@@ -63,7 +63,7 @@ defmodule Mix.Tasks.Cmd do
|> Enum.map(&String.to_atom/1)
if apps != [] do
IO.warn("the --app in mix cmd is deprecated")
IO.warn("the --app in mix cmd is deprecated. Use mix do --app instead.")
end
if apps == [] or Mix.Project.config()[:app] in apps do
+4 -1
View File
@@ -23,6 +23,7 @@ defmodule Mix.Tasks.Deps.Loadpaths do
* `--no-compile` - does not compile even if files require compilation
* `--no-deps-check` - does not check or compile deps, only load available ones
* `--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
"""
@@ -71,7 +72,9 @@ defmodule Mix.Tasks.Deps.Loadpaths do
# For now we only allow listeners defined in dependencies, so
# we start them right after adding adding deps to the path
Mix.PubSub.start_listeners()
if "--no-listeners" not in args do
Mix.PubSub.start_listeners()
end
:ok
end
+1
View File
@@ -21,6 +21,7 @@ defmodule Mix.Tasks.Loadpaths do
* `--no-compile` - does not compile dependencies, only check and load them
* `--no-deps-check` - does not check dependencies, only load available ones
* `--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
"""
+4
View File
@@ -389,6 +389,10 @@ defmodule Mix.Tasks.CompileTest do
File.write!("src/b.erl", "-module(b).")
File.write!("src/c.erl", "-module(c).")
# Ensure we can boot with compilation and listeners if desired
assert mix(["loadpaths", "--no-compile", "--no-listeners"]) == ""
# Now setup dependencies
mix(["deps.compile"])
parent = self()