Repository: redis/redis

Problem statement:
[BUG] lpop/pop with count 0 returning null
**Describe the bug**

The [LPOP documentation](https://redis.io/commands/lpop) says that when a count is given the result is "Array reply: list of popped elements, or nil when key does not exist." However, [this fast path](https://github.com/redis/redis/blob/c5e6a6204c4cf57f85e7c83a9b4e99f1a7204fd2/src/t_list.c#L538) writes a nil reply when count is 0, rather than an empty list.

It's also exiting before even checking the type of the key, so instead of getting a WRONGTYPE error, it can "successfully" do an LPOP on non-lists. This differs from `SPOP key 0` which return a WRONGTYPE error.

**To reproduce**

With `redis-cli` (note, I'm using server version 6.2.6, but only have redis-cli 5.0.7 handy, so this is presumably all RESP2):
```
127.0.0.1:6379> rpush foo a
(integer) 1
127.0.0.1:6379> lpop foo 0
(nil)
```

**Expected behavior**

Expected to get `(empty list or set)`.

**Additional information**

It seems a bit odd that SPOP returns an empty array if the key does not exist while LPOP/RPOP returns nil.


Hints:
i don't know the `nil` history, we may not be able to change it very well for compatibility.
but the `WRONGTYPE` error indeed is really weird...

```
127.0.0.1:6379> set foo a
OK
127.0.0.1:6379> lpop foo 0
(nil)
127.0.0.1:6379> zpopmax foo 0
(empty array)
127.0.0.1:6379> spop foo 0
(error) WRONGTYPE Operation against a key holding the wrong kind of value
```

Patch:
diff --git a/src/t_list.c b/src/t_list.c
index 0744e4dd2df..6a4bcf5eb7a 100644
--- a/src/t_list.c
+++ b/src/t_list.c
@@ -522,6 +522,7 @@ void listElementsRemoved(client *c, robj *key, int where, robj *o, long count, i
  * optional count may be provided as the third argument of the client's
  * command. */
 void popGenericCommand(client *c, int where) {
+    int hascount = (c->argc == 3);
     long count = 0;
     robj *value;
 
@@ -529,21 +530,22 @@ void popGenericCommand(client *c, int where) {
         addReplyErrorFormat(c,"wrong number of arguments for '%s' command",
                             c->cmd->name);
         return;
-    } else if (c->argc == 3) {
+    } else if (hascount) {
         /* Parse the optional count argument. */
         if (getPositiveLongFromObjectOrReply(c,c->argv[2],&count,NULL) != C_OK) 
             return;
-        if (count == 0) {
-            /* Fast exit path. */
-            addReplyNullArray(c);
-            return;
-        }
     }
 
     robj *o = lookupKeyWriteOrReply(c, c->argv[1], shared.null[c->resp]);
     if (o == NULL || checkType(c, o, OBJ_LIST))
         return;
 
+    if (hascount && !count) {
+        /* Fast exit path. */
+        addReply(c,shared.emptyarray);
+        return;
+    }
+
     if (!count) {
         /* Pop a single element. This is POP's original behavior that replies
          * with a bulk string. */


Test patch:
diff --git a/tests/unit/type/list.tcl b/tests/unit/type/list.tcl
index 41b70ccb9c5..754083f702a 100644
--- a/tests/unit/type/list.tcl
+++ b/tests/unit/type/list.tcl
@@ -133,10 +133,9 @@ start_server {
         assert_equal $largevalue(linkedlist) [r rpop mylist2]
         assert_equal c [r lpop mylist2]
     }
-    
-    test {R/LPOP with the optional count argument} {
+
+    test {RPOP/LPOP with the optional count argument} {
         assert_equal 7 [r lpush listcount aa bb cc dd ee ff gg]
-        assert_equal {} [r lpop listcount 0]
         assert_equal {gg} [r lpop listcount 1]
         assert_equal {ff ee} [r lpop listcount 2]
         assert_equal {aa bb} [r rpop listcount 2]
@@ -145,6 +144,16 @@ start_server {
         assert_error "*ERR*range*" {r lpop forbarqaz -123}
     }
 
+    # Make sure we can distinguish between an empty array and a null response
+    r readraw 1
+
+    test {RPOP/LPOP with the count 0 returns an empty array} {
+        r lpush listcount zero
+        r lpop listcount 0
+    } {*0}
+
+    r readraw 0
+
     test {Variadic RPUSH/LPUSH} {
         r del mylist
         assert_equal 4 [r lpush mylist a b c d]