Visitar URL original
False "redundant clause" warning when an operand of `and`/`or` in a guard may raise · Issue #16035 · elixir-lang/elixir · GitHub
Skip to content

False "redundant clause" warning when an operand of and/or in a guard may raise #16035

Closed

Description

@josevalim

Elixir and Erlang/OTP versions

Elixir 1.21.0-dev (c81e88f) on Erlang/OTP 29 (also on 1.20.4).

Operating system

macOS

Current behavior

defmodule PE2 do
  def f(x) when (is_integer(x) or is_tuple(x)) and tuple_size(x) == 2 or is_integer(x), do: :first
  def f(x) when is_integer(x), do: :second
  def f(_), do: :other
end

PE2.f(3)
#=> :second

emits:

warning: the following clause is redundant:

    def f(x) when is_integer(x)

it has type:

    integer()

previous clauses have already matched on the following types:

    integer() or {term(), term()}

However, PE2.f(3) returns :second, so the clause is not redundant.

Expected behavior

No warning. For an integer, tuple_size(x) raises. That makes the whole guard fail, so the final or is_integer(x) is never evaluated. The first clause accepts no integer.

Two things go wrong:

  • and keeps the environment of its left operand (integer() or tuple()) as exact, although its right operand may raise on some of those values. It should be exact only when the right operand cannot raise on any success of the left one.
  • or then treats the successes of its right operand (is_integer(x)) as exact, although its left operand may raise on them.

This is likely the same root cause as #16034, also in or. A fix would track whether each operand may raise, and treat a result as exact only if no earlier operand can raise on it.

Addendum: a more precise analysis

Once the above is fixed, the analysis could also remove the values on which the left conjunct surely succeeds and the right conjunct surely raises. In the example, is_integer(x) or is_tuple(x) surely succeeds on integers and tuple_size(x) == 2 surely raises on them. So the first disjunct can be analysed as "exact" under x: not integer(). Then the final is_integer(x) is analysed under not integer() and not {term(), term()} and is always false. That would allow a warning on that disjunct, and the checker would know the first clause accepts exactly 2-tuples.

Found while working on the formalization of the guard analysis. Assisted-by: Claude Code:claude-opus-5-5

Activity

  1. self-assigned this
    on Oct 28, 2026
  2. added theissue type on Nov 1, 2026
  3. added a commit that references this issue on Nov 2, 2026
    9bdeaa5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions