This transforms the struct update into a type assertion,
requiring the type system to be sure the expression has
precisely the given struct type.
The struct update syntax may still be deprecated in the
future but this will provide a safer migration path and
allow us to engage in more conversations with the community.
The struct update syntax was added early in Elixir to help validate
at compile-time that the update keys were valid. However, for a couple
releases already, Elixir's static analysis can perform such validation
more reliably and find more error if you pattern match on the struct
when the variable is defined instead.
This deprecation simplifies the language and pushes developers to
better alternatives.
Closes#13974.
We now warn:
* `expr.field` when `expr` may not be a map
* `expr.call()` when `expr` may not be an atom
* `expr.call(...)` when `expr` may not be an atom
* `&expr.foo/1` when `expr` may not be an atom
Furthermore, we lay down the ground work for checking undefined
and deprecation warnings across unions. For example, if you write
this code:
mod = if something?, do: Foo, else: Bar
mod.some_function()
In the future, it will warn if any of Foo OR Bar do not define the relevant function.
Finally, we improve pretty printing of maps and aliases in types.
Ideally we want to list them as requires but we don't have
the infrastructure to do so. So meanwhile, we list them
as exports, which is the same level used by require.
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.
The Elixir compiler spawns a separate process per file.
When a file has to wait on another module, Elixir tracks
in the compiler that the file is waiting.
However, every time a module is defined, the Elixir compiler
spawns a separate process to compile to .beam, and this
process may expand structs in the typespec. Since this
new process is no longer the original file process, Elixir
was not able to track its waiting time.
This PR address this issue by passing the original file_pid
to the .beam compiler process. Note though that, if we
change typespecs to be compiled in the original file process,
this change is no longer required, but at the moment there are
no plans to make such change.
Closes#11036.
This feature allows IDEs and other tools wanting to
perform source code analysis to do so reliably without
a need to reimplement Elixir's compiler expansion and
without relying on Elixir's private APIs.
This commit also adds :parser_options to compiler
options, which allows developers to combine both options
to retrieve more accurate information, such as columns.
This is important when you may want to check if a module
is available but if that module is running a similar check
on you, you don't want the compiler to fail.
This change fixes inconsistencies when building structs: if the
struct was built using the map syntax the `:__struct__` key
was overridden by the given module name; however, if the struct
was built using `struct/1,2` the `:__struct__` key was kept.
For example:
defmodule X do
defstruct [:x]
end
defmodule Y do
defdelegate __struct__, to: X
defdelegate __struct__(args), to: X
end
iex> %Y{}
%Y{x: nil}
iex> struct(Y)
%X{x: nil}
Closes#8800
__struct__/0,1 is not meant to return an AST. Using
Macro.to_string/1 might lead to incorrect error messages:
defmodule MyStruct do
def __struct__, do: {:ok, :one, :two}
def __struct__(_), do: {:ok, :one, :two}
end
iex> %MyStruct{}
** (CompileError) iex:2: expected MyStruct.__struct__/1 to
return a map with a :__struct__ key that holds the name
of the struct (atom), got: ok
Prior to this patch, if a module depends on the struct of
another module and that other module depends on the struct
of the first module inside typespecs, it would lead to a
compiler deadlock, which this deadlock would not happen
in "regular code".
This commit also adds a test to ensure @enforce_keys are
not enforced when expanding typespecs.
For now, we are using a compiler private API, but we should
provide a public version for it in a future commit.
Optimizes :maps.put/3 to a VM operation instead of a remote call, including nested calls to it.
It does the same for :maps.merge/2 with a simple map on the right side.
Closes#7353
We use this option in ExUnit as we are escaping
the code to eventually convert it to a string
representation and therefore the metadata is not
relevant.