Previously, the internal Elixir expansion pass worked directly on
Macro.Env. However, this poised an issue, if we want to track
more information in the pass, we ended up exposing it on
Macro.Env, making it larger, and potentially slowing down
operations such as __ENV__ serialization.
This commit refactors the expansion pass to work with two
structures, the Macro.Env struct and a #elixir_ex{} record.
On OTP 24:
iex> <<x::float-16>> = <<60, 0>>
iex> x
1.0
iex> <<x::float-16>>
<<60, 0>>
Before OTP 24 we'd get errors or wouldn't match:
iex> <<1.0::float-16>>
** (ArgumentError) argument error while evaluating iex at line 1
<<x::float-16>> = <<60, 0>>
** (MatchError) no match of right hand side value: <<60, 0>>
iex> (fn <<x::float-16>> -> x; _ -> :nomatch end).(<<60, 0>>)
:nomatch
During the bitstring expansion, guard checks were avoided due to
a special case for inlining binaries during interpolation. These
changes ensure guards are checked.
Binary/bitstring matching allows to dynamically define the
`size` of the binary in certain conditions:
* if the `size` variable is defined prior to the pattern
match:
iex> size = 8
iex> <<a::size(size), rest::binary>> = "hello"
iex> a
104
* if the `size` variable is matched within the same
binary/bitstring match, prior to its use:
iex> <<name_size::size(8), name::binary-size(name_size), _rest::binary>> = <<5, "Frank the Walrus">>
iex> name
"Frank"
Other cases are considered illegal patterns, for example:
{name_size, <<name::binary-size(name_size), _rest::binary>>} = {5, "Frank the Walrus"}
This commit raises a `CompileError: undefined variable ...` for such cases.
This gives us better control over when and how unused variables are printed.
As a result, we are able to emit unused variable warnings in situations
we could not before. This also opens up the way for us to remove a
dependency on erl_lint and track types information, which allows us to
speed up compilation times about 5% and allow us to emit more performant
code in some situations.