Compare commits

...
7 Commits
Author SHA1 Message Date
inoas bbf5e2bc7b Change wording from "change" to "rebind" (#8475) 2018-12-06 13:07:49 +01:00
José Valim ceafd0cdab Update CHANGELOG.md 2016-10-29 10:40:29 +02:00
José Valim 87710cd495 Release v1.3.4 2016-10-09 16:28:40 +02:00
Magnus Lång 6e2c29c9d4 Fix Dialyzer warnings on opaque protocol calls (#5286)
Prior to this change, calling a protocol function with an opaque type
would yield a warning, as Dialyzer concludes that the impl_for/1
function can't handle opaque arguments, since all clauses would
destructure their arguments in some way.

By adding a catch-all clause that does not destructure its argument,
Dialyzer no longer draws this conclusion, and the warnings go away.

As noted in the protocol.ex comment, this is technically a hack as it
relies on Dialyzer not being smart enough. However, I would not expect
it to break soon, if ever.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-10-04 14:17:47 +02:00
James Fish f7b32487c0 Flush error_logger before capturing (#5280)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-10-01 10:39:22 +02:00
José Valim eedc9bc757 Do not build matches if variables are the same
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-09-27 18:25:04 +02:00
José Valim f053df848f Move raise checks to runtime callback, closes #5257
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-09-26 20:36:22 +02:00
10 changed files with 106 additions and 34 deletions
+19 -1
View File
@@ -23,7 +23,7 @@ def format(message, opts) do
end
```
The `if` block above is implicitly changing the value in `message`. Now imagine we want to move the `if` block to its own function to clean up the implementation:
The `if` block above is implicitly rebinding the outer scope variable `message` to a new value. Now imagine we want to move the `if` block to its own function to clean up the implementation:
```elixir
def format(message, opts) do
@@ -268,6 +268,20 @@ end
By restricting hierarchies in favor of named setups, it is straight-forward for the developer to glance at each describe block and know exactly the setup steps involved.
## v1.3.4 (2016-10-09)
### 1. Bug fixes
#### Elixir
* [Kernel] Ensure the compiler does not generate unecessary variable bindings inside case statements. This improves the code emitted and make sure "unused variable warnings" are not mistakenly silenced
* [Kernel] Move `raise` checks to runtime to avoid crashing cover on Erlang 19.1
* [Protocol] Do not emit warnings when using protocols on opaque types
#### ExUnit
* [ExUnit.CaptureLog] Flush Erlang's `:error_logger` before capturing to avoid mixed messages
## v1.3.3 (2016-09-17)
### 1. Enhancements
@@ -503,3 +517,7 @@ This release deprecates many APIs that have been soft-deprecated in previous Eli
* [String] `String.valid_character?/1` is deprecated in favor of `String.valid?/1` with pattern matching
* [Task] `Task.find/2` is deprecated in favor of explicit message matching
* [URI] Passing a non-map to `URI.decode_query/2` is deprecated
## v1.2
The CHANGELOG for v1.2 releases can be found [in the v1.2 branch](https://github.com/elixir-lang/elixir/blob/v1.2/CHANGELOG.md).
+1 -1
View File
@@ -1 +1 @@
1.3.3
1.3.4
+2 -24
View File
@@ -1350,31 +1350,9 @@ defmodule Kernel do
:erlang.error unquote(alias).exception([])
end
_ ->
generated = fn fun, var ->
{fun, [generated: true, line: -1], [{var, [], __MODULE__}]}
quote do
:erlang.error Kernel.Utils.raise(unquote(msg))
end
{fun, meta, [arg, [do: clauses]]} =
quote do
case unquote(msg) do
msg when unquote(generated.(:is_binary, :msg)) ->
:erlang.error RuntimeError.exception(msg)
atom when unquote(generated.(:is_atom, :atom)) ->
:erlang.error atom.exception([])
%{__struct__: struct, __exception__: true} = other when is_atom(struct) ->
:erlang.error other
other ->
message = "raise/1 expects an alias, string or exception as the first argument, got: #{inspect other}"
:erlang.error ArgumentError.exception(message)
end
end
clauses =
:lists.map(fn {:->, meta, args} ->
{:->, [generated: true] ++ Keyword.put(meta, :line, -1), args}
end, clauses)
{fun, meta, [arg, [do: clauses]]}
end
end
+29
View File
@@ -3,6 +3,9 @@ import Kernel, except: [destructure: 2, defdelegate: 2, defstruct: 2]
defmodule Kernel.Utils do
@moduledoc false
@doc """
Callback for destructure.
"""
def destructure(list, count) when is_list(list), do: destructure_list(list, count)
def destructure(nil, count), do: destructure_nil(count)
@@ -13,6 +16,9 @@ defmodule Kernel.Utils do
defp destructure_nil(0), do: []
defp destructure_nil(count), do: [nil | destructure_nil(count - 1)]
@doc """
Callback for defdelegate.
"""
def defdelegate(fun, opts) do
append_first = Keyword.get(opts, :append_first, false)
@@ -56,6 +62,9 @@ defmodule Kernel.Utils do
"defdelegate/2 only accepts function parameters, got: #{Macro.to_string(code)}"
end
@doc """
Callback for defstruct.
"""
def defstruct(module, fields) do
case fields do
fs when is_list(fs) ->
@@ -85,10 +94,30 @@ defmodule Kernel.Utils do
Module.get_attribute(module, :derive)}
end
@doc """
Announcing callback for defstruct.
"""
def announce_struct(module) do
case :erlang.get(:elixir_compiler_pid) do
:undefined -> :ok
pid -> send(pid, {:struct_available, module})
end
end
@doc """
Callback for raise.
"""
def raise(msg) when is_binary(msg) do
RuntimeError.exception(msg)
end
def raise(atom) when is_atom(atom) do
atom.exception([])
end
def raise(%{__struct__: struct, __exception__: true} = exception) when is_atom(struct) do
exception
end
def raise(other) do
ArgumentError.exception("raise/1 expects an alias, string or exception as " <>
"the first argument, got: #{inspect other}")
end
end
+10
View File
@@ -451,6 +451,16 @@ defmodule Protocol do
end
end, builtin)
# Define a catch-all impl_for/1 clause to pacify Dialyzer (since
# destructuring opaque types is illegal, Dialyzer will think none of the
# previous clauses matches opaque types, and without this clause, will
# conclude that impl_for can't handle an opaque argument). This is a hack
# since it relies on Dialyzer not being smart enough to conclude that all
# opaque types will get the any_impl_for/0 implementation.
Kernel.def impl_for(_) do
any_impl_for()
end
@doc false
@spec impl_for!(term) :: atom | no_return
Kernel.def impl_for!(data) do
+6 -6
View File
@@ -198,17 +198,17 @@ normalize_vars(Key, {Ref, Counter, _Safe},
% Generate match vars by checking if they were updated
% or not and assigning the previous value.
generate_match_vars([{Key, Value, Expr} | T], ClauseVars, Left, Right) ->
generate_match_vars([{Key, {Value, _, _}, Expr} | T], ClauseVars, Left, Right) ->
case maps:find(Key, ClauseVars) of
{ok, Value} ->
{ok, {Value, _, _}} ->
generate_match_vars(T, ClauseVars, Left, Right);
{ok, Clause} ->
{ok, {Clause, _, _}} ->
generate_match_vars(T, ClauseVars,
[{var, 0, element(1, Value)} | Left],
[{var, 0, element(1, Clause)} | Right]);
[{var, 0, Value} | Left],
[{var, 0, Clause} | Right]);
error ->
generate_match_vars(T, ClauseVars,
[{var, 0, element(1, Value)} | Left], [Expr | Right])
[{var, 0, Value} | Left], [Expr | Right])
end;
generate_match_vars([], _ClauseVars, Left, Right) ->
@@ -0,0 +1,21 @@
defmodule Dialyzer.ProtocolOpaque do
def circus() do
Dialyzer.ProtocolOpaque.Entity.speak(Dialyzer.ProtocolOpaque.Duck.new)
end
end
defprotocol Dialyzer.ProtocolOpaque.Entity do
def speak(entity)
end
defmodule Dialyzer.ProtocolOpaque.Duck do
@opaque t :: %__MODULE__{}
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
@@ -81,6 +81,13 @@ defmodule Kernel.DialyzerTest do
assert_dialyze_no_warnings! context
end
test "no warnings on protocol calls with opaque types", context do
copy_beam! context, Dialyzer.ProtocolOpaque
copy_beam! context, Dialyzer.ProtocolOpaque.Entity
copy_beam! context, Dialyzer.ProtocolOpaque.Duck
assert_dialyze_no_warnings! context
end
defp copy_beam!(context, module) do
name = "#{module}.beam"
File.cp! Path.join(context[:base_dir], name),
+10 -1
View File
@@ -37,7 +37,15 @@ defmodule ExUnit.CaptureLog do
Returns the binary which is the captured output.
This function mutes the `:console` backend
and captures any log messages sent to Logger.
and captures any log messages sent to Logger from the calling
processes. It is possible to ensure explicit log messages from other
processes are captured by waiting for their exit or monitor
signal. This does not guarantee to capture VM generated log messages
when a process that is spawned using a low level `Kernel` spawn function
(e.g. `Kernel.spawn/1`) raises an exception that isn't rescued or a throw
that isn't caught. A `Task`, or other OTP process, will send explicit logs
that are sent before its exit or monitor signal and will not cause VM
generated log messages.
Note that when the `async` is set to `true`,
the messages from another test might be captured.
@@ -59,6 +67,7 @@ defmodule ExUnit.CaptureLog do
{:ok, string_io} = StringIO.open("")
try do
_ = :gen_event.which_handlers(:error_logger)
:ok = add_capture(string_io, opts)
ref = ExUnit.CaptureServer.log_capture_on(self())
+1 -1
View File
@@ -1,6 +1,6 @@
{application, elixir,
[{description, "elixir"},
{vsn, "1.3.3"},
{vsn, "1.3.4"},
{modules, [
elixir
]},