Clojure

case fails when a single single clause with an empty test seq is used

Details

  • Type: Defect Defect
  • Status: Open Open
  • Priority: Minor Minor
  • Resolution: Unresolved
  • Affects Version/s: Release 1.8
  • Fix Version/s: None
  • Component/s: None
  • Labels:
    None
  • Patch:
    Code and Test
  • Approval:
    Triaged

Description

It is not possible to use case with a single empty seq of options, or with a single seq of options and a default clause.

I would expect

(case 1 () :a :none)

to return :none, instead it fails with an uninformative exception: "Unhandled clojure.lang.ArityException: Wrong number of args (-2) passed to: core/max"

I would expect (case 1 () :a) to fail with "java.lang.IllegalArgumentException: No matching clause", but instead it also fails with
"Unhandled clojure.lang.ArityException: Wrong number of args (-2) passed to: core/max"

This seems inconsistent, as passing an empty list of options is fine when there are other alternatives:

(case 1 () :a 2 :b :none)

returns :none, as expected

The attached patch removes the test-clause pairs with empty test lists before further conversion to case*, and adds tests.

Activity

Hide
Chris Blom added a comment -

oops, typo in the title (duplicated "single")

Show
Chris Blom added a comment - oops, typo in the title (duplicated "single")
Hide
Alex Miller added a comment -

An empty match list in case seems like it should be an error - maybe this should fail to compile rather than ignoring?

Show
Alex Miller added a comment - An empty match list in case seems like it should be an error - maybe this should fail to compile rather than ignoring?
Hide
Chris Blom added a comment - - edited

An empty list of options is currently supported when multiple clauses are given, so failing to compile on empty lists would be a breaking change.

This works in 1.8:

(case 1
() :never-happens
1 :ok
:default)
=> :ok

But this does not:

(case 1
() :never-happens
:default)
=> throws clojure.lang.ArityException: Wrong number of args (-2) passed to: core/max

Only failing when no other clauses are given seems very inconsistent to me.

Show
Chris Blom added a comment - - edited An empty list of options is currently supported when multiple clauses are given, so failing to compile on empty lists would be a breaking change. This works in 1.8: (case 1 () :never-happens 1 :ok :default) => :ok But this does not: (case 1 () :never-happens :default) => throws clojure.lang.ArityException: Wrong number of args (-2) passed to: core/max Only failing when no other clauses are given seems very inconsistent to me.
Hide
Nicola Mometto added a comment -

an empty list doesn't make any sense in case as the correct way to match a literal empty list is `(case () (()) :empty)`. I don't see any value in making it not throw and my vote is to have the `case` macro complain at compile time every time a `()` ise used

Show
Nicola Mometto added a comment - an empty list doesn't make any sense in case as the correct way to match a literal empty list is `(case () (()) :empty)`. I don't see any value in making it not throw and my vote is to have the `case` macro complain at compile time every time a `()` ise used
Hide
Alex Miller added a comment -

() would never match anything now, so failing on () would not break any existing case that matches.

The one case I can imagine might exist though is a macro that creates a case and could potentially programmatically create an empty case list? Something like this:

(defmacro make-case [xs] `(defn ~'foo [e#] (case e# ~xs "matched" "nope")))
Show
Alex Miller added a comment - () would never match anything now, so failing on () would not break any existing case that matches. The one case I can imagine might exist though is a macro that creates a case and could potentially programmatically create an empty case list? Something like this:
(defmacro make-case [xs] `(defn ~'foo [e#] (case e# ~xs "matched" "nope")))
Hide
Chris Blom added a comment - - edited

I'm not using this to match empty lists, I ran into this corner case when generating case statements from a DSL.
While it is a pathological case, I disagree that is does not make any sense, an empty list here simply represents no alternatives,
so the clause wil never match and its result-expr will never run.

My point is that now (in clojure 1.8) this is allowed:

(case a
() :never-happens
1 :a
2 :b
:default)

it is equivalent to (as an empty list never matches)

(case a
1 :a
2 :b
:default)

But

(case a
() :never-happens
:default)

gives an uninformative error.

I argue that it should be equivalent to

(case a
:default)

as rejecting empty lists in case statements in general would be a breaking change,
and only rejecting empty lists when no other clauses are present is inconsistent.

Show
Chris Blom added a comment - - edited I'm not using this to match empty lists, I ran into this corner case when generating case statements from a DSL. While it is a pathological case, I disagree that is does not make any sense, an empty list here simply represents no alternatives, so the clause wil never match and its result-expr will never run. My point is that now (in clojure 1.8) this is allowed: (case a () :never-happens 1 :a 2 :b :default) it is equivalent to (as an empty list never matches) (case a 1 :a 2 :b :default) But (case a () :never-happens :default) gives an uninformative error. I argue that it should be equivalent to (case a :default) as rejecting empty lists in case statements in general would be a breaking change, and only rejecting empty lists when no other clauses are present is inconsistent.
Hide
Chris Blom added a comment -

() would never match anything now, so failing on () would not break any existing case that matches.

As () in case is allowed in clojure =<1.8 (just not when its the only clause), letting the compiler reject it would potentially break existing code.

The one case I can imagine might exist though is a macro that creates a case and could potentially programmatically create an empty case list?

That is exactly how i ran into this problem

Show
Chris Blom added a comment - () would never match anything now, so failing on () would not break any existing case that matches. As () in case is allowed in clojure =<1.8 (just not when its the only clause), letting the compiler reject it would potentially break existing code. The one case I can imagine might exist though is a macro that creates a case and could potentially programmatically create an empty case list? That is exactly how i ran into this problem

People

Vote (1)
Watch (2)

Dates

  • Created:
    Updated: