Skip to content

try with many independent problems only warns once (1.20 RC5) #15397

@evadne

Description

@evadne

Existing issue

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

Elixir and Erlang/OTP versions

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

Elixir 1.20.0-rc.5 (383ffc5) (compiled with Erlang/OTP 28)

Operating system

Linux / macOS

Current behavior

On current main (b58e84352), the behaviour of try’s diagnostics changed; once one try alternative sets context.failed, later independent alternatives stop reporting diagnostics:

1. Two rescue bodies with independent bad calls:

defmodule A1R1 do
  def f do
    try do
      raise "oops"
    rescue
      _ in RuntimeError -> String.length(1)
      _ in ArgumentError -> String.length(2)
    end
  end
end

v1.19.5 (d33fd8e41):

(none)

Current main / v1.20 RC (b58e84352):

warning: incompatible types given to String.length/1:
    String.length(1)
given types:
    -integer()-
but expected one of:
    binary()
└─ a1_r1.ex:6:35: A1R1.f/0

2. Failed rescue body followed by undefined exception head:

defmodule A1R2 do
  def f do
    try do
      raise "oops"
    rescue
      _ in RuntimeError -> String.length(1)
      e in UnknownError -> e
    end
  end
end

v1.19.5 (d33fd8e41):

warning: struct UnknownError is undefined (module UnknownError is not available or is yet to be defined). Make sure the module name is correct and has been specified in full (or that an alias has been defined)
└─ a1_r2.ex:7:9: A1R2.f/0

Current main / v1.20 RC (b58e84352):

warning: incompatible types given to String.length/1:
    String.length(1)
given types:
    -integer()-
but expected one of:
    binary()
└─ a1_r2.ex:6:35: A1R2.f/0

3. Failed rescue body before catch body:

defmodule A1R3 do
  def f do
    try do
      throw(:x)
    rescue
      _ in RuntimeError -> String.length(1)
    catch
      :throw, :x -> Integer.to_string(:atom)
    end
  end
end

v1.19.5 (d33fd8e41):

warning: the call to integer_to_binary/1 will fail with a 'badarg' exception
└─ a1_r3.ex:8:29

warning: incompatible types given to Integer.to_string/1:
    Integer.to_string(:atom)
given types:
    -:atom-
but expected one of:
    integer()
└─ a1_r3.ex:8:29: A1R3.f/0

Current main / v1.20 RC (b58e84352):

warning: incompatible types given to String.length/1:
    String.length(1)
given types:
    -integer()-
but expected one of:
    binary()
└─ a1_r3.ex:6:35: A1R3.f/0

4. else body warning before rescue body:

defmodule A1R4 do
  def f(x) do
    try do
      x
    rescue
      _ in RuntimeError -> Integer.to_string(:atom)
    else
      :ok -> String.length(1)
    end
  end
end

v1.19.5 (d33fd8e41):

warning: incompatible types given to Integer.to_string/1:
    Integer.to_string(:atom)
given types:
    -:atom-
but expected one of:
    integer()
└─ a1_r4.ex:6:36: A1R4.f/1

Current main / v1.20 RC (b58e84352):

warning: incompatible types given to String.length/1:
    String.length(1)
given types:
    -integer()-
but expected one of:
    binary()
└─ a1_r4.ex:8:21: A1R4.f/1

Concern

So, try seems to suppress diagnostics after the 1st failing alternative.

Expected behavior

Equivalent non-try structures still behave as expected (case clauses, multiple catch clauses, or multiple else clauses); they report independent clause warnings.

Speculation

The behaviour appears to come from lib/elixir/lib/module/types/expr.ex:

  • try handles else first, then reduces rescue/catch blocks sequentially;
  • Rescue clauses are checked by an inline nested Enum.reduce;
  • of_rescue/9 resets variables after the rescue body with Of.reset_vars/2, but it does not isolate or restore failed;
  • Normal clause checking goes through of_clauses_fun/7, which wraps each clause with reset_failed/2 and set_failed/2, allowing independent diagnostics while preserving the final failed state.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions