Skip to content

Variable leak between default expressions in def #15463

@lukaszsamson

Description

@lukaszsamson

Existing issue

  • I have searched existing issues and could not find a duplicate.

Elixir and Erlang/OTP versions

Erlang/OTP 28 [erts-16.4.0.1] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit]

Elixir 1.20.1 (compiled with Erlang/OTP 28)
Reproducible in all supported versions, crashing typesystem since 1.18

Operating system

any

Current behavior

Repro:

defmodule Repro do
              def f(a \\ (x = 1), b \\ x), do: {a, b}
            end
** (RuntimeError) found error while checking types for Repro.f/1:

** (MatchError) no match of right hand side value:

    %{
      vars: %{
        1 => %{
          name: :x1,
          type: %{dynamic: :term},
          context: :elixir_def,
          paths: [],
          deps: %{},
          off_traces: [
            {{:x1, [generated: true, version: 1], :elixir_def}, "iex",
             %{dynamic: :term}}
          ]
        }
      },
      failed: false,
      warnings: [],
      reverse_arrows: %{},
      local_sigs: %{
        {:f, 0} => {:def,
         {:infer, nil,
          [{[], %{dynamic: %{tuple: {-98443456, :closed, [:term, :term]}}}}]},
         [{0, 0}]},
        {:f, 1} => :def,
        {:f, 2} => {:def,
         {:infer, nil,
          [
            {[:term, :term],
             %{dynamic: %{tuple: {-98443456, :closed, [:term, :term]}}}}
          ]}, [{0, 0}]}
      },
      local_used: %{},
      conditional_vars: nil,
      pattern_info: nil,
      subpatterns: %{}
    }

The exception happened while checking this code:

def f(x1) do
  super(x1, x)
end

Please report this bug at: https://github.com/elixir-lang/elixir/issues

    (elixir 1.20.1) lib/module/types/of.ex:98: Module.Types.Of.refine_body_var/5
    (elixir 1.20.1) lib/module/types/helpers.ex:583: Module.Types.Helpers.zip_map_reduce/5
    (elixir 1.20.1) lib/module/types/apply.ex:1618: Module.Types.Apply.local_arrows/7
    (elixir 1.20.1) lib/module/types.ex:360: Module.Types.default_local_handler/9
    (elixir 1.20.1) lib/module/types.ex:306: Module.Types.local_handler/5
    (elixir 1.20.1) lib/module/types.ex:86: anonymous fn/5 in Module.Types.infer/7
    (elixir 1.20.1) lib/enum.ex:2622: Enum."-reduce/3-lists^foldl/2-0-"/3
    iex:1: (file)

The code triggering the bug is invalid but the bug itself is not fully related to typesystem. On 1.17

** (CompileError) Elixir.Repro: unbound variable '_x@1' in f/1
    (stdlib 5.2.3.6) lists.erl:1686: :lists.foreach_1/2
    (elixir 1.16.3) src/elixir_erl.erl:448: :elixir_erl.load_form/5
    (elixir 1.16.3) src/elixir_module.erl:190: anonymous fn/9 in :elixir_module.compile/7
    iex:20: (file)

Likely reason:
When expanding def default args, SE is threaded through the reduce thus variables defined in one default leak to the other

expand_defaults([{'\\\\', Meta, [Expr, Default]} | Args], S, E, Acc) ->
{ExpandedDefault, SE, _} = elixir_expand:expand(Default, S, E),
expand_defaults(Args, SE, E, [{'\\\\', Meta, [Expr, ExpandedDefault]} | Acc]);
expand_defaults([Arg | Args], S, E, Acc) ->
expand_defaults(Args, S, E, [Arg | Acc]);
expand_defaults([], S, _E, Acc) ->
{lists:reverse(Acc), S}.

Expected behavior

A clean compilation error and variables not leaking

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions