Re: BUG: PL/pgSQL FOREACH misparses variable named "slice" with SLICE clause - Mailing list pgsql-bugs

From David G. Johnston
Subject Re: BUG: PL/pgSQL FOREACH misparses variable named "slice" with SLICE clause
Date
Msg-id CAKFQuwbjcqPV-xf6FUq4ECrwmt0woWVB78gs2sszJWVzJH5dYA@mail.gmail.com
Whole thread
In response to BUG: PL/pgSQL FOREACH misparses variable named "slice" with SLICE clause  (Leendert Gravendeel <leenderthenk@gmail.com>)
List pgsql-bugs
On Fri, Apr 17, 2026 at 7:33 AM Leendert Gravendeel <leenderthenk@gmail.com> wrote:
I believe I have found a parser issue in PL/pgSQL involving the
FOREACH ... SLICE syntax.

Thanks for the report!



  CREATE FUNCTION test_slice_conflict() RETURNS text
  LANGUAGE plpgsql AS $$
  DECLARE
    slice integer[];
    arr integer[] := ARRAY[[1,2],[3,4]];
  BEGIN
    FOREACH slice SLICE 1 IN ARRAY arr LOOP
    END LOOP;
    RETURN 'ok';
  END;
  $$;

Observed behavior:
The function fails to compile due to incorrect parsing of `slice`
as the SLICE keyword.

Expected behavior:
`slice` should be treated as a normal identifier (loop variable),
and the function should compile and run successfully.


Confirmed on master.

Chat provided much more context for how/why this happened and why this seems like a good fix; prior art is my main argument though.

If a PL/pgSQL variable is named "slice", using it in a FOREACH ... SLICE
loop produces a spurious syntax error:

  DO $$ DECLARE
      slice integer[];
      arr   integer[] := ARRAY[[1,2],[3,4]];
  BEGIN
      FOREACH slice SLICE 1 IN ARRAY arr LOOP
      END LOOP;
  END; $$;
  ERROR:  syntax error at or near "SLICE"

The one-token lookahead in the for_variable grammar action runs under
normal identifier lookup, so when "slice" is in scope the following SLICE
keyword is consumed as a T_DATUM reference rather than K_SLICE, and
foreach_slice fails.

The fix is to suppress variable lookup for that lookahead, using the same
save/restore pattern already used in pl_gram.y::read_cursor_args():

  /* Read the argument name, ignoring any matching variable */
  save_IdentifierLookup = plpgsql_IdentifierLookup;
  plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE;
  yylex(yylvalp, yyllocp, yyscanner);
  argname = yylvalp->str;
  plpgsql_IdentifierLookup = save_IdentifierLookup;

Therefore we need:

diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 5009e59a78f..681fd3d5cff 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -1635,11 +1635,15 @@ for_variable : T_DATUM
                     else
                     {
                         int tok;
+ IdentifierLookup save_IdentifierLookup;
 
                         $$.scalar = $1.datum;
                         $$.row = NULL;
                         /* check for comma-separated list */
+ save_IdentifierLookup = plpgsql_IdentifierLookup;
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE;
                         tok = yylex(&yylval, &yylloc, yyscanner);
+ plpgsql_IdentifierLookup = save_IdentifierLookup;
                         plpgsql_push_back_token(tok, &yylval, &yylloc, yyscanner);
                         if (tok == ',')
                             $$.row = (PLpgSQL_datum *)

David J.

pgsql-bugs by date:

Previous
From: Tom Lane
Date:
Subject: Re: BUG: PL/pgSQL FOREACH misparses variable named "slice" with SLICE clause
Next
From: Jacob Champion
Date:
Subject: Re: PostgreSQL 17: Bug in libpq when libpq is dlopened/closed multiple times