Skip to content

Commit 35eeab3

Browse files
committed
Fix regression on default inference, closes #15359
1 parent aae39c8 commit 35eeab3

3 files changed

Lines changed: 37 additions & 7 deletions

File tree

lib/elixir/lib/module/types.ex

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -360,20 +360,21 @@ defmodule Module.Types do
360360
Apply.local_arrows(call_fun, call_args, term, body, stack, head_context, of_fun)
361361

362362
# For each arrow, compute the default arrow
363-
{_, mapping, inferred} =
364-
Enum.reduce(arrows, {0, [], []}, fn
365-
{clause_domain, return_type}, {index, mapping, inferred} ->
363+
{_, _, mapping, inferred} =
364+
Enum.reduce(arrows, {0, 0, [], []}, fn
365+
{clause_domain, return_type}, {index, total, mapping, inferred} ->
366366
of_fun = &Expr.of_expr(&1, &2, body, stack, &3)
367367

368368
{_clause_args, clause_context} =
369369
Helpers.zip_map_reduce(call_args, clause_domain, head_context, of_fun)
370370

371371
clause_types = Pattern.of_domain(trees, stack, clause_context)
372372

373-
{_type_index, inferred} =
374-
add_inferred(inferred, clause_types, return_type, index - 1, [])
373+
{type_index, inferred} =
374+
add_inferred(inferred, clause_types, return_type, total - 1, [])
375375

376-
{index + 1, [{0, index} | mapping], inferred}
376+
total = if type_index == -1, do: total + 1, else: total
377+
{index + 1, total, [{0, index} | mapping], inferred}
377378
end)
378379

379380
domain =

lib/elixir/pages/references/gradual-set-theoretic-types.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ On the other hand, type inference offers the benefit of enabling type checking f
235235

236236
Type inference in Elixir is best-effort: it doesn't guarantee it will find all possible type incompatibilities, only that it may find bugs where all combinations of a type _will_ fail, even in the absence of explicit type annotations. It is meant to be an efficient routine that brings developers some benefits of static typing, without requiring any effort from them and keeping the expressiveness of the language.
237237

238-
In the long term, Elixir developers who want static typing guarantees must explicitly add type signatures to their functions (see "Roadmap"). Any function with an explicit type signature will be typed checked against the user-provided annotations, as in other statically typed languages.
238+
In the long term, Elixir developers who want static typing guarantees must explicitly add type signatures to their functions (see "Roadmap"). Any function with an explicit type signature will be typed checked against the user-provided annotations, as in other statically typed languages.
239239

240240
### False positives during inference
241241

lib/elixir/test/elixir/module/types/infer_test.exs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,4 +283,33 @@ defmodule Module.Types.InferTest do
283283
{[atom([:error])], dynamic(tuple([atom([:error]), term()]))}
284284
]
285285
end
286+
287+
test "from defaults (regression with multiple clauses)", config do
288+
types =
289+
infer config do
290+
def entries(tree_node, opts \\ [keep_text: true])
291+
292+
def entries({_, _, subentries}, keep_text: false) do
293+
Enum.filter(subentries, &is_tuple/1)
294+
end
295+
296+
def entries({_, _, subentries}, keep_text: _) do
297+
subentries
298+
end
299+
300+
def entries({_, _, _} = tree_node, opts) do
301+
opts = Keyword.validate!(opts, keep_text: true)
302+
entries(tree_node, keep_text: opts[:keep_text])
303+
end
304+
305+
def entries(_tree_node, _opts), do: nil
306+
end
307+
308+
{:infer, _, signature} = types[{:entries, 1}]
309+
310+
assert signature == [
311+
{[tuple([term(), term(), term()])], dynamic()},
312+
{[negation(tuple([term(), term(), term()]))], atom([nil])}
313+
]
314+
end
286315
end

0 commit comments

Comments
 (0)