Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/elixir/lib/module/types/descr.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2099,9 +2099,9 @@ defmodule Module.Types.Descr do
defp list_hd_static(%{}), do: none()

@doc """
Returns the tail of a list.
Returns the tail of a list.
For a `non_empty_list(t)`, the tail type is `list(t)`.
For a `non_empty_list(t)`, the tail type is `list(t)`.
For an improper list `non_empty_list(t, s)`, the tail type is
`list(t, s) or s` (either the rest of the list or the terminator)
"""
Expand Down
1 change: 1 addition & 0 deletions lib/elixir/lib/module/types/expr.ex
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,7 @@ defmodule Module.Types.Expr do
_ ->
expected = if structs == [], do: @exception, else: Enum.reduce(structs, &union/2)
expr = {:__block__, [type_check: info], [expr]}
context = Of.declare_var(var, context)
{_ok?, _type, context} = Of.refine_head_var(var, expected, expr, stack, context)
context
end
Expand Down
2 changes: 1 addition & 1 deletion lib/elixir/lib/module/types/helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ defmodule Module.Types.Helpers do
version = meta[:version]

case vars do
%{^version => %{off_traces: off_traces, name: name, context: context}} ->
%{^version => %{off_traces: [_ | _] = off_traces, name: name, context: context}} ->
{:ok,
Map.put(versions, version, %{
type: :variable,
Expand Down
82 changes: 38 additions & 44 deletions lib/elixir/lib/module/types/of.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,40 @@ defmodule Module.Types.Of do

@doc """
Marks a variable with error.

This purposedly deletes all traces of the variable,
as it is often invoked when the cause for error is elsewhere.
"""
def error_var({_var_name, meta, _var_context}, context) do
version = Keyword.fetch!(meta, :version)

update_in(context.vars[version], fn
%{errored: true} = data -> data
data -> Map.put(%{data | type: error_type(), off_traces: []}, :errored, true)
end)
end

@doc """
Declares a variable.
"""
def error_var(var, context) do
def declare_var(var, context) do
{var_name, meta, var_context} = var
version = Keyword.fetch!(meta, :version)

data = %{
type: error_type(),
name: var_name,
context: var_context,
off_traces: []
}
case context.vars do
%{^version => _} ->
context

vars ->
data = %{
type: term(),
name: var_name,
context: var_context,
off_traces: []
}

put_in(context.vars[version], data)
%{context | vars: Map.put(vars, version, data)}
end
end

@doc """
Expand All @@ -56,7 +77,7 @@ defmodule Module.Types.Of do
version = Keyword.fetch!(meta, :version)
%{vars: %{^version => %{type: old_type, off_traces: off_traces} = data} = vars} = context

if gradual?(old_type) and type not in [term(), dynamic()] do
if gradual?(old_type) and type not in [term(), dynamic()] and not is_map_key(data, :errored) do
case compatible_intersection(old_type, type) do
{:ok, new_type} when new_type != old_type ->
data = %{
Expand All @@ -82,11 +103,13 @@ defmodule Module.Types.Of do
because we want to refine types. Otherwise we should
use compatibility.
"""
def refine_head_var(var, type, expr, stack, context) do
{var_name, meta, var_context} = var
def refine_head_var({_, meta, _}, type, expr, stack, context) do
version = Keyword.fetch!(meta, :version)

case context.vars do
%{^version => %{errored: true}} ->
{:ok, error_type(), context}

%{^version => %{type: old_type, off_traces: off_traces} = data} = vars ->
new_type = intersection(type, old_type)

Expand All @@ -96,26 +119,14 @@ defmodule Module.Types.Of do
off_traces: new_trace(expr, type, stack, off_traces)
}

context = %{context | vars: %{vars | version => data}}

# We need to return error otherwise it leads to cascading errors
if empty?(new_type) do
{:error, error_type(),
error({:refine_head_var, old_type, type, var, context}, meta, stack, context)}
data = Map.put(%{data | type: error_type()}, :errored, true)
context = %{context | vars: %{vars | version => data}}
{:error, old_type, context}
else
context = %{context | vars: %{vars | version => data}}
{:ok, new_type, context}
end

%{} = vars ->
data = %{
type: type,
name: var_name,
context: var_context,
off_traces: new_trace(expr, type, stack, [])
}

context = %{context | vars: Map.put(vars, version, data)}
{:ok, type, context}
end
end

Expand Down Expand Up @@ -546,23 +557,6 @@ defmodule Module.Types.Of do
error(__MODULE__, warning, meta, stack, context)
end

def format_diagnostic({:refine_head_var, old_type, new_type, var, context}) do
traces = collect_traces(var, context)

%{
details: %{typing_traces: traces},
message:
IO.iodata_to_binary([
"""
incompatible types assigned to #{format_var(var)}:

#{to_quoted_string(old_type)} !~ #{to_quoted_string(new_type)}
""",
format_traces(traces)
])
}
end

def format_diagnostic({:badbinary, kind, meta, expr, expected_type, actual_type, context}) do
type = if kind == :match, do: "matching", else: "construction"
hints = if meta[:inferred_bitstring_spec], do: [:inferred_bitstring_spec], else: []
Expand Down
Loading
Loading